@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.js CHANGED
@@ -30308,12 +30308,12 @@ import { dirname, join as join3 } from "node:path";
30308
30308
  function pathFor(root, appSlug, kind, name) {
30309
30309
  if (!slugPattern.test(appSlug)) throw new Error("App slug is invalid");
30310
30310
  if (kind === "action" ? !actionPattern.test(name) : kind !== "app" && !resourcePattern.test(name)) throw new Error("Resource name is invalid");
30311
- return kind === "app" ? join3(root, "drafts", appSlug, "app.jsonc") : join3(root, "drafts", appSlug, paths[kind], `${name}.jsonc`);
30311
+ return kind === "app" ? join3(root, "drafts", diskName(appSlug), "app.jsonc") : join3(root, "drafts", diskName(appSlug), paths[kind], `${diskName(name)}.jsonc`);
30312
30312
  }
30313
30313
  function record2(row) {
30314
30314
  return { id: String(row.id), appSlug: String(row.app_slug), appId: row.app_id === null ? null : String(row.app_id), kind: String(row.kind), name: String(row.name), ownerSessionId: row.owner_session_id === null ? null : String(row.owner_session_id), filePath: String(row.file_path), intent: row.intent === "remove" ? "remove" : "edit", baseVersion: row.base_version === null ? null : Number(row.base_version) };
30315
30315
  }
30316
- var DraftConflict, paths, slugPattern, resourcePattern, actionPattern, DraftStore;
30316
+ var DraftConflict, paths, slugPattern, resourcePattern, actionPattern, diskName, DraftStore;
30317
30317
  var init_drafts = __esm({
30318
30318
  "src/discovery/drafts.ts"() {
30319
30319
  "use strict";
@@ -30329,6 +30329,7 @@ var init_drafts = __esm({
30329
30329
  slugPattern = /^[a-z][a-z0-9-]{0,79}$/u;
30330
30330
  resourcePattern = /^[a-z][a-z0-9_-]{0,79}$/u;
30331
30331
  actionPattern = /^[a-z][a-z0-9_-]{0,79}\.[a-z][a-z0-9_-]{0,79}$/u;
30332
+ diskName = (name) => /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/iu.test(name) ? `~${name}` : name;
30332
30333
  DraftStore = class {
30333
30334
  constructor(state, root) {
30334
30335
  this.state = state;
@@ -31114,8 +31115,24 @@ var require_dist = __commonJS({
31114
31115
  }
31115
31116
  });
31116
31117
 
31117
- // src/capture-server/client.ts
31118
+ // src/process.ts
31118
31119
  import { spawn } from "node:child_process";
31120
+ function backgroundOptions(env, platform4 = process.platform) {
31121
+ return { detached: platform4 !== "win32", windowsHide: true, stdio: "ignore", env };
31122
+ }
31123
+ function startBackground(script, env, onError) {
31124
+ const child = spawn(process.execPath, [script], backgroundOptions(env));
31125
+ child.once("error", onError);
31126
+ child.unref();
31127
+ return child;
31128
+ }
31129
+ var init_process = __esm({
31130
+ "src/process.ts"() {
31131
+ "use strict";
31132
+ }
31133
+ });
31134
+
31135
+ // src/capture-server/client.ts
31119
31136
  import { createHash as createHash2 } from "node:crypto";
31120
31137
  import { chmodSync as chmodSync4, existsSync as existsSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync4, rmSync as rmSync2 } from "node:fs";
31121
31138
  import { dirname as dirname2, join as join4 } from "node:path";
@@ -31133,6 +31150,9 @@ function compatibleBroker(client, health, requestedVersion) {
31133
31150
  if (health.protocol === "apilift.capture/v1" || health.version === requestedVersion) return true;
31134
31151
  return false;
31135
31152
  }
31153
+ function reusableBroker(client, health, requestedVersion) {
31154
+ return compatibleBroker(client, health, requestedVersion) && (health.version === requestedVersion || health.active);
31155
+ }
31136
31156
  function receiptAt(root) {
31137
31157
  try {
31138
31158
  const raw = JSON.parse(readFileSync4(join4(root, "server.json"), "utf8"));
@@ -31180,7 +31200,7 @@ async function ensureCaptureServerUnderUpdateLock(options2) {
31180
31200
  if (current) {
31181
31201
  const client = new CaptureServerClient(current);
31182
31202
  try {
31183
- if (compatibleBroker(client, await client.health(), options2.version)) return client;
31203
+ if (reusableBroker(client, await client.health(), options2.version)) return client;
31184
31204
  } catch {
31185
31205
  }
31186
31206
  }
@@ -31197,7 +31217,7 @@ async function ensureCaptureServerUnderUpdateLock(options2) {
31197
31217
  const existing = new CaptureServerClient(previous);
31198
31218
  try {
31199
31219
  const health = await existing.health();
31200
- if (compatibleBroker(existing, health, options2.version)) return existing;
31220
+ if (reusableBroker(existing, health, options2.version)) return existing;
31201
31221
  if (health.active) throw Object.assign(new Error("Capture server cannot be replaced while browser work is active"), { code: "capture-server-busy" });
31202
31222
  await existing.retire();
31203
31223
  } catch (error62) {
@@ -31209,10 +31229,13 @@ async function ensureCaptureServerUnderUpdateLock(options2) {
31209
31229
  }
31210
31230
  }
31211
31231
  if (!existsSync2(options2.script)) throw new Error(`Capture server entry point is missing: ${options2.script}`);
31212
- const child = spawn(process.execPath, [options2.script], { detached: true, stdio: "ignore", env: { ...process.env, ...options2.env, APILIFT_HOME: root } });
31213
- child.unref();
31232
+ let startupError;
31233
+ const child = startBackground(options2.script, { ...process.env, ...options2.env, APILIFT_HOME: root }, (error62) => {
31234
+ startupError = error62;
31235
+ });
31214
31236
  const deadline = Date.now() + (options2.timeoutMs ?? 1e4);
31215
31237
  while (Date.now() < deadline) {
31238
+ if (startupError || child.exitCode !== null) throw Object.assign(new Error("The local browser process could not start. Run apilift setup continue to repair the installation."), { code: "capture-server-unavailable" });
31216
31239
  const current2 = receiptAt(root);
31217
31240
  if (current2 && child.pid && current2.pid === child.pid && current2.version === options2.version) {
31218
31241
  const client = new CaptureServerClient(current2);
@@ -31233,6 +31256,7 @@ var CaptureServerClient;
31233
31256
  var init_client2 = __esm({
31234
31257
  "src/capture-server/client.ts"() {
31235
31258
  "use strict";
31259
+ init_process();
31236
31260
  CaptureServerClient = class {
31237
31261
  constructor(receipt, fetcher = fetch) {
31238
31262
  this.receipt = receipt;
@@ -31458,7 +31482,7 @@ function dataRoot(env) {
31458
31482
  const home = homedir();
31459
31483
  if (platform() === "darwin") return join7(home, "Library", "Application Support", "apilift");
31460
31484
  if (platform() === "win32") return join7(env.LOCALAPPDATA || join7(home, "AppData", "Local"), "apilift");
31461
- return join7(env.XDG_DATA_HOME || join7(home, ".local", "share"), "apilift");
31485
+ return join7(env.XDG_DATA_HOME && isAbsolute(env.XDG_DATA_HOME) ? env.XDG_DATA_HOME : join7(home, ".local", "share"), "apilift");
31462
31486
  }
31463
31487
  var LocalState;
31464
31488
  var init_state = __esm({
@@ -33393,30 +33417,58 @@ var init_dist = __esm({
33393
33417
  });
33394
33418
 
33395
33419
  // src/capture-server/store.ts
33420
+ import { mkdirSync as mkdirSync6, chmodSync as chmodSync6, readFileSync as readFileSync6 } from "node:fs";
33396
33421
  import { execFileSync } from "node:child_process";
33397
33422
  import { DatabaseSync as DatabaseSync5 } from "node:sqlite";
33398
- function ownerProcessAlive(pid, expectedStartedAt, probe = {}) {
33399
- if (!Number.isInteger(pid) || pid <= 0 || !Number.isFinite(expectedStartedAt)) return false;
33400
- const alive = probe.alive ?? ((candidate2) => {
33401
- try {
33402
- process.kill(candidate2, 0);
33403
- return true;
33404
- } catch {
33423
+ function createOwnerVerifier(probe = {}) {
33424
+ const cached2 = /* @__PURE__ */ new Map();
33425
+ const alive = probe.alive ?? pidAlive, now = probe.now ?? Date.now;
33426
+ return (pid, startedAt, fresh = false) => {
33427
+ if (!Number.isInteger(pid) || pid <= 0 || !Number.isFinite(startedAt) || !alive(pid)) {
33428
+ cached2.delete(pid);
33405
33429
  return false;
33406
33430
  }
33407
- });
33408
- const inspect = probe.inspect ?? ((command6, args, timeout) => execFileSync(command6, args, { encoding: "utf8", timeout }).trim());
33431
+ if ((probe.platform ?? process.platform) === "linux") return inspectOwner(pid, startedAt, probe);
33432
+ const prior = cached2.get(pid), time3 = now();
33433
+ if (!fresh && prior && prior.startedAt === startedAt && time3 >= prior.checkedAt && time3 - prior.checkedAt < 2e3) return true;
33434
+ cached2.delete(pid);
33435
+ if (!inspectOwner(pid, startedAt, probe)) return false;
33436
+ if (cached2.size >= 256) cached2.delete(cached2.keys().next().value);
33437
+ cached2.set(pid, { startedAt, checkedAt: now() });
33438
+ return true;
33439
+ };
33440
+ }
33441
+ function ownerProcessAlive(pid, expectedStartedAt, probe) {
33442
+ return typeof probe === "object" ? inspectOwner(pid, expectedStartedAt, probe) : defaultOwnerVerifier(pid, expectedStartedAt, probe);
33443
+ }
33444
+ function inspectOwner(pid, expectedStartedAt, probe) {
33445
+ if (!Number.isInteger(pid) || pid <= 0 || !Number.isFinite(expectedStartedAt)) return false;
33446
+ const alive = probe.alive ?? pidAlive;
33447
+ const inspect = probe.inspect ?? inspectProcess;
33409
33448
  const platform4 = probe.platform ?? process.platform;
33410
33449
  const now = probe.now ?? Date.now;
33411
33450
  const started = now();
33412
33451
  const windows = platform4 === "win32";
33452
+ if (platform4 === "linux") {
33453
+ try {
33454
+ const read = probe.read ?? ((path2) => readFileSync6(path2, "utf8"));
33455
+ const stat2 = read(`/proc/${pid}/stat`), fields = stat2.slice(stat2.lastIndexOf(")") + 2).trim().split(/\s+/u);
33456
+ if (["Z", "X"].includes(fields[0])) return false;
33457
+ const ticks = Number(fields[19]), boot = Number(/^btime (\d+)$/mu.exec(read("/proc/stat"))?.[1]);
33458
+ const hz = probe.inspect ? Number(inspect("getconf", ["CLK_TCK"], 2e3)) : linuxClockTicks ??= Number(inspect("getconf", ["CLK_TCK"], 2e3));
33459
+ const observed = boot * 1e3 + ticks * 1e3 / hz;
33460
+ return Number.isFinite(observed) && hz > 0 && Math.abs(observed - expectedStartedAt) <= 5e3;
33461
+ } catch {
33462
+ return false;
33463
+ }
33464
+ }
33413
33465
  for (let attempt = 0; attempt < (windows ? 2 : 1); attempt++) {
33414
33466
  if (!alive(pid)) return false;
33415
33467
  const remaining = windows ? 8e3 - (now() - started) : 2e3;
33416
33468
  if (remaining <= 0) return false;
33417
33469
  try {
33418
- 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);
33419
- const observed = Date.parse(raw.trim());
33470
+ 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);
33471
+ const observed = Date.parse(windows ? raw.trim() : `${raw.trim()} UTC`);
33420
33472
  return Number.isFinite(observed) && Math.abs(observed - expectedStartedAt) <= 5e3;
33421
33473
  } catch (error62) {
33422
33474
  if (!windows || error62.code !== "ETIMEDOUT") return false;
@@ -33424,25 +33476,36 @@ function ownerProcessAlive(pid, expectedStartedAt, probe = {}) {
33424
33476
  }
33425
33477
  return false;
33426
33478
  }
33479
+ var pidAlive, inspectProcess, linuxClockTicks, defaultOwnerVerifier;
33427
33480
  var init_store = __esm({
33428
33481
  "src/capture-server/store.ts"() {
33429
33482
  "use strict";
33430
33483
  init_dist();
33484
+ pidAlive = (candidate2) => {
33485
+ try {
33486
+ process.kill(candidate2, 0);
33487
+ return true;
33488
+ } catch {
33489
+ return false;
33490
+ }
33491
+ };
33492
+ inspectProcess = (command6, args, timeout) => execFileSync(command6, args, { encoding: "utf8", timeout, windowsHide: true, env: { ...process.env, LC_ALL: "C", TZ: "UTC" }, stdio: ["ignore", "pipe", "ignore"] }).trim();
33493
+ defaultOwnerVerifier = createOwnerVerifier();
33431
33494
  }
33432
33495
  });
33433
33496
 
33434
33497
  // src/discovery/lock.ts
33435
33498
  import { createHash as createHash3 } from "node:crypto";
33436
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync6, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "node:fs";
33499
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync7, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "node:fs";
33437
33500
  import { join as join10 } from "node:path";
33438
33501
  function lockDiscovery(root, sessionId) {
33439
33502
  const name = createHash3("sha256").update(sessionId).digest("hex");
33440
33503
  const directory = join10(root, "locks", `discovery-${name}`);
33441
- mkdirSync6(join10(root, "locks"), { recursive: true, mode: 448 });
33504
+ mkdirSync7(join10(root, "locks"), { recursive: true, mode: 448 });
33442
33505
  const owner = { pid: process.pid, startedAt: Date.now() - process.uptime() * 1e3 };
33443
33506
  for (let attempt = 0; attempt < 2; attempt++) {
33444
33507
  try {
33445
- mkdirSync6(directory, { mode: 448 });
33508
+ mkdirSync7(directory, { mode: 448 });
33446
33509
  writeFileSync6(join10(directory, "owner.json"), JSON.stringify(owner), { flag: "wx", mode: 384 });
33447
33510
  return () => {
33448
33511
  rmSync3(directory, { recursive: true, force: true });
@@ -33451,7 +33514,7 @@ function lockDiscovery(root, sessionId) {
33451
33514
  if (error62.code !== "EEXIST") throw error62;
33452
33515
  let held = null;
33453
33516
  try {
33454
- held = JSON.parse(readFileSync6(join10(directory, "owner.json"), "utf8"));
33517
+ held = JSON.parse(readFileSync7(join10(directory, "owner.json"), "utf8"));
33455
33518
  } catch {
33456
33519
  }
33457
33520
  if (!held || ownerProcessAlive(held.pid, held.startedAt)) throw new Error("discovery-command-busy \u2014 This agent already has a discovery command running; wait for it to finish, then retry");
@@ -33495,7 +33558,7 @@ var init_session = __esm({
33495
33558
  });
33496
33559
 
33497
33560
  // src/commands/discover.ts
33498
- import { existsSync as existsSync3, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync2, readFileSync as readFileSync7, renameSync as renameSync2, writeFileSync as writeFileSync7 } from "node:fs";
33561
+ import { existsSync as existsSync3, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync2, readFileSync as readFileSync8, renameSync as renameSync2, writeFileSync as writeFileSync7 } from "node:fs";
33499
33562
  import { randomUUID as randomUUID8 } from "node:crypto";
33500
33563
  import { tmpdir as tmpdir2 } from "node:os";
33501
33564
  import { dirname as dirname5, join as join11 } from "node:path";
@@ -33654,7 +33717,7 @@ ${local}When ready to test the configured request: ${command4(invocation, "disco
33654
33717
  For browser observation: ${command4(invocation, "discover capture start")}
33655
33718
  `;
33656
33719
  if (draft.kind === "transport") {
33657
- const browser = parseDraftEnvelope(readFileSync7(draft.filePath, "utf8")).envelope?.definition.mode === "browser";
33720
+ const browser = parseDraftEnvelope(readFileSync8(draft.filePath, "utf8")).envelope?.definition.mode === "browser";
33658
33721
  const browserAuth = browser ? `
33659
33722
  For a page-derived authentication value, place its observed locator under definition.inject[].from and the destination under definition.inject[].to. Example invented notes app definition fragment: ${JSON.stringify(browserLocatorExample)}. definition.credentials is only for separately saved human-provided secrets; leave it [] when browser login supplies access. Never copy a token value. Verify the harmless read before accepting the recipe.
33660
33723
  ` : "";
@@ -33698,7 +33761,7 @@ File: ${selected.filePath}
33698
33761
  if (draft.intent === "remove" && store.publication(draft.id)) return fail5(`A removal publication may have been sent. Reconcile its retained request with ${command4(invocation, "discover publish")} before changing, deferring or discarding it.
33699
33762
  File: ${draft.filePath}
33700
33763
  `, 12);
33701
- if (resource && readFileSync7(draft.filePath, "utf8") === draftTemplate(kind)) {
33764
+ if (resource && readFileSync8(draft.filePath, "utf8") === draftTemplate(kind)) {
33702
33765
  const definition2 = resource.definition;
33703
33766
  const dependencies = kind === "action" && !("unavailable" in definition2) ? { transport: String(definition2.transport ?? "") } : kind === "transport" && definition2.browserContext ? { browserPage: String(definition2.browserContext) } : void 0;
33704
33767
  const editable = { definition: definition2, ...dependencies ? { dependencies } : {}, ...kind === "action" && !("unavailable" in definition2) || kind === "transport" ? { verification: { inputs: {}, review: "pending", notes: "" } } : {} };
@@ -33706,7 +33769,7 @@ File: ${draft.filePath}
33706
33769
  `, { mode: 384 });
33707
33770
  }
33708
33771
  void root;
33709
- return ok4(draftGuidance(invocation, draft, Boolean(parseDraftEnvelope(readFileSync7(draft.filePath, "utf8")).envelope?.definition.unavailable)));
33772
+ return ok4(draftGuidance(invocation, draft, Boolean(parseDraftEnvelope(readFileSync8(draft.filePath, "utf8")).envelope?.definition.unavailable)));
33710
33773
  }
33711
33774
  function requireDiscovery(store, session, invocation) {
33712
33775
  const current = store.session(session.id);
@@ -33733,7 +33796,7 @@ function handleResearch(invocation, state, store, session, root) {
33733
33796
  return fail5(`${error62 instanceof Error ? error62.message : "Invalid research scope"}.
33734
33797
  `);
33735
33798
  }
33736
- mkdirSync7(dirname5(path2), { recursive: true, mode: 448 });
33799
+ mkdirSync8(dirname5(path2), { recursive: true, mode: 448 });
33737
33800
  if (!existsSync3(path2)) writeFileSync7(path2, `// Record a focused check of official operation and access documentation.
33738
33801
  ${JSON.stringify({ scope: scope2, conclusion: "pending", findings: "", sources: [{ kind: "operation", url: "", finding: "" }, { kind: "access", url: "", finding: "" }] }, null, 2)}
33739
33802
  `, { flag: "wx", mode: 384 });
@@ -33767,7 +33830,7 @@ If newer official docs change a finding, edit its file and run ${command4(invoca
33767
33830
  const selection = state.db.prepare("SELECT r.* FROM discovery_research r JOIN selected_discovery_research s ON s.app_slug=r.app_slug AND s.scope=r.scope WHERE s.session_id=?").get(session.id);
33768
33831
  if (!selection) return fail5(`No API research file is selected. Start with ${command4(invocation, "discover api assess <action-or-app>")}.
33769
33832
  `);
33770
- const source = readFileSync7(selection.file_path, "utf8"), errors = [];
33833
+ const source = readFileSync8(selection.file_path, "utf8"), errors = [];
33771
33834
  const seen = [], duplicates = [];
33772
33835
  visit3(source, { onObjectBegin: () => {
33773
33836
  seen.push(/* @__PURE__ */ new Set());
@@ -34194,7 +34257,7 @@ async function rebaseDraft(invocation, api, store, session) {
34194
34257
  if (draft.intent === "remove") {
34195
34258
  const checked = await checkDraft(store, session.id, api);
34196
34259
  if (checked.issues.length) return checkResponse(checked, invocation);
34197
- const source = readFileSync7(draft.filePath, "utf8");
34260
+ const source = readFileSync8(draft.filePath, "utf8");
34198
34261
  const updated = applyEdits(source, modify(source, ["expectedVersion"], current.version, { formattingOptions: { insertSpaces: true, tabSize: 2 } }));
34199
34262
  const temporary = `${draft.filePath}.${randomUUID8()}.tmp`;
34200
34263
  writeFileSync7(temporary, updated, { flag: "wx", mode: 384 });
@@ -34826,715 +34889,769 @@ var init_output = __esm({
34826
34889
  }
34827
34890
  });
34828
34891
 
34829
- // src/plugin.ts
34830
- import { execFileSync as execFileSync2 } from "node:child_process";
34831
- import { createHash as createHash5 } from "node:crypto";
34832
- import { cpSync, existsSync as existsSync4, mkdirSync as mkdirSync8, readFileSync as readFileSync8, readdirSync, renameSync as renameSync3, rmSync as rmSync4, writeFileSync as writeFileSync8 } from "node:fs";
34833
- import { homedir as homedir2 } from "node:os";
34834
- import { dirname as dirname6, join as join13, resolve as resolve2 } from "node:path";
34892
+ // ../../node_modules/.pnpm/tinyexec@1.3.1/node_modules/tinyexec/dist/main.mjs
34893
+ import { spawn as spawn2, spawnSync } from "node:child_process";
34894
+ import { cwd } from "node:process";
34895
+ import { basename, delimiter, dirname as dirname6, normalize, resolve as resolve2 } from "node:path";
34835
34896
  import { fileURLToPath } from "node:url";
34836
- function missingSkill(root) {
34837
- return skillNames.find((name) => !existsSync4(join13(root, "skills", name, "SKILL.md")));
34838
- }
34839
- function completeCopy(source, target) {
34840
- return files(source).every((path2) => existsSync4(join13(target, path2)));
34897
+ import { pipeline } from "node:stream/promises";
34898
+ import { PassThrough } from "node:stream";
34899
+ import readline from "node:readline";
34900
+ import { closeSync, openSync, readSync, statSync } from "node:fs";
34901
+ function getPathFromEnv(env) {
34902
+ for (const key in env) {
34903
+ if (!Object.prototype.hasOwnProperty.call(env, key) || !isPathLikePattern.test(key)) continue;
34904
+ const value = env[key];
34905
+ if (!value) return defaultEnvPathInfo;
34906
+ return {
34907
+ key,
34908
+ value
34909
+ };
34910
+ }
34911
+ return defaultEnvPathInfo;
34841
34912
  }
34842
- function files(root, relative = "") {
34843
- return readdirSync(join13(root, relative), { withFileTypes: true }).flatMap((entry2) => {
34844
- const path2 = join13(relative, entry2.name);
34845
- return entry2.isDirectory() ? files(root, path2) : [path2];
34846
- }).sort();
34913
+ function addNodeBinToPath(cwd2, path2) {
34914
+ const parts = path2.value.split(delimiter);
34915
+ const nodeBinPaths = [];
34916
+ let currentPath = typeof cwd2 === "string" ? cwd2 : fileURLToPath(cwd2, { windows: false });
34917
+ let lastPath;
34918
+ do {
34919
+ nodeBinPaths.push(resolve2(currentPath, "node_modules", ".bin"));
34920
+ lastPath = currentPath;
34921
+ currentPath = dirname6(currentPath);
34922
+ } while (currentPath !== lastPath);
34923
+ nodeBinPaths.push(dirname6(process.execPath));
34924
+ const newPath = nodeBinPaths.concat(parts).join(delimiter);
34925
+ return {
34926
+ key: path2.key,
34927
+ value: newPath
34928
+ };
34847
34929
  }
34848
- function digest(root) {
34849
- const hash4 = createHash5("sha256");
34850
- for (const path2 of files(root)) hash4.update(path2).update("\0").update(readFileSync8(join13(root, path2))).update("\0");
34851
- return hash4.digest("hex");
34930
+ function computeEnv(cwd2, env, nodePath = true) {
34931
+ const envWithDefault = {
34932
+ ...process.env,
34933
+ ...env
34934
+ };
34935
+ if (!nodePath) return envWithDefault;
34936
+ const envPathInfo = addNodeBinToPath(cwd2, getPathFromEnv(envWithDefault));
34937
+ envWithDefault[envPathInfo.key] = envPathInfo.value;
34938
+ return envWithDefault;
34852
34939
  }
34853
- function writeJson(path2, value) {
34854
- mkdirSync8(dirname6(path2), { recursive: true });
34855
- writeFileSync8(path2, `${JSON.stringify(value, null, 2)}
34856
- `, { mode: 384 });
34940
+ function normalizeSpawnCommand(command6, args = [], options2 = {}) {
34941
+ if (options2.shell === true || !isWindows) return {
34942
+ command: command6,
34943
+ args,
34944
+ options: options2
34945
+ };
34946
+ let file2 = resolveCommand(command6, options2);
34947
+ let shebang = null;
34948
+ if (file2 !== null) {
34949
+ const size = 150;
34950
+ const buffer = Buffer.alloc(size);
34951
+ let fd = null;
34952
+ try {
34953
+ fd = openSync(file2, "r");
34954
+ readSync(fd, buffer, 0, size, 0);
34955
+ } catch {
34956
+ } finally {
34957
+ if (fd !== null) closeSync(fd);
34958
+ }
34959
+ const match = buffer.toString().match(shebangRegExp);
34960
+ if (match !== null) {
34961
+ const line = match[1].trim();
34962
+ const separatorIndex = line.indexOf(" ");
34963
+ const path2 = separatorIndex !== -1 ? line.slice(0, separatorIndex) : line;
34964
+ const argument = separatorIndex !== -1 ? line.slice(separatorIndex + 1) : "";
34965
+ const binary = basename(path2);
34966
+ shebang = binary === "env" ? argument || null : binary;
34967
+ }
34968
+ }
34969
+ if (shebang !== null && file2 !== null) {
34970
+ args = [file2, ...args];
34971
+ command6 = shebang;
34972
+ file2 = resolveCommand(command6, options2);
34973
+ }
34974
+ if (file2 === null || !isWindowsExecutableRegExp.test(file2)) {
34975
+ const needsDoubleEscapeMetaChars = file2 !== null && isNodeModulesCmdRegExp.test(file2);
34976
+ command6 = normalize(command6);
34977
+ command6 = command6.replace(metaCharsRegExp, "^$1");
34978
+ args = args.map((arg) => {
34979
+ arg = arg.replace(/(?=(\\+?)?)\1"/g, '$1$1\\"');
34980
+ arg = arg.replace(/(?=(\\+?)?)\1$/, "$1$1");
34981
+ arg = `"${arg}"`;
34982
+ arg = arg.replace(metaCharsRegExp, "^$1");
34983
+ if (needsDoubleEscapeMetaChars) arg = arg.replace(metaCharsRegExp, "^$1");
34984
+ return arg;
34985
+ });
34986
+ args = [
34987
+ "/d",
34988
+ "/s",
34989
+ "/c",
34990
+ `"${[command6, ...args].join(" ")}"`
34991
+ ];
34992
+ command6 = options2.env?.comspec ?? "cmd.exe";
34993
+ options2 = {
34994
+ ...options2,
34995
+ windowsVerbatimArguments: true
34996
+ };
34997
+ }
34998
+ return {
34999
+ command: command6,
35000
+ args,
35001
+ options: options2
35002
+ };
34857
35003
  }
34858
- function readJson(path2) {
34859
- try {
34860
- return JSON.parse(readFileSync8(path2, "utf8"));
34861
- } catch {
34862
- return null;
35004
+ function resolveCommand(command6, options2) {
35005
+ const cwd$3 = (options2.cwd ?? cwd()).toString();
35006
+ const env = options2.env ?? process.env;
35007
+ const PATH = getPathFromEnv(env).value;
35008
+ const pathEnv = command6.includes("/") || command6.includes("\\") ? [""] : [cwd$3, ...PATH.split(delimiter)];
35009
+ let pathExt = env.PATHEXT ? env.PATHEXT.split(delimiter) : defaultPathExt;
35010
+ if (command6.includes(".") && pathExt[0] !== "") pathExt = ["", ...pathExt];
35011
+ for (const extensions of [pathExt, noPathExt]) for (const path2 of pathEnv) {
35012
+ const unquoted = path2.startsWith('"') && path2.endsWith('"') && path2.length > 1 ? path2.slice(1, -1) : path2;
35013
+ const dest = resolve2(cwd$3, unquoted, command6);
35014
+ for (const ext of extensions) {
35015
+ const destWithExt = dest + ext;
35016
+ try {
35017
+ if (statSync(destWithExt).isFile()) return destWithExt;
35018
+ } catch {
35019
+ }
35020
+ }
34863
35021
  }
35022
+ return null;
34864
35023
  }
34865
- function command5(binary, args) {
34866
- const output2 = execFileSync2(binary, args, { encoding: "utf8", timeout: 1e4, stdio: ["ignore", "pipe", "pipe"] });
34867
- try {
34868
- return JSON.parse(output2);
34869
- } catch {
34870
- return output2;
35024
+ function combineSignals(signals) {
35025
+ const controller = new AbortController();
35026
+ for (const signal of signals) {
35027
+ if (signal.aborted) {
35028
+ controller.abort();
35029
+ return signal;
35030
+ }
35031
+ const onAbort = () => {
35032
+ controller.abort(signal.reason);
35033
+ };
35034
+ signal.addEventListener("abort", onAbort, { signal: controller.signal });
34871
35035
  }
35036
+ return controller.signal;
34872
35037
  }
34873
- function active(agent2, expected) {
34874
- const args = agent2 === "codex" ? ["plugin", "list", "--marketplace", "personal", "--json"] : ["plugin", "list", "--json"];
35038
+ async function readStream(stream) {
35039
+ let output2 = "";
34875
35040
  try {
34876
- const listed = command5(agent2, args);
34877
- const entries = agent2 === "codex" ? listed?.installed : listed;
34878
- if (!Array.isArray(entries) || entries.some((entry2) => typeof entry2 !== "object" || entry2 === null || typeof (agent2 === "codex" ? entry2.pluginId : entry2.id) !== "string")) return { ready: false, known: false, detail: `${agent2} plugin status could not be checked: plugin list returned an unexpected response` };
34879
- const plugin = entries.find((entry2) => (agent2 === "codex" ? entry2.pluginId : entry2.id) === "apilift@personal");
34880
- if (!plugin) return { ready: false, known: true, detail: "Apilift is not installed in this agent" };
34881
- if (typeof plugin.enabled !== "boolean" || typeof plugin.version !== "string") return { ready: false, known: false, detail: `${agent2} plugin status could not be checked: Apilift status is incomplete` };
34882
- if (!plugin.enabled) return { ready: false, known: true, detail: "Apilift is disabled in this agent" };
34883
- if (plugin.version !== expected) return { ready: false, known: true, detail: "The active Apilift plugin does not match the packaged version" };
34884
- return { ready: true, known: true, detail: "Apilift setup, use and discovery skills are active" };
34885
- } catch (error62) {
34886
- const code = typeof error62 === "object" && error62 && "code" in error62 ? error62.code : void 0;
34887
- const reason = code === "ETIMEDOUT" ? "plugin list timed out after 10 seconds" : code === "ENOENT" ? `${agent2} executable was not found` : "plugin list failed";
34888
- return { ready: false, known: false, detail: `${agent2} plugin status could not be checked: ${reason}` };
35041
+ for await (const chunk of stream) output2 += chunk.toString();
35042
+ } catch {
34889
35043
  }
35044
+ return output2;
34890
35045
  }
34891
- function inspectAgentPlugin(agent2, env, sourceOverride) {
34892
- if (env.APILIFT_DEV_PLUGIN_ROOT) return { ready: false, version: "development", detail: "Development plugin activation has not been verified" };
34893
- const source = resolve2(sourceOverride || join13(dirname6(fileURLToPath(import.meta.url)), "plugin"));
34894
- const missing = missingSkill(source);
34895
- if (missing) return { ready: false, version: "unknown", detail: `CLI package is missing its ${missing} skill` };
34896
- const version2 = readJson(join13(source, `.${agent2}-plugin`, "plugin.json"))?.version;
34897
- if (typeof version2 !== "string" || !/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/.test(version2)) throw new Error("Plugin package version is invalid");
34898
- const bundleDigest = digest(source);
34899
- const expected = agent2 === "codex" ? `${version2}+codex.${bundleDigest.slice(0, 12)}` : version2;
34900
- const home = resolve2(env.APILIFT_AGENT_HOME || homedir2());
34901
- const plugin = agent2 === "codex" ? join13(home, "plugins", "apilift") : join13(home, ".agents", "plugins", "plugins", "apilift");
34902
- const marker = readJson(join13(plugin, markerName));
34903
- const staged = marker?.package === "apilift" && marker.bundleDigest === bundleDigest && marker.version === version2 && completeCopy(source, plugin);
34904
- if (!staged) return { ready: false, version: expected, detail: "Apilift plugin files are missing or stale" };
34905
- if (env.APILIFT_AGENT_HOME || env.APILIFT_AGENT_NO_ACTIVATE === "1") return { ready: false, version: expected, detail: "Apilift skills are staged; activation is unverified" };
34906
- const activation = active(agent2, expected);
34907
- return { ready: activation.ready, version: expected, detail: activation.detail, checkFailed: !activation.known };
35046
+ function xSync(command6, args, options2) {
35047
+ const opts = {
35048
+ ...defaultSyncOptions,
35049
+ ...options2
35050
+ };
35051
+ const nodeOptions = {
35052
+ ...defaultNodeOptions,
35053
+ ...opts.nodeOptions
35054
+ };
35055
+ const cwd$2 = nodeOptions?.cwd ?? cwd();
35056
+ if (opts.timeout !== void 0) nodeOptions.timeout = opts.timeout;
35057
+ nodeOptions.env = computeEnv(cwd$2, nodeOptions.env, opts.nodePath);
35058
+ const crossResult = normalizeSpawnCommand(command6, args ?? [], nodeOptions);
35059
+ const spawnResult = spawnSync(crossResult.command, crossResult.args, crossResult.options);
35060
+ if (spawnResult.error) throw spawnResult.error;
35061
+ const stdout = spawnResult.stdout?.toString() ?? "";
35062
+ const stderr = spawnResult.stderr?.toString() ?? "";
35063
+ const exitCode = spawnResult.status ?? void 0;
35064
+ const signalCode = spawnResult.signal ?? null;
35065
+ const killed = signalCode !== null;
35066
+ const result3 = {
35067
+ stdout,
35068
+ stderr,
35069
+ get exitCode() {
35070
+ return exitCode;
35071
+ },
35072
+ get signalCode() {
35073
+ return signalCode;
35074
+ },
35075
+ get pid() {
35076
+ return spawnResult.pid;
35077
+ },
35078
+ get killed() {
35079
+ return killed;
35080
+ },
35081
+ *[Symbol.iterator]() {
35082
+ for (const text2 of [stdout, stderr]) {
35083
+ if (!text2) continue;
35084
+ const lines = text2.split(LINE_SEPARATOR_REGEX);
35085
+ if (lines[lines.length - 1] === "") lines.pop();
35086
+ yield* lines;
35087
+ }
35088
+ }
35089
+ };
35090
+ if (opts.throwOnError && (exitCode !== 0 && exitCode !== void 0 || signalCode !== null)) throw new NonZeroExitError(result3, {
35091
+ stdout: result3.stdout,
35092
+ stderr: result3.stderr,
35093
+ exitCode: result3.exitCode
35094
+ }, command6, args);
35095
+ return result3;
34908
35096
  }
34909
- function ensureAgentPlugin(agent2, env, sourceOverride) {
34910
- if (env.APILIFT_DEV_PLUGIN_ROOT) {
34911
- const source2 = resolve2(env.APILIFT_DEV_PLUGIN_ROOT);
34912
- const missing2 = missingSkill(source2);
34913
- if (missing2) throw new Error(`Development plugin ${missing2} skill is missing`);
34914
- const bundleDigest2 = digest(source2);
34915
- const root = resolve2(env.APILIFT_HOME || join13(homedir2(), ".local", "share", "apilift"));
34916
- writeJson(join13(root, "agent-plugins", `${agent2}.json`), { root: source2, digest: bundleDigest2 });
34917
- return { ready: false, version: `dev+${bundleDigest2.slice(0, 12)}`, detail: "Development plugin source recorded; activation was not verified" };
34918
- }
34919
- const source = resolve2(sourceOverride || join13(dirname6(fileURLToPath(import.meta.url)), "plugin"));
34920
- const missing = missingSkill(source);
34921
- if (missing) throw new Error(`CLI package is missing its ${missing} skill`);
34922
- const version2 = readJson(join13(source, `.${agent2}-plugin`, "plugin.json"))?.version;
34923
- if (typeof version2 !== "string" || !/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/.test(version2)) throw new Error("Plugin package version is invalid");
34924
- const bundleDigest = digest(source);
34925
- const home = resolve2(env.APILIFT_AGENT_HOME || homedir2());
34926
- const marketplace = join13(home, ".agents", "plugins");
34927
- const plugin = agent2 === "codex" ? join13(home, "plugins", "apilift") : join13(marketplace, "plugins", "apilift");
34928
- const marker = readJson(join13(plugin, markerName));
34929
- if (existsSync4(plugin) && marker?.package !== "apilift") throw new Error(`Refusing to replace unmanaged plugin ${plugin}`);
34930
- const expected = agent2 === "codex" ? `${version2}+codex.${bundleDigest.slice(0, 12)}` : version2;
34931
- const current = marker?.bundleDigest === bundleDigest && marker.version === version2 && completeCopy(source, plugin);
34932
- if (!current) {
34933
- const staging = `${plugin}.stage-${process.pid}`;
34934
- rmSync4(staging, { recursive: true, force: true });
34935
- mkdirSync8(dirname6(staging), { recursive: true });
34936
- cpSync(source, staging, { recursive: true });
34937
- const manifestPath = join13(staging, `.${agent2}-plugin`, "plugin.json");
34938
- const manifest = readJson(manifestPath);
34939
- if (!manifest) throw new Error("Plugin manifest is invalid");
34940
- manifest.version = expected;
34941
- writeJson(manifestPath, manifest);
34942
- writeJson(join13(staging, markerName), { schema: "apilift.plugin-install/v1", package: "apilift", version: version2, bundleDigest });
34943
- const backup = `${plugin}.backup-${process.pid}`;
34944
- if (existsSync4(plugin)) renameSync3(plugin, backup);
34945
- try {
34946
- renameSync3(staging, plugin);
34947
- } catch (error62) {
34948
- if (existsSync4(backup)) renameSync3(backup, plugin);
34949
- throw error62;
34950
- }
34951
- rmSync4(backup, { recursive: true, force: true });
34952
- }
34953
- const marketplacePath = join13(marketplace, "marketplace.json");
34954
- const document = readJson(marketplacePath) ?? { name: "personal", interface: { displayName: "Personal" }, plugins: [] };
34955
- if (document.name !== "personal" || !Array.isArray(document.plugins)) throw new Error("Unsupported personal plugin marketplace");
34956
- document.plugins = [...document.plugins.filter((item) => typeof item === "object" && item !== null && item.name !== "apilift"), { name: "apilift", source: { source: "local", path: "./plugins/apilift" }, policy: { installation: "AVAILABLE", authentication: "ON_INSTALL" }, category: "Developer Tools" }];
34957
- writeJson(marketplacePath, document);
34958
- writeJson(join13(marketplace, ".claude-plugin", "marketplace.json"), { "$schema": "https://anthropic.com/claude-code/marketplace.schema.json", name: "personal", description: "Personal agent plugins", owner: { name: "Local" }, plugins: [{ name: "apilift", description: "Use logged-in apps and discover missing actions with Apilift", source: "./plugins/apilift", category: "development" }] });
34959
- if (env.APILIFT_AGENT_HOME || env.APILIFT_AGENT_NO_ACTIVATE === "1") return { ready: false, version: expected, detail: `Plugin staged at ${plugin}; activation is disabled` };
34960
- const initial = active(agent2, expected);
34961
- if (!initial.known) return { ready: false, version: expected, detail: initial.detail, checkFailed: true };
34962
- if (initial.ready) return { ready: true, version: expected, detail: initial.detail };
34963
- {
34964
- if (agent2 === "codex") command5("codex", ["plugin", "add", "apilift@personal"]);
34965
- else {
34966
- let marketplaces;
34967
- try {
34968
- marketplaces = command5("claude", ["plugin", "marketplace", "list", "--json"]);
34969
- } catch {
34970
- marketplaces = [];
35097
+ var isPathLikePattern, defaultEnvPathInfo, combineStreams, metaCharsRegExp, shebangRegExp, isWindowsExecutableRegExp, isNodeModulesCmdRegExp, isWindows, defaultPathExt, noPathExt, NonZeroExitError, LINE_SEPARATOR_REGEX, defaultOptions, defaultSyncOptions, defaultNodeOptions, ExecProcess, x, exec;
35098
+ var init_main2 = __esm({
35099
+ "../../node_modules/.pnpm/tinyexec@1.3.1/node_modules/tinyexec/dist/main.mjs"() {
35100
+ isPathLikePattern = /^path$/i;
35101
+ defaultEnvPathInfo = {
35102
+ key: "PATH",
35103
+ value: ""
35104
+ };
35105
+ combineStreams = (streams) => {
35106
+ let streamCount = streams.length;
35107
+ const combined = new PassThrough();
35108
+ const maybeEmitEnd = () => {
35109
+ if (--streamCount === 0) combined.end();
35110
+ };
35111
+ for (const stream of streams) pipeline(stream, combined, { end: false }).then(maybeEmitEnd).catch(maybeEmitEnd);
35112
+ return combined;
35113
+ };
35114
+ metaCharsRegExp = /([()\][%!^"`<>&|;, *?])/g;
35115
+ shebangRegExp = /^#!\s*(.+)/;
35116
+ isWindowsExecutableRegExp = /\.(?:com|exe)$/i;
35117
+ isNodeModulesCmdRegExp = /node_modules[\\/]\.bin[\\/][^\\/]+\.cmd$/i;
35118
+ isWindows = process.platform === "win32";
35119
+ defaultPathExt = [
35120
+ ".EXE",
35121
+ ".CMD",
35122
+ ".BAT",
35123
+ ".COM"
35124
+ ];
35125
+ noPathExt = [""];
35126
+ NonZeroExitError = class extends Error {
35127
+ result;
35128
+ output;
35129
+ exitCode;
35130
+ get signalCode() {
35131
+ return this.result.signalCode;
35132
+ }
35133
+ constructor(result3, output2, command6, args) {
35134
+ let target = "The process";
35135
+ if (command6) target = `The command \`${args?.length ? `${command6} ${args.map((a) => /[ "'`()]/.test(a) ? JSON.stringify(a) : a).join(" ")}` : command6}\``;
35136
+ const exitCode = result3.exitCode ?? 1;
35137
+ super(result3.signalCode !== null ? `${target} was killed by the signal ${result3.signalCode}` : `${target} exited with a non-zero status (${exitCode})`);
35138
+ this.result = result3;
35139
+ this.output = output2;
35140
+ this.exitCode = exitCode;
35141
+ Object.defineProperty(this, "result", {
35142
+ enumerable: false,
35143
+ writable: false,
35144
+ configurable: false
35145
+ });
34971
35146
  }
34972
- if (!Array.isArray(marketplaces) || !marketplaces.some((item) => typeof item === "object" && item !== null && item.name === "personal")) command5("claude", ["plugin", "marketplace", "add", marketplace, "--scope", "user"]);
34973
- const listed = command5("claude", ["plugin", "list", "--json"]);
34974
- const installed = Array.isArray(listed) && listed.some((item) => typeof item === "object" && item !== null && item.id === "apilift@personal");
34975
- command5("claude", ["plugin", installed ? "update" : "install", "apilift@personal", "--scope", "user"]);
34976
- command5("claude", ["plugin", "enable", "apilift@personal", "--scope", "user"]);
34977
- }
34978
- }
34979
- const activation = active(agent2, expected);
34980
- return { ready: activation.ready, version: expected, detail: activation.detail, checkFailed: !activation.known };
34981
- }
34982
- var markerName, skillNames;
34983
- var init_plugin = __esm({
34984
- "src/plugin.ts"() {
34985
- "use strict";
34986
- markerName = ".apilift-managed.json";
34987
- skillNames = ["setup", "use", "discover"];
34988
- }
34989
- });
34990
-
34991
- // ../../packages/core/dist/analytics/index.js
34992
- function normalizeRoute(route) {
34993
- try {
34994
- const parsed = new URL(route, "https://apilift.dev");
34995
- return parsed.pathname.replace(/^\/dashboard\/apps\/[^/]+/, "/dashboard/apps/:app").replace(uuid5, ":id").replace(longId, ":id");
34996
- } catch {
34997
- return "/invalid";
34998
- }
34999
- }
35000
- function scrubEventProperties(event, input2) {
35001
- const allowed = eventProperties[event];
35002
- const result3 = {};
35003
- for (const [key, value] of Object.entries(input2)) {
35004
- if (!allowed.includes(key))
35005
- continue;
35006
- const normalized = key === "route" && typeof value === "string" ? normalizeRoute(value) : value;
35007
- if (typeof normalized === "boolean" || typeof normalized === "number" && Number.isFinite(normalized) || typeof normalized === "string" && safeString.test(normalized))
35008
- result3[key] = normalized;
35009
- }
35010
- return result3;
35011
- }
35012
- var eventProperties, safeString, uuid5, longId;
35013
- var init_analytics = __esm({
35014
- "../../packages/core/dist/analytics/index.js"() {
35015
- "use strict";
35016
- eventProperties = {
35017
- "$pageview": ["route"],
35018
- navigation_clicked: ["route", "target"],
35019
- pricing_viewed: ["route"],
35020
- catalog_viewed: ["route"],
35021
- app_page_viewed: ["route"],
35022
- onboarding_entered: ["route"],
35023
- broken_page_viewed: ["route"],
35024
- authentication_started: ["source", "stage"],
35025
- authentication_code_sent: ["source", "stage"],
35026
- authentication_completed: ["source", "stage"],
35027
- authentication_failed: ["source", "stage", "code"],
35028
- onboarding_attempt_started: ["attempt_id", "agent", "stage", "outcome", "source", "target"],
35029
- onboarding_stage_viewed: ["attempt_id", "agent", "stage"],
35030
- setup_prompt_copied: ["attempt_id", "agent", "stage"],
35031
- onboarding_next_action_selected: ["attempt_id", "agent", "target"],
35032
- onboarding_failed: ["stage", "attempt_id", "code", "outcome", "duration_ms"],
35033
- checkout_requested: ["plan", "interval", "stage", "source"],
35034
- checkout_ui_changed: ["plan", "interval", "stage", "source"],
35035
- billing_portal_requested: ["source"],
35036
- paid_feature_viewed: ["code", "route"],
35037
- paid_feature_upgrade_opened: ["code", "route"],
35038
- account_created: [],
35039
- account_deleted: [],
35040
- agent_selected: ["agent", "attempt_id", "status"],
35041
- device_authorization_changed: ["agent", "attempt_id", "status"],
35042
- onboarding_stage_completed: ["stage", "active", "agent"],
35043
- installation_readiness_changed: ["stage", "active", "agent"],
35044
- app_made_public: ["app_id"],
35045
- execution_finished: ["app_id", "action_id", "outcome", "duration_ms", "http_status", "agent", "purpose"],
35046
- activation_changed: ["activated"],
35047
- execution_classified: ["purpose", "revision"],
35048
- value_snapshot: ["saved_time_ms", "saved_tokens", "saved_value_microusd", "saved_time_ms_available", "saved_tokens_available", "saved_value_microusd_available"],
35049
- trial_started: ["plan", "trial_day", "trial_days_remaining", "outcome"],
35050
- trial_progress: ["plan", "trial_day", "trial_days_remaining", "outcome"],
35051
- trial_expired: ["plan", "trial_day", "trial_days_remaining", "outcome"],
35052
- trial_completed: ["plan", "trial_day", "trial_days_remaining", "outcome"],
35053
- payment_completed: ["plan", "interval", "status", "previous_status", "currency", "amount", "billing_environment", "synthetic"],
35054
- payment_status_changed: ["plan", "interval", "status", "previous_status", "currency", "amount", "billing_environment", "synthetic"],
35055
- payment_failed: ["plan", "interval", "status", "previous_status", "currency", "amount", "billing_environment", "synthetic"],
35056
- subscription_changed: ["plan", "interval", "status", "previous_status", "currency", "amount", "billing_environment", "synthetic"],
35057
- payment_adjusted: ["plan", "interval", "status", "previous_status", "currency", "amount", "billing_environment", "synthetic"],
35058
- critical_failure: ["code", "count"],
35059
- cli_command_finished: ["command", "purpose", "outcome", "duration_ms", "stage", "agent"],
35060
- setup_stage_changed: ["stage", "agent"],
35061
- discovery_started: ["purpose", "agent"],
35062
- discovery_finished: ["purpose", "outcome", "duration_ms", "agent"],
35063
- client_exception: ["code"],
35064
- native_host_connected: [],
35065
- native_host_failure: ["code", "operation", "stage", "origin", "failure_count", "duration_ms"],
35066
- native_host_recovered: ["operation", "stage", "origin", "failure_count", "duration_ms"],
35067
- extension_installed: ["stage"],
35068
- extension_connected: ["stage"],
35069
- extension_disconnected: ["stage"],
35070
- extension_failure: ["code"],
35071
- extension_interaction: ["stage", "outcome"],
35072
- credential_setup_finished: ["stage", "outcome", "code"],
35073
- "$exception": ["route", "method", "exception_type", "exception_frames", "exception_handled", "exception_level"]
35074
35147
  };
35075
- safeString = /^[a-zA-Z0-9_./:{} -]{1,160}$/;
35076
- uuid5 = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi;
35077
- longId = /\b[a-zA-Z0-9_-]{24,}\b/g;
35078
- }
35079
- });
35080
-
35081
- // src/analytics.ts
35082
- import { randomUUID as randomUUID10 } from "node:crypto";
35083
- var EVENT, EU_HOST, CliAnalytics;
35084
- var init_analytics2 = __esm({
35085
- "src/analytics.ts"() {
35086
- "use strict";
35087
- init_analytics();
35088
- init_api2();
35089
- init_api3();
35090
- EVENT = /* @__PURE__ */ new Set(["cli_command_finished", "setup_stage_changed", "onboarding_failed", "discovery_started", "discovery_finished", "client_exception"]);
35091
- EU_HOST = "https://eu.i.posthog.com";
35092
- CliAnalytics = class {
35093
- constructor(state, release = "1.0.0", fetcher = fetch) {
35094
- this.state = state;
35095
- this.release = release;
35096
- this.fetcher = fetcher;
35097
- state.db.exec("CREATE TABLE IF NOT EXISTS cli_analytics_identity (id INTEGER PRIMARY KEY CHECK(id=1), json TEXT NOT NULL, refreshed_at INTEGER NOT NULL)");
35098
- state.db.exec("CREATE TABLE IF NOT EXISTS cli_analytics_queue (id TEXT PRIMARY KEY, account_id TEXT NOT NULL, event TEXT NOT NULL, properties_json TEXT NOT NULL, occurred_at TEXT NOT NULL, attempts INTEGER NOT NULL DEFAULT 0, next_attempt_at INTEGER NOT NULL DEFAULT 0)");
35099
- state.db.exec("CREATE TABLE IF NOT EXISTS cli_onboarding_attempt (id INTEGER PRIMARY KEY CHECK(id=1), attempt_id TEXT NOT NULL, account_id TEXT NOT NULL)");
35148
+ LINE_SEPARATOR_REGEX = /\r?\n/;
35149
+ defaultOptions = {
35150
+ timeout: void 0,
35151
+ persist: false
35152
+ };
35153
+ defaultSyncOptions = { timeout: void 0 };
35154
+ defaultNodeOptions = { windowsHide: true };
35155
+ ExecProcess = class {
35156
+ _process;
35157
+ _aborted = false;
35158
+ _options;
35159
+ _command;
35160
+ _args;
35161
+ _resolveClose;
35162
+ _processClosed;
35163
+ _thrownError;
35164
+ get process() {
35165
+ return this._process;
35100
35166
  }
35101
- saveOnboardingAttempt(attemptId) {
35102
- const installation = this.state.installation();
35103
- if (!installation) return;
35104
- this.state.db.prepare("INSERT INTO cli_onboarding_attempt (id,attempt_id,account_id) VALUES (1,?,?) ON CONFLICT(id) DO UPDATE SET attempt_id=excluded.attempt_id,account_id=excluded.account_id").run(attemptId, installation.accountId);
35167
+ get pid() {
35168
+ return this._process?.pid;
35105
35169
  }
35106
- onboardingAttempt() {
35107
- const installation = this.state.installation();
35108
- if (!installation) return null;
35109
- const row = this.state.db.prepare("SELECT attempt_id FROM cli_onboarding_attempt WHERE id=1 AND account_id=?").get(installation.accountId);
35110
- return row?.attempt_id ?? null;
35170
+ get exitCode() {
35171
+ if (this._process && this._process.exitCode !== null) return this._process.exitCode;
35111
35172
  }
35112
- identity() {
35113
- const row = this.state.db.prepare("SELECT json FROM cli_analytics_identity WHERE id=1").get();
35114
- if (!row) return null;
35115
- try {
35116
- const identity2 = JSON.parse(row.json);
35117
- const installation = this.state.installation();
35118
- return installation && identity2.expiresAt > Date.now() && identity2.accountId === installation.accountId && identity2.workspaceId === installation.workspaceId && identity2.installationId === installation.id ? identity2 : null;
35119
- } catch {
35120
- return null;
35121
- }
35173
+ get signalCode() {
35174
+ return this._process?.signalCode ?? null;
35122
35175
  }
35123
- updateIdentity(me2, now = Date.now()) {
35124
- const previous = this.identity();
35125
- const next = { accountId: me2.account.id, workspaceId: me2.workspace.id, installationId: me2.installation.id, ...me2.analytics, enabled: me2.analytics.enabled && me2.analytics.environment !== "disabled" && me2.analytics.host === EU_HOST && /^phc_[A-Za-z0-9]+$/u.test(me2.analytics.projectKey), expiresAt: now + 24 * 60 * 6e4 };
35126
- if (!next.enabled || previous && previous.accountId !== next.accountId) this.state.db.exec("DELETE FROM cli_analytics_queue");
35127
- this.state.db.prepare("INSERT INTO cli_analytics_identity (id,json,refreshed_at) VALUES (1,?,?) ON CONFLICT(id) DO UPDATE SET json=excluded.json,refreshed_at=excluded.refreshed_at").run(JSON.stringify(next), now);
35128
- this.state.db.prepare("DELETE FROM cli_analytics_queue WHERE account_id<>? OR occurred_at<?").run(next.accountId, new Date(now - 7 * 864e5).toISOString());
35176
+ constructor(command6, args, options2) {
35177
+ this._options = {
35178
+ ...defaultOptions,
35179
+ ...options2
35180
+ };
35181
+ this._command = command6;
35182
+ this._args = args ?? [];
35183
+ this._processClosed = new Promise((resolve6) => {
35184
+ this._resolveClose = resolve6;
35185
+ });
35129
35186
  }
35130
- async refresh(site, now = Date.now()) {
35131
- const installation = this.state.installation();
35132
- if (!installation) return "unavailable";
35133
- const last = this.state.db.prepare("SELECT refreshed_at FROM cli_analytics_identity WHERE id=1").get();
35134
- if (last && now - last.refreshed_at < 5 * 6e4 && this.identity()) return "ok";
35135
- try {
35136
- this.updateIdentity(await createCliApi(site, this.fetcher, installation.token).call("me"), now);
35137
- return "ok";
35138
- } catch (error62) {
35139
- if (error62 instanceof ApiClientError && error62.response?.error?.code === "installation-revoked") {
35140
- this.state.db.exec("DELETE FROM cli_analytics_queue; DELETE FROM cli_analytics_identity");
35141
- return "revoked";
35142
- }
35143
- return "unavailable";
35144
- }
35187
+ kill(signal) {
35188
+ return this._process?.kill(signal) === true;
35189
+ }
35190
+ get aborted() {
35191
+ return this._aborted;
35192
+ }
35193
+ get killed() {
35194
+ return this._process?.killed === true;
35195
+ }
35196
+ pipe(command6, args, options2) {
35197
+ return exec(command6, args, {
35198
+ ...options2,
35199
+ stdin: this
35200
+ });
35201
+ }
35202
+ async *[Symbol.asyncIterator]() {
35203
+ const proc = this._process;
35204
+ if (!proc) return;
35205
+ const streams = [];
35206
+ if (this._streamErr) streams.push(this._streamErr);
35207
+ if (this._streamOut) streams.push(this._streamOut);
35208
+ const streamCombined = combineStreams(streams);
35209
+ const rl = readline.createInterface({ input: streamCombined });
35210
+ for await (const chunk of rl) yield chunk.toString();
35211
+ await this._processClosed;
35212
+ proc.removeAllListeners();
35213
+ if (this._thrownError) throw this._thrownError;
35214
+ if (this._options?.throwOnError && (this.exitCode !== 0 && this.exitCode !== void 0 || this.signalCode !== null)) throw new NonZeroExitError(this, void 0, this._command, this._args);
35215
+ }
35216
+ async _waitForOutput() {
35217
+ const proc = this._process;
35218
+ if (!proc) throw new Error("No process was started");
35219
+ const [stdout, stderr] = await Promise.all([this._streamOut ? readStream(this._streamOut) : "", this._streamErr ? readStream(this._streamErr) : ""]);
35220
+ await this._processClosed;
35221
+ const { stdin } = this._options;
35222
+ if (stdin && typeof stdin !== "string") await stdin;
35223
+ proc.removeAllListeners();
35224
+ if (this._thrownError) throw this._thrownError;
35225
+ const result3 = {
35226
+ stderr,
35227
+ stdout,
35228
+ exitCode: this.exitCode
35229
+ };
35230
+ if (this._options.throwOnError && (this.exitCode !== 0 && this.exitCode !== void 0 || this.signalCode !== null)) throw new NonZeroExitError(this, result3, this._command, this._args);
35231
+ return result3;
35145
35232
  }
35146
- record(event, values = {}) {
35147
- if (!EVENT.has(event)) return;
35148
- const installation = this.state.installation();
35149
- if (!installation) return;
35150
- const identity2 = this.identity();
35151
- if (identity2 && !identity2.enabled) return;
35152
- this.state.db.prepare("INSERT INTO cli_analytics_queue (id,account_id,event,properties_json,occurred_at) VALUES (?,?,?,?,?)").run(randomUUID10(), installation.accountId, event, JSON.stringify(scrubEventProperties(event, values)), (/* @__PURE__ */ new Date()).toISOString());
35153
- this.state.db.exec("DELETE FROM cli_analytics_queue WHERE id IN (SELECT id FROM cli_analytics_queue ORDER BY occurred_at DESC LIMIT -1 OFFSET 1000)");
35233
+ then(onfulfilled, onrejected) {
35234
+ return this._waitForOutput().then(onfulfilled, onrejected);
35154
35235
  }
35155
- async flush() {
35156
- const identity2 = this.identity();
35157
- if (!identity2?.enabled) return;
35158
- const rows = this.state.db.prepare("SELECT id,account_id,event,properties_json,occurred_at,attempts FROM cli_analytics_queue WHERE account_id=? AND next_attempt_at<=? ORDER BY occurred_at,id LIMIT 50").all(identity2.accountId, Date.now());
35159
- if (!rows.length) return;
35160
- const batch = rows.map((row) => ({ uuid: row.id, event: row.event, distinct_id: identity2.accountId, timestamp: row.occurred_at, properties: { ...JSON.parse(row.properties_json), schema_version: "apilift.analytics/v1", environment: identity2.environment, component: "cli", release: this.release, account_id: identity2.accountId, workspace_id: identity2.workspaceId, installation_id: identity2.installationId, is_internal: identity2.internal, $geoip_disable: true } }));
35161
- let sent = false;
35162
- try {
35163
- sent = (await this.fetcher(`${identity2.host}/batch/`, { method: "POST", redirect: "error", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ api_key: identity2.projectKey, batch }), signal: AbortSignal.timeout(2e3) })).ok;
35164
- } catch {
35165
- }
35166
- for (const row of rows) {
35167
- if (sent) this.state.db.prepare("DELETE FROM cli_analytics_queue WHERE id=?").run(row.id);
35168
- else this.state.db.prepare("UPDATE cli_analytics_queue SET attempts=?,next_attempt_at=? WHERE id=?").run(Math.min(20, row.attempts + 1), Date.now() + Math.min(3600, 2 ** (row.attempts + 1)) * 1e3, row.id);
35236
+ _streamOut;
35237
+ _streamErr;
35238
+ spawn() {
35239
+ const options2 = this._options;
35240
+ const nodeOptions = {
35241
+ ...defaultNodeOptions,
35242
+ ...options2.nodeOptions
35243
+ };
35244
+ const cwd$1 = nodeOptions?.cwd ?? cwd();
35245
+ const signals = [];
35246
+ this._resetState();
35247
+ if (options2.timeout !== void 0) signals.push(AbortSignal.timeout(options2.timeout));
35248
+ if (options2.signal !== void 0) signals.push(options2.signal);
35249
+ if (options2.persist === true) nodeOptions.detached = true;
35250
+ if (signals.length > 0) nodeOptions.signal = combineSignals(signals);
35251
+ nodeOptions.env = computeEnv(cwd$1, nodeOptions.env, options2.nodePath);
35252
+ const crossResult = normalizeSpawnCommand(this._command, this._args, nodeOptions);
35253
+ const handle2 = spawn2(crossResult.command, crossResult.args, crossResult.options);
35254
+ if (handle2.stderr) this._streamErr = handle2.stderr;
35255
+ if (handle2.stdout) this._streamOut = handle2.stdout;
35256
+ this._process = handle2;
35257
+ handle2.once("error", this._onError);
35258
+ handle2.once("close", this._onClose);
35259
+ if (handle2.stdin) {
35260
+ const { stdin } = options2;
35261
+ if (typeof stdin === "string") handle2.stdin.end(stdin);
35262
+ else stdin?.process?.stdout?.pipe(handle2.stdin);
35169
35263
  }
35170
35264
  }
35265
+ _resetState() {
35266
+ this._aborted = false;
35267
+ this._processClosed = new Promise((resolve6) => {
35268
+ this._resolveClose = resolve6;
35269
+ });
35270
+ this._thrownError = void 0;
35271
+ }
35272
+ _onError = (err) => {
35273
+ if (err.name === "AbortError" && (!(err.cause instanceof Error) || err.cause.name !== "TimeoutError")) {
35274
+ this._aborted = true;
35275
+ return;
35276
+ }
35277
+ this._thrownError = err;
35278
+ };
35279
+ _onClose = () => {
35280
+ if (this._resolveClose) this._resolveClose();
35281
+ };
35282
+ };
35283
+ x = (command6, args, userOptions) => {
35284
+ const proc = new ExecProcess(command6, args, userOptions);
35285
+ proc.spawn();
35286
+ return proc;
35171
35287
  };
35288
+ exec = x;
35172
35289
  }
35173
35290
  });
35174
35291
 
35175
- // src/usage/process.ts
35176
- import { spawn as spawn2 } from "node:child_process";
35177
- import { existsSync as existsSync5 } from "node:fs";
35292
+ // src/plugin.ts
35293
+ import { createHash as createHash5 } from "node:crypto";
35294
+ import { cpSync, existsSync as existsSync4, mkdirSync as mkdirSync9, readFileSync as readFileSync9, readdirSync, renameSync as renameSync3, rmSync as rmSync4, writeFileSync as writeFileSync8 } from "node:fs";
35295
+ import { homedir as homedir2 } from "node:os";
35296
+ import { dirname as dirname7, join as join13, resolve as resolve3 } from "node:path";
35178
35297
  import { fileURLToPath as fileURLToPath2 } from "node:url";
35179
- function startCollector(env) {
35180
- if (process.env.VITEST || env.VITEST || env.APILIFT_COLLECTOR_DISABLED === "1") return;
35181
- const entry2 = fileURLToPath2(new URL("./apilift-collector.js", import.meta.url));
35182
- if (!existsSync5(entry2)) return;
35298
+ function missingSkill(root) {
35299
+ return skillNames.find((name) => !existsSync4(join13(root, "skills", name, "SKILL.md")));
35300
+ }
35301
+ function completeCopy(source, target) {
35302
+ return files(source).every((path2) => existsSync4(join13(target, path2)));
35303
+ }
35304
+ function files(root, relative = "") {
35305
+ return readdirSync(join13(root, relative), { withFileTypes: true }).flatMap((entry2) => {
35306
+ const path2 = join13(relative, entry2.name);
35307
+ return entry2.isDirectory() ? files(root, path2) : [path2];
35308
+ }).sort();
35309
+ }
35310
+ function digest(root) {
35311
+ const hash4 = createHash5("sha256");
35312
+ for (const path2 of files(root)) hash4.update(path2).update("\0").update(readFileSync9(join13(root, path2))).update("\0");
35313
+ return hash4.digest("hex");
35314
+ }
35315
+ function writeJson(path2, value) {
35316
+ mkdirSync9(dirname7(path2), { recursive: true });
35317
+ writeFileSync8(path2, `${JSON.stringify(value, null, 2)}
35318
+ `, { mode: 384 });
35319
+ }
35320
+ function readJson(path2) {
35183
35321
  try {
35184
- spawn2(process.execPath, [entry2], { detached: true, stdio: "ignore", env }).unref();
35322
+ return JSON.parse(readFileSync9(path2, "utf8"));
35185
35323
  } catch {
35324
+ return null;
35186
35325
  }
35187
35326
  }
35188
- var init_process = __esm({
35189
- "src/usage/process.ts"() {
35190
- "use strict";
35191
- }
35192
- });
35193
-
35194
- // ../../node_modules/.pnpm/tinyexec@1.3.1/node_modules/tinyexec/dist/main.mjs
35195
- import { spawn as spawn3, spawnSync } from "node:child_process";
35196
- import { cwd } from "node:process";
35197
- import { basename, delimiter, dirname as dirname7, normalize, resolve as resolve3 } from "node:path";
35198
- import { fileURLToPath as fileURLToPath3 } from "node:url";
35199
- import { pipeline } from "node:stream/promises";
35200
- import { PassThrough } from "node:stream";
35201
- import readline from "node:readline";
35202
- import { closeSync, openSync, readSync, statSync } from "node:fs";
35203
- function getPathFromEnv(env) {
35204
- for (const key in env) {
35205
- if (!Object.prototype.hasOwnProperty.call(env, key) || !isPathLikePattern.test(key)) continue;
35206
- const value = env[key];
35207
- if (!value) return defaultEnvPathInfo;
35208
- return {
35209
- key,
35210
- value
35211
- };
35327
+ function command5(binary, args) {
35328
+ const { stdout: output2 } = xSync(binary, args, { timeout: 1e4, throwOnError: true, nodeOptions: { windowsHide: true, stdio: ["ignore", "pipe", "pipe"] } });
35329
+ try {
35330
+ return JSON.parse(output2);
35331
+ } catch {
35332
+ return output2;
35212
35333
  }
35213
- return defaultEnvPathInfo;
35214
35334
  }
35215
- function addNodeBinToPath(cwd2, path2) {
35216
- const parts = path2.value.split(delimiter);
35217
- const nodeBinPaths = [];
35218
- let currentPath = typeof cwd2 === "string" ? cwd2 : fileURLToPath3(cwd2, { windows: false });
35219
- let lastPath;
35220
- do {
35221
- nodeBinPaths.push(resolve3(currentPath, "node_modules", ".bin"));
35222
- lastPath = currentPath;
35223
- currentPath = dirname7(currentPath);
35224
- } while (currentPath !== lastPath);
35225
- nodeBinPaths.push(dirname7(process.execPath));
35226
- const newPath = nodeBinPaths.concat(parts).join(delimiter);
35227
- return {
35228
- key: path2.key,
35229
- value: newPath
35230
- };
35335
+ function active(agent2, expected) {
35336
+ const args = agent2 === "codex" ? ["plugin", "list", "--marketplace", "personal", "--json"] : ["plugin", "list", "--json"];
35337
+ try {
35338
+ const listed = command5(agent2, args);
35339
+ const entries = agent2 === "codex" ? listed?.installed : listed;
35340
+ if (!Array.isArray(entries) || entries.some((entry2) => typeof entry2 !== "object" || entry2 === null || typeof (agent2 === "codex" ? entry2.pluginId : entry2.id) !== "string")) return { ready: false, known: false, detail: `${agent2} plugin status could not be checked: plugin list returned an unexpected response` };
35341
+ const plugin = entries.find((entry2) => (agent2 === "codex" ? entry2.pluginId : entry2.id) === "apilift@personal");
35342
+ if (!plugin) return { ready: false, known: true, detail: "Apilift is not installed in this agent" };
35343
+ if (typeof plugin.enabled !== "boolean" || typeof plugin.version !== "string") return { ready: false, known: false, detail: `${agent2} plugin status could not be checked: Apilift status is incomplete` };
35344
+ if (!plugin.enabled) return { ready: false, known: true, detail: "Apilift is disabled in this agent" };
35345
+ if (plugin.version !== expected) return { ready: false, known: true, detail: "The active Apilift plugin does not match the packaged version" };
35346
+ return { ready: true, known: true, detail: "Apilift setup, use and discovery skills are active" };
35347
+ } catch (error62) {
35348
+ const code = typeof error62 === "object" && error62 && "code" in error62 ? error62.code : void 0;
35349
+ const reason = code === "ETIMEDOUT" ? "plugin list timed out after 10 seconds" : code === "ENOENT" ? `${agent2} executable was not found` : "plugin list failed";
35350
+ return { ready: false, known: false, detail: `${agent2} plugin status could not be checked: ${reason}` };
35351
+ }
35231
35352
  }
35232
- function computeEnv(cwd2, env, nodePath = true) {
35233
- const envWithDefault = {
35234
- ...process.env,
35235
- ...env
35236
- };
35237
- if (!nodePath) return envWithDefault;
35238
- const envPathInfo = addNodeBinToPath(cwd2, getPathFromEnv(envWithDefault));
35239
- envWithDefault[envPathInfo.key] = envPathInfo.value;
35240
- return envWithDefault;
35353
+ function inspectAgentPlugin(agent2, env, sourceOverride) {
35354
+ if (env.APILIFT_DEV_PLUGIN_ROOT) return { ready: false, version: "development", detail: "Development plugin activation has not been verified" };
35355
+ const source = resolve3(sourceOverride || join13(dirname7(fileURLToPath2(import.meta.url)), "plugin"));
35356
+ const missing = missingSkill(source);
35357
+ if (missing) return { ready: false, version: "unknown", detail: `CLI package is missing its ${missing} skill` };
35358
+ const version2 = readJson(join13(source, `.${agent2}-plugin`, "plugin.json"))?.version;
35359
+ if (typeof version2 !== "string" || !/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/.test(version2)) throw new Error("Plugin package version is invalid");
35360
+ const bundleDigest = digest(source);
35361
+ const expected = agent2 === "codex" ? `${version2}+codex.${bundleDigest.slice(0, 12)}` : version2;
35362
+ const home = resolve3(env.APILIFT_AGENT_HOME || homedir2());
35363
+ const plugin = agent2 === "codex" ? join13(home, "plugins", "apilift") : join13(home, ".agents", "plugins", "plugins", "apilift");
35364
+ const marker = readJson(join13(plugin, markerName));
35365
+ const staged = marker?.package === "apilift" && marker.bundleDigest === bundleDigest && marker.version === version2 && completeCopy(source, plugin);
35366
+ if (!staged) return { ready: false, version: expected, detail: "Apilift plugin files are missing or stale" };
35367
+ if (env.APILIFT_AGENT_HOME || env.APILIFT_AGENT_NO_ACTIVATE === "1") return { ready: false, version: expected, detail: "Apilift skills are staged; activation is unverified" };
35368
+ const activation = active(agent2, expected);
35369
+ return { ready: activation.ready, version: expected, detail: activation.detail, checkFailed: !activation.known };
35241
35370
  }
35242
- function normalizeSpawnCommand(command6, args = [], options2 = {}) {
35243
- if (options2.shell === true || !isWindows) return {
35244
- command: command6,
35245
- args,
35246
- options: options2
35247
- };
35248
- let file2 = resolveCommand(command6, options2);
35249
- let shebang = null;
35250
- if (file2 !== null) {
35251
- const size = 150;
35252
- const buffer = Buffer.alloc(size);
35253
- let fd = null;
35254
- try {
35255
- fd = openSync(file2, "r");
35256
- readSync(fd, buffer, 0, size, 0);
35257
- } catch {
35258
- } finally {
35259
- if (fd !== null) closeSync(fd);
35260
- }
35261
- const match = buffer.toString().match(shebangRegExp);
35262
- if (match !== null) {
35263
- const line = match[1].trim();
35264
- const separatorIndex = line.indexOf(" ");
35265
- const path2 = separatorIndex !== -1 ? line.slice(0, separatorIndex) : line;
35266
- const argument = separatorIndex !== -1 ? line.slice(separatorIndex + 1) : "";
35267
- const binary = basename(path2);
35268
- shebang = binary === "env" ? argument || null : binary;
35269
- }
35270
- }
35271
- if (shebang !== null && file2 !== null) {
35272
- args = [file2, ...args];
35273
- command6 = shebang;
35274
- file2 = resolveCommand(command6, options2);
35371
+ function ensureAgentPlugin(agent2, env, sourceOverride) {
35372
+ if (env.APILIFT_DEV_PLUGIN_ROOT) {
35373
+ const source2 = resolve3(env.APILIFT_DEV_PLUGIN_ROOT);
35374
+ const missing2 = missingSkill(source2);
35375
+ if (missing2) throw new Error(`Development plugin ${missing2} skill is missing`);
35376
+ const bundleDigest2 = digest(source2);
35377
+ const root = resolve3(env.APILIFT_HOME || join13(homedir2(), ".local", "share", "apilift"));
35378
+ writeJson(join13(root, "agent-plugins", `${agent2}.json`), { root: source2, digest: bundleDigest2 });
35379
+ return { ready: false, version: `dev+${bundleDigest2.slice(0, 12)}`, detail: "Development plugin source recorded; activation was not verified" };
35275
35380
  }
35276
- if (file2 === null || !isWindowsExecutableRegExp.test(file2)) {
35277
- const needsDoubleEscapeMetaChars = file2 !== null && isNodeModulesCmdRegExp.test(file2);
35278
- command6 = normalize(command6);
35279
- command6 = command6.replace(metaCharsRegExp, "^$1");
35280
- args = args.map((arg) => {
35281
- arg = arg.replace(/(?=(\\+?)?)\1"/g, '$1$1\\"');
35282
- arg = arg.replace(/(?=(\\+?)?)\1$/, "$1$1");
35283
- arg = `"${arg}"`;
35284
- arg = arg.replace(metaCharsRegExp, "^$1");
35285
- if (needsDoubleEscapeMetaChars) arg = arg.replace(metaCharsRegExp, "^$1");
35286
- return arg;
35287
- });
35288
- args = [
35289
- "/d",
35290
- "/s",
35291
- "/c",
35292
- `"${[command6, ...args].join(" ")}"`
35293
- ];
35294
- command6 = options2.env?.comspec ?? "cmd.exe";
35295
- options2 = {
35296
- ...options2,
35297
- windowsVerbatimArguments: true
35298
- };
35381
+ const source = resolve3(sourceOverride || join13(dirname7(fileURLToPath2(import.meta.url)), "plugin"));
35382
+ const missing = missingSkill(source);
35383
+ if (missing) throw new Error(`CLI package is missing its ${missing} skill`);
35384
+ const version2 = readJson(join13(source, `.${agent2}-plugin`, "plugin.json"))?.version;
35385
+ if (typeof version2 !== "string" || !/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/.test(version2)) throw new Error("Plugin package version is invalid");
35386
+ const bundleDigest = digest(source);
35387
+ const home = resolve3(env.APILIFT_AGENT_HOME || homedir2());
35388
+ const marketplace = join13(home, ".agents", "plugins");
35389
+ const plugin = agent2 === "codex" ? join13(home, "plugins", "apilift") : join13(marketplace, "plugins", "apilift");
35390
+ const marker = readJson(join13(plugin, markerName));
35391
+ if (existsSync4(plugin) && marker?.package !== "apilift") throw new Error(`Refusing to replace unmanaged plugin ${plugin}`);
35392
+ const expected = agent2 === "codex" ? `${version2}+codex.${bundleDigest.slice(0, 12)}` : version2;
35393
+ const current = marker?.bundleDigest === bundleDigest && marker.version === version2 && completeCopy(source, plugin);
35394
+ if (!current) {
35395
+ const staging = `${plugin}.stage-${process.pid}`;
35396
+ rmSync4(staging, { recursive: true, force: true });
35397
+ mkdirSync9(dirname7(staging), { recursive: true });
35398
+ cpSync(source, staging, { recursive: true });
35399
+ const manifestPath = join13(staging, `.${agent2}-plugin`, "plugin.json");
35400
+ const manifest = readJson(manifestPath);
35401
+ if (!manifest) throw new Error("Plugin manifest is invalid");
35402
+ manifest.version = expected;
35403
+ writeJson(manifestPath, manifest);
35404
+ writeJson(join13(staging, markerName), { schema: "apilift.plugin-install/v1", package: "apilift", version: version2, bundleDigest });
35405
+ const backup = `${plugin}.backup-${process.pid}`;
35406
+ if (existsSync4(plugin)) renameSync3(plugin, backup);
35407
+ try {
35408
+ renameSync3(staging, plugin);
35409
+ } catch (error62) {
35410
+ if (existsSync4(backup)) renameSync3(backup, plugin);
35411
+ throw error62;
35412
+ }
35413
+ rmSync4(backup, { recursive: true, force: true });
35299
35414
  }
35300
- return {
35301
- command: command6,
35302
- args,
35303
- options: options2
35304
- };
35305
- }
35306
- function resolveCommand(command6, options2) {
35307
- const cwd$3 = (options2.cwd ?? cwd()).toString();
35308
- const env = options2.env ?? process.env;
35309
- const PATH = getPathFromEnv(env).value;
35310
- const pathEnv = command6.includes("/") || command6.includes("\\") ? [""] : [cwd$3, ...PATH.split(delimiter)];
35311
- let pathExt = env.PATHEXT ? env.PATHEXT.split(delimiter) : defaultPathExt;
35312
- if (command6.includes(".") && pathExt[0] !== "") pathExt = ["", ...pathExt];
35313
- for (const extensions of [pathExt, noPathExt]) for (const path2 of pathEnv) {
35314
- const unquoted = path2.startsWith('"') && path2.endsWith('"') && path2.length > 1 ? path2.slice(1, -1) : path2;
35315
- const dest = resolve3(cwd$3, unquoted, command6);
35316
- for (const ext of extensions) {
35317
- const destWithExt = dest + ext;
35415
+ const marketplacePath = join13(marketplace, "marketplace.json");
35416
+ const document = readJson(marketplacePath) ?? { name: "personal", interface: { displayName: "Personal" }, plugins: [] };
35417
+ if (document.name !== "personal" || !Array.isArray(document.plugins)) throw new Error("Unsupported personal plugin marketplace");
35418
+ document.plugins = [...document.plugins.filter((item) => typeof item === "object" && item !== null && item.name !== "apilift"), { name: "apilift", source: { source: "local", path: "./plugins/apilift" }, policy: { installation: "AVAILABLE", authentication: "ON_INSTALL" }, category: "Developer Tools" }];
35419
+ writeJson(marketplacePath, document);
35420
+ writeJson(join13(marketplace, ".claude-plugin", "marketplace.json"), { "$schema": "https://anthropic.com/claude-code/marketplace.schema.json", name: "personal", description: "Personal agent plugins", owner: { name: "Local" }, plugins: [{ name: "apilift", description: "Use logged-in apps and discover missing actions with Apilift", source: "./plugins/apilift", category: "development" }] });
35421
+ if (env.APILIFT_AGENT_HOME || env.APILIFT_AGENT_NO_ACTIVATE === "1") return { ready: false, version: expected, detail: `Plugin staged at ${plugin}; activation is disabled` };
35422
+ const initial = active(agent2, expected);
35423
+ if (!initial.known) return { ready: false, version: expected, detail: initial.detail, checkFailed: true };
35424
+ if (initial.ready) return { ready: true, version: expected, detail: initial.detail };
35425
+ {
35426
+ if (agent2 === "codex") command5("codex", ["plugin", "add", "apilift@personal"]);
35427
+ else {
35428
+ let marketplaces;
35318
35429
  try {
35319
- if (statSync(destWithExt).isFile()) return destWithExt;
35430
+ marketplaces = command5("claude", ["plugin", "marketplace", "list", "--json"]);
35320
35431
  } catch {
35432
+ marketplaces = [];
35321
35433
  }
35434
+ if (!Array.isArray(marketplaces) || !marketplaces.some((item) => typeof item === "object" && item !== null && item.name === "personal")) command5("claude", ["plugin", "marketplace", "add", marketplace, "--scope", "user"]);
35435
+ const listed = command5("claude", ["plugin", "list", "--json"]);
35436
+ const installed = Array.isArray(listed) && listed.some((item) => typeof item === "object" && item !== null && item.id === "apilift@personal");
35437
+ command5("claude", ["plugin", installed ? "update" : "install", "apilift@personal", "--scope", "user"]);
35438
+ command5("claude", ["plugin", "enable", "apilift@personal", "--scope", "user"]);
35322
35439
  }
35323
35440
  }
35324
- return null;
35441
+ const activation = active(agent2, expected);
35442
+ return { ready: activation.ready, version: expected, detail: activation.detail, checkFailed: !activation.known };
35325
35443
  }
35326
- function combineSignals(signals) {
35327
- const controller = new AbortController();
35328
- for (const signal of signals) {
35329
- if (signal.aborted) {
35330
- controller.abort();
35331
- return signal;
35332
- }
35333
- const onAbort = () => {
35334
- controller.abort(signal.reason);
35335
- };
35336
- signal.addEventListener("abort", onAbort, { signal: controller.signal });
35444
+ var markerName, skillNames;
35445
+ var init_plugin = __esm({
35446
+ "src/plugin.ts"() {
35447
+ "use strict";
35448
+ init_main2();
35449
+ markerName = ".apilift-managed.json";
35450
+ skillNames = ["setup", "use", "discover"];
35337
35451
  }
35338
- return controller.signal;
35339
- }
35340
- async function readStream(stream) {
35341
- let output2 = "";
35452
+ });
35453
+
35454
+ // ../../packages/core/dist/analytics/index.js
35455
+ function normalizeRoute(route) {
35342
35456
  try {
35343
- for await (const chunk of stream) output2 += chunk.toString();
35457
+ const parsed = new URL(route, "https://apilift.dev");
35458
+ return parsed.pathname.replace(/^\/dashboard\/apps\/[^/]+/, "/dashboard/apps/:app").replace(uuid5, ":id").replace(longId, ":id");
35344
35459
  } catch {
35460
+ return "/invalid";
35345
35461
  }
35346
- return output2;
35347
35462
  }
35348
- var isPathLikePattern, defaultEnvPathInfo, combineStreams, metaCharsRegExp, shebangRegExp, isWindowsExecutableRegExp, isNodeModulesCmdRegExp, isWindows, defaultPathExt, noPathExt, NonZeroExitError, defaultOptions, defaultNodeOptions, ExecProcess, x, exec;
35349
- var init_main2 = __esm({
35350
- "../../node_modules/.pnpm/tinyexec@1.3.1/node_modules/tinyexec/dist/main.mjs"() {
35351
- isPathLikePattern = /^path$/i;
35352
- defaultEnvPathInfo = {
35353
- key: "PATH",
35354
- value: ""
35355
- };
35356
- combineStreams = (streams) => {
35357
- let streamCount = streams.length;
35358
- const combined = new PassThrough();
35359
- const maybeEmitEnd = () => {
35360
- if (--streamCount === 0) combined.end();
35361
- };
35362
- for (const stream of streams) pipeline(stream, combined, { end: false }).then(maybeEmitEnd).catch(maybeEmitEnd);
35363
- return combined;
35364
- };
35365
- metaCharsRegExp = /([()\][%!^"`<>&|;, *?])/g;
35366
- shebangRegExp = /^#!\s*(.+)/;
35367
- isWindowsExecutableRegExp = /\.(?:com|exe)$/i;
35368
- isNodeModulesCmdRegExp = /node_modules[\\/]\.bin[\\/][^\\/]+\.cmd$/i;
35369
- isWindows = process.platform === "win32";
35370
- defaultPathExt = [
35371
- ".EXE",
35372
- ".CMD",
35373
- ".BAT",
35374
- ".COM"
35375
- ];
35376
- noPathExt = [""];
35377
- NonZeroExitError = class extends Error {
35378
- result;
35379
- output;
35380
- exitCode;
35381
- get signalCode() {
35382
- return this.result.signalCode;
35383
- }
35384
- constructor(result3, output2, command6, args) {
35385
- let target = "The process";
35386
- if (command6) target = `The command \`${args?.length ? `${command6} ${args.map((a) => /[ "'`()]/.test(a) ? JSON.stringify(a) : a).join(" ")}` : command6}\``;
35387
- const exitCode = result3.exitCode ?? 1;
35388
- super(result3.signalCode !== null ? `${target} was killed by the signal ${result3.signalCode}` : `${target} exited with a non-zero status (${exitCode})`);
35389
- this.result = result3;
35390
- this.output = output2;
35391
- this.exitCode = exitCode;
35392
- Object.defineProperty(this, "result", {
35393
- enumerable: false,
35394
- writable: false,
35395
- configurable: false
35396
- });
35397
- }
35398
- };
35399
- defaultOptions = {
35400
- timeout: void 0,
35401
- persist: false
35402
- };
35403
- defaultNodeOptions = { windowsHide: true };
35404
- ExecProcess = class {
35405
- _process;
35406
- _aborted = false;
35407
- _options;
35408
- _command;
35409
- _args;
35410
- _resolveClose;
35411
- _processClosed;
35412
- _thrownError;
35413
- get process() {
35414
- return this._process;
35415
- }
35416
- get pid() {
35417
- return this._process?.pid;
35418
- }
35419
- get exitCode() {
35420
- if (this._process && this._process.exitCode !== null) return this._process.exitCode;
35421
- }
35422
- get signalCode() {
35423
- return this._process?.signalCode ?? null;
35424
- }
35425
- constructor(command6, args, options2) {
35426
- this._options = {
35427
- ...defaultOptions,
35428
- ...options2
35429
- };
35430
- this._command = command6;
35431
- this._args = args ?? [];
35432
- this._processClosed = new Promise((resolve6) => {
35433
- this._resolveClose = resolve6;
35434
- });
35435
- }
35436
- kill(signal) {
35437
- return this._process?.kill(signal) === true;
35438
- }
35439
- get aborted() {
35440
- return this._aborted;
35441
- }
35442
- get killed() {
35443
- return this._process?.killed === true;
35463
+ function scrubEventProperties(event, input2) {
35464
+ const allowed = eventProperties[event];
35465
+ const result3 = {};
35466
+ for (const [key, value] of Object.entries(input2)) {
35467
+ if (!allowed.includes(key))
35468
+ continue;
35469
+ const normalized = key === "route" && typeof value === "string" ? normalizeRoute(value) : value;
35470
+ if (typeof normalized === "boolean" || typeof normalized === "number" && Number.isFinite(normalized) || typeof normalized === "string" && safeString.test(normalized))
35471
+ result3[key] = normalized;
35472
+ }
35473
+ return result3;
35474
+ }
35475
+ var eventProperties, safeString, uuid5, longId;
35476
+ var init_analytics = __esm({
35477
+ "../../packages/core/dist/analytics/index.js"() {
35478
+ "use strict";
35479
+ eventProperties = {
35480
+ "$pageview": ["route"],
35481
+ navigation_clicked: ["route", "target"],
35482
+ pricing_viewed: ["route"],
35483
+ catalog_viewed: ["route"],
35484
+ app_page_viewed: ["route"],
35485
+ onboarding_entered: ["route"],
35486
+ broken_page_viewed: ["route"],
35487
+ authentication_started: ["source", "stage"],
35488
+ authentication_code_sent: ["source", "stage"],
35489
+ authentication_completed: ["source", "stage"],
35490
+ authentication_failed: ["source", "stage", "code"],
35491
+ onboarding_attempt_started: ["attempt_id", "agent", "stage", "outcome", "source", "target"],
35492
+ onboarding_stage_viewed: ["attempt_id", "agent", "stage"],
35493
+ setup_prompt_copied: ["attempt_id", "agent", "stage"],
35494
+ onboarding_next_action_selected: ["attempt_id", "agent", "target"],
35495
+ onboarding_failed: ["stage", "attempt_id", "code", "outcome", "duration_ms"],
35496
+ checkout_requested: ["plan", "interval", "stage", "source"],
35497
+ checkout_ui_changed: ["plan", "interval", "stage", "source"],
35498
+ billing_portal_requested: ["source"],
35499
+ paid_feature_viewed: ["code", "route"],
35500
+ paid_feature_upgrade_opened: ["code", "route"],
35501
+ account_created: [],
35502
+ account_deleted: [],
35503
+ agent_selected: ["agent", "attempt_id", "status"],
35504
+ device_authorization_changed: ["agent", "attempt_id", "status"],
35505
+ onboarding_stage_completed: ["stage", "active", "agent"],
35506
+ installation_readiness_changed: ["stage", "active", "agent"],
35507
+ app_made_public: ["app_id"],
35508
+ execution_finished: ["app_id", "action_id", "outcome", "duration_ms", "http_status", "agent", "purpose"],
35509
+ activation_changed: ["activated"],
35510
+ execution_classified: ["purpose", "revision"],
35511
+ value_snapshot: ["saved_time_ms", "saved_tokens", "saved_value_microusd", "saved_time_ms_available", "saved_tokens_available", "saved_value_microusd_available"],
35512
+ trial_started: ["plan", "trial_day", "trial_days_remaining", "outcome"],
35513
+ trial_progress: ["plan", "trial_day", "trial_days_remaining", "outcome"],
35514
+ trial_expired: ["plan", "trial_day", "trial_days_remaining", "outcome"],
35515
+ trial_completed: ["plan", "trial_day", "trial_days_remaining", "outcome"],
35516
+ payment_completed: ["plan", "interval", "status", "previous_status", "currency", "amount", "billing_environment", "synthetic"],
35517
+ payment_status_changed: ["plan", "interval", "status", "previous_status", "currency", "amount", "billing_environment", "synthetic"],
35518
+ payment_failed: ["plan", "interval", "status", "previous_status", "currency", "amount", "billing_environment", "synthetic"],
35519
+ subscription_changed: ["plan", "interval", "status", "previous_status", "currency", "amount", "billing_environment", "synthetic"],
35520
+ payment_adjusted: ["plan", "interval", "status", "previous_status", "currency", "amount", "billing_environment", "synthetic"],
35521
+ critical_failure: ["code", "count"],
35522
+ cli_command_finished: ["command", "purpose", "outcome", "duration_ms", "stage", "agent"],
35523
+ setup_stage_changed: ["stage", "agent"],
35524
+ discovery_started: ["purpose", "agent"],
35525
+ discovery_finished: ["purpose", "outcome", "duration_ms", "agent"],
35526
+ client_exception: ["code"],
35527
+ native_host_connected: [],
35528
+ native_host_failure: ["code", "operation", "stage", "origin", "failure_count", "duration_ms"],
35529
+ native_host_recovered: ["operation", "stage", "origin", "failure_count", "duration_ms"],
35530
+ extension_installed: ["stage"],
35531
+ extension_connected: ["stage"],
35532
+ extension_disconnected: ["stage"],
35533
+ extension_failure: ["code"],
35534
+ extension_interaction: ["stage", "outcome"],
35535
+ credential_setup_finished: ["stage", "outcome", "code"],
35536
+ "$exception": ["route", "method", "exception_type", "exception_frames", "exception_handled", "exception_level"]
35537
+ };
35538
+ safeString = /^[a-zA-Z0-9_./:{} -]{1,160}$/;
35539
+ uuid5 = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi;
35540
+ longId = /\b[a-zA-Z0-9_-]{24,}\b/g;
35541
+ }
35542
+ });
35543
+
35544
+ // src/analytics.ts
35545
+ import { randomUUID as randomUUID10 } from "node:crypto";
35546
+ var EVENT, EU_HOST, CliAnalytics;
35547
+ var init_analytics2 = __esm({
35548
+ "src/analytics.ts"() {
35549
+ "use strict";
35550
+ init_analytics();
35551
+ init_api2();
35552
+ init_api3();
35553
+ EVENT = /* @__PURE__ */ new Set(["cli_command_finished", "setup_stage_changed", "onboarding_failed", "discovery_started", "discovery_finished", "client_exception"]);
35554
+ EU_HOST = "https://eu.i.posthog.com";
35555
+ CliAnalytics = class {
35556
+ constructor(state, release = "1.0.0", fetcher = fetch) {
35557
+ this.state = state;
35558
+ this.release = release;
35559
+ this.fetcher = fetcher;
35560
+ state.db.exec("CREATE TABLE IF NOT EXISTS cli_analytics_identity (id INTEGER PRIMARY KEY CHECK(id=1), json TEXT NOT NULL, refreshed_at INTEGER NOT NULL)");
35561
+ state.db.exec("CREATE TABLE IF NOT EXISTS cli_analytics_queue (id TEXT PRIMARY KEY, account_id TEXT NOT NULL, event TEXT NOT NULL, properties_json TEXT NOT NULL, occurred_at TEXT NOT NULL, attempts INTEGER NOT NULL DEFAULT 0, next_attempt_at INTEGER NOT NULL DEFAULT 0)");
35562
+ state.db.exec("CREATE TABLE IF NOT EXISTS cli_onboarding_attempt (id INTEGER PRIMARY KEY CHECK(id=1), attempt_id TEXT NOT NULL, account_id TEXT NOT NULL)");
35444
35563
  }
35445
- pipe(command6, args, options2) {
35446
- return exec(command6, args, {
35447
- ...options2,
35448
- stdin: this
35449
- });
35564
+ saveOnboardingAttempt(attemptId) {
35565
+ const installation = this.state.installation();
35566
+ if (!installation) return;
35567
+ this.state.db.prepare("INSERT INTO cli_onboarding_attempt (id,attempt_id,account_id) VALUES (1,?,?) ON CONFLICT(id) DO UPDATE SET attempt_id=excluded.attempt_id,account_id=excluded.account_id").run(attemptId, installation.accountId);
35450
35568
  }
35451
- async *[Symbol.asyncIterator]() {
35452
- const proc = this._process;
35453
- if (!proc) return;
35454
- const streams = [];
35455
- if (this._streamErr) streams.push(this._streamErr);
35456
- if (this._streamOut) streams.push(this._streamOut);
35457
- const streamCombined = combineStreams(streams);
35458
- const rl = readline.createInterface({ input: streamCombined });
35459
- for await (const chunk of rl) yield chunk.toString();
35460
- await this._processClosed;
35461
- proc.removeAllListeners();
35462
- if (this._thrownError) throw this._thrownError;
35463
- if (this._options?.throwOnError && (this.exitCode !== 0 && this.exitCode !== void 0 || this.signalCode !== null)) throw new NonZeroExitError(this, void 0, this._command, this._args);
35569
+ onboardingAttempt() {
35570
+ const installation = this.state.installation();
35571
+ if (!installation) return null;
35572
+ const row = this.state.db.prepare("SELECT attempt_id FROM cli_onboarding_attempt WHERE id=1 AND account_id=?").get(installation.accountId);
35573
+ return row?.attempt_id ?? null;
35464
35574
  }
35465
- async _waitForOutput() {
35466
- const proc = this._process;
35467
- if (!proc) throw new Error("No process was started");
35468
- const [stdout, stderr] = await Promise.all([this._streamOut ? readStream(this._streamOut) : "", this._streamErr ? readStream(this._streamErr) : ""]);
35469
- await this._processClosed;
35470
- const { stdin } = this._options;
35471
- if (stdin && typeof stdin !== "string") await stdin;
35472
- proc.removeAllListeners();
35473
- if (this._thrownError) throw this._thrownError;
35474
- const result3 = {
35475
- stderr,
35476
- stdout,
35477
- exitCode: this.exitCode
35478
- };
35479
- if (this._options.throwOnError && (this.exitCode !== 0 && this.exitCode !== void 0 || this.signalCode !== null)) throw new NonZeroExitError(this, result3, this._command, this._args);
35480
- return result3;
35575
+ identity() {
35576
+ const row = this.state.db.prepare("SELECT json FROM cli_analytics_identity WHERE id=1").get();
35577
+ if (!row) return null;
35578
+ try {
35579
+ const identity2 = JSON.parse(row.json);
35580
+ const installation = this.state.installation();
35581
+ return installation && identity2.expiresAt > Date.now() && identity2.accountId === installation.accountId && identity2.workspaceId === installation.workspaceId && identity2.installationId === installation.id ? identity2 : null;
35582
+ } catch {
35583
+ return null;
35584
+ }
35481
35585
  }
35482
- then(onfulfilled, onrejected) {
35483
- return this._waitForOutput().then(onfulfilled, onrejected);
35586
+ updateIdentity(me2, now = Date.now()) {
35587
+ const previous = this.identity();
35588
+ const next = { accountId: me2.account.id, workspaceId: me2.workspace.id, installationId: me2.installation.id, ...me2.analytics, enabled: me2.analytics.enabled && me2.analytics.environment !== "disabled" && me2.analytics.host === EU_HOST && /^phc_[A-Za-z0-9]+$/u.test(me2.analytics.projectKey), expiresAt: now + 24 * 60 * 6e4 };
35589
+ if (!next.enabled || previous && previous.accountId !== next.accountId) this.state.db.exec("DELETE FROM cli_analytics_queue");
35590
+ this.state.db.prepare("INSERT INTO cli_analytics_identity (id,json,refreshed_at) VALUES (1,?,?) ON CONFLICT(id) DO UPDATE SET json=excluded.json,refreshed_at=excluded.refreshed_at").run(JSON.stringify(next), now);
35591
+ this.state.db.prepare("DELETE FROM cli_analytics_queue WHERE account_id<>? OR occurred_at<?").run(next.accountId, new Date(now - 7 * 864e5).toISOString());
35484
35592
  }
35485
- _streamOut;
35486
- _streamErr;
35487
- spawn() {
35488
- const options2 = this._options;
35489
- const nodeOptions = {
35490
- ...defaultNodeOptions,
35491
- ...options2.nodeOptions
35492
- };
35493
- const cwd$1 = nodeOptions?.cwd ?? cwd();
35494
- const signals = [];
35495
- this._resetState();
35496
- if (options2.timeout !== void 0) signals.push(AbortSignal.timeout(options2.timeout));
35497
- if (options2.signal !== void 0) signals.push(options2.signal);
35498
- if (options2.persist === true) nodeOptions.detached = true;
35499
- if (signals.length > 0) nodeOptions.signal = combineSignals(signals);
35500
- nodeOptions.env = computeEnv(cwd$1, nodeOptions.env, options2.nodePath);
35501
- const crossResult = normalizeSpawnCommand(this._command, this._args, nodeOptions);
35502
- const handle2 = spawn3(crossResult.command, crossResult.args, crossResult.options);
35503
- if (handle2.stderr) this._streamErr = handle2.stderr;
35504
- if (handle2.stdout) this._streamOut = handle2.stdout;
35505
- this._process = handle2;
35506
- handle2.once("error", this._onError);
35507
- handle2.once("close", this._onClose);
35508
- if (handle2.stdin) {
35509
- const { stdin } = options2;
35510
- if (typeof stdin === "string") handle2.stdin.end(stdin);
35511
- else stdin?.process?.stdout?.pipe(handle2.stdin);
35593
+ async refresh(site, now = Date.now()) {
35594
+ const installation = this.state.installation();
35595
+ if (!installation) return "unavailable";
35596
+ const last = this.state.db.prepare("SELECT refreshed_at FROM cli_analytics_identity WHERE id=1").get();
35597
+ if (last && now - last.refreshed_at < 5 * 6e4 && this.identity()) return "ok";
35598
+ try {
35599
+ this.updateIdentity(await createCliApi(site, this.fetcher, installation.token).call("me"), now);
35600
+ return "ok";
35601
+ } catch (error62) {
35602
+ if (error62 instanceof ApiClientError && error62.response?.error?.code === "installation-revoked") {
35603
+ this.state.db.exec("DELETE FROM cli_analytics_queue; DELETE FROM cli_analytics_identity");
35604
+ return "revoked";
35605
+ }
35606
+ return "unavailable";
35512
35607
  }
35513
35608
  }
35514
- _resetState() {
35515
- this._aborted = false;
35516
- this._processClosed = new Promise((resolve6) => {
35517
- this._resolveClose = resolve6;
35518
- });
35519
- this._thrownError = void 0;
35609
+ record(event, values = {}) {
35610
+ if (!EVENT.has(event)) return;
35611
+ const installation = this.state.installation();
35612
+ if (!installation) return;
35613
+ const identity2 = this.identity();
35614
+ if (identity2 && !identity2.enabled) return;
35615
+ this.state.db.prepare("INSERT INTO cli_analytics_queue (id,account_id,event,properties_json,occurred_at) VALUES (?,?,?,?,?)").run(randomUUID10(), installation.accountId, event, JSON.stringify(scrubEventProperties(event, values)), (/* @__PURE__ */ new Date()).toISOString());
35616
+ this.state.db.exec("DELETE FROM cli_analytics_queue WHERE id IN (SELECT id FROM cli_analytics_queue ORDER BY occurred_at DESC LIMIT -1 OFFSET 1000)");
35520
35617
  }
35521
- _onError = (err) => {
35522
- if (err.name === "AbortError" && (!(err.cause instanceof Error) || err.cause.name !== "TimeoutError")) {
35523
- this._aborted = true;
35524
- return;
35618
+ async flush() {
35619
+ const identity2 = this.identity();
35620
+ if (!identity2?.enabled) return;
35621
+ const rows = this.state.db.prepare("SELECT id,account_id,event,properties_json,occurred_at,attempts FROM cli_analytics_queue WHERE account_id=? AND next_attempt_at<=? ORDER BY occurred_at,id LIMIT 50").all(identity2.accountId, Date.now());
35622
+ if (!rows.length) return;
35623
+ const batch = rows.map((row) => ({ uuid: row.id, event: row.event, distinct_id: identity2.accountId, timestamp: row.occurred_at, properties: { ...JSON.parse(row.properties_json), schema_version: "apilift.analytics/v1", environment: identity2.environment, component: "cli", release: this.release, account_id: identity2.accountId, workspace_id: identity2.workspaceId, installation_id: identity2.installationId, is_internal: identity2.internal, $geoip_disable: true } }));
35624
+ let sent = false;
35625
+ try {
35626
+ sent = (await this.fetcher(`${identity2.host}/batch/`, { method: "POST", redirect: "error", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ api_key: identity2.projectKey, batch }), signal: AbortSignal.timeout(2e3) })).ok;
35627
+ } catch {
35525
35628
  }
35526
- this._thrownError = err;
35527
- };
35528
- _onClose = () => {
35529
- if (this._resolveClose) this._resolveClose();
35530
- };
35531
- };
35532
- x = (command6, args, userOptions) => {
35533
- const proc = new ExecProcess(command6, args, userOptions);
35534
- proc.spawn();
35535
- return proc;
35629
+ for (const row of rows) {
35630
+ if (sent) this.state.db.prepare("DELETE FROM cli_analytics_queue WHERE id=?").run(row.id);
35631
+ else this.state.db.prepare("UPDATE cli_analytics_queue SET attempts=?,next_attempt_at=? WHERE id=?").run(Math.min(20, row.attempts + 1), Date.now() + Math.min(3600, 2 ** (row.attempts + 1)) * 1e3, row.id);
35632
+ }
35633
+ }
35536
35634
  };
35537
- exec = x;
35635
+ }
35636
+ });
35637
+
35638
+ // src/usage/process.ts
35639
+ import { existsSync as existsSync5 } from "node:fs";
35640
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
35641
+ function startCollector(env) {
35642
+ if (process.env.VITEST || env.VITEST || env.APILIFT_COLLECTOR_DISABLED === "1") return;
35643
+ const entry2 = fileURLToPath3(new URL("./apilift-collector.js", import.meta.url));
35644
+ if (!existsSync5(entry2)) return;
35645
+ try {
35646
+ startBackground(entry2, env, () => {
35647
+ });
35648
+ } catch {
35649
+ }
35650
+ }
35651
+ var init_process2 = __esm({
35652
+ "src/usage/process.ts"() {
35653
+ "use strict";
35654
+ init_process();
35538
35655
  }
35539
35656
  });
35540
35657
 
@@ -37573,16 +37690,79 @@ var init_compat2 = __esm({
37573
37690
  });
37574
37691
 
37575
37692
  // src/native-host/registration.ts
37576
- import { createHash as createHash6 } from "node:crypto";
37693
+ import { createHash as createHash6, randomUUID as randomUUID11 } from "node:crypto";
37694
+ import { execFileSync as execFileSync2 } from "node:child_process";
37695
+ import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "node:fs";
37577
37696
  import { homedir as homedir3, platform as platform2 } from "node:os";
37578
37697
  import { dirname as dirname8, isAbsolute as isAbsolute4, join as join14, resolve as resolve4 } from "node:path";
37579
- import { chmod as chmod2, mkdir, readFile, rename as rename2, writeFile, copyFile } from "node:fs/promises";
37580
- function manifestDirectory(env = process.env) {
37698
+ import { chmod as chmod2, mkdir, readFile, rename as rename2, writeFile, copyFile, rm, stat } from "node:fs/promises";
37699
+ function batchLiteral(value) {
37700
+ if (/["\r\n\0]/u.test(value)) throw new Error("Native host launcher values contain unsupported control characters");
37701
+ return value.replaceAll("%", "%%");
37702
+ }
37703
+ function nativeLauncher(values, windows) {
37704
+ const environment = { APILIFT_HOME: values.root, APILIFT_NATIVE_HOST_NAME: values.hostName, APILIFT_NATIVE_MESSAGING_DIR: values.manifestRoot, ...values.site ? { APILIFT_URL: values.site } : {} };
37705
+ if (windows) return `@echo off\r
37706
+ chcp 65001 >nul 2>&1\r
37707
+ setlocal DisableDelayedExpansion\r
37708
+ ${Object.entries(environment).map(([key, value]) => `set "${key}=${batchLiteral(value)}"\r
37709
+ `).join("")}"${batchLiteral(values.nodePath)}" "${batchLiteral(values.hostScript)}" %*\r
37710
+ exit /b %errorlevel%\r
37711
+ `;
37712
+ return `#!/bin/sh
37713
+ ${Object.entries(environment).map(([key, value]) => `${key}=${posixQuote(value)}`).join(" ")} exec ${posixQuote(values.nodePath)} ${posixQuote(values.hostScript)} "$@"
37714
+ `;
37715
+ }
37716
+ function readWindowsHostValues(hostName, run2 = execFileSync2) {
37717
+ if (!/^[a-z0-9_]+(?:\.[a-z0-9_]+)*$/u.test(hostName)) throw new Error("Invalid native host name");
37718
+ const script = `$ErrorActionPreference='Stop'; $results=@(); foreach($view in @('32','64')) {
37719
+ $base=[Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::CurrentUser,[Microsoft.Win32.RegistryView]('Registry'+$view));
37720
+ try {
37721
+ $key=$base.OpenSubKey('Software\\Google\\Chrome\\NativeMessagingHosts\\${hostName}');
37722
+ $kind=$null; $data=$null;
37723
+ if($null -ne $key) { try {
37724
+ $value=$key.GetValue('',$null,[Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames);
37725
+ if($null -ne $value) { $kind=$key.GetValueKind('').ToString(); $data=[Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes((ConvertTo-Json -InputObject $value -Compress))); }
37726
+ } finally { $key.Dispose(); } }
37727
+ $results+=@{view=$view;present=($null -ne $key);kind=$kind;data=$data};
37728
+ } finally { $base.Dispose(); }
37729
+ }; ConvertTo-Json -InputObject @($results) -Compress`;
37730
+ try {
37731
+ const output2 = run2("powershell.exe", ["-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand", Buffer.from(script, "utf16le").toString("base64")], { encoding: "utf8", timeout: 1e4, windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
37732
+ const values = JSON.parse(String(output2));
37733
+ if (!Array.isArray(values) || values.length !== 2) throw new Error("Invalid registry snapshot");
37734
+ return ["32", "64"].map((view) => {
37735
+ const items = values.filter((item2) => item2?.view === view);
37736
+ const item = items[0];
37737
+ 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");
37738
+ return { view, present: item.present, kind: item.kind, value: item.data === null ? null : JSON.parse(Buffer.from(item.data, "base64").toString("utf8")) };
37739
+ });
37740
+ } catch {
37741
+ throw new Error("Could not inspect the current user's Chrome native host registration");
37742
+ }
37743
+ }
37744
+ function registerWindowsHost(hostName, manifest, owned, run2 = execFileSync2, write = true, backup) {
37745
+ const key = `HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\${hostName}`;
37746
+ const options2 = { encoding: "utf8", timeout: 1e4, windowsHide: true, stdio: ["ignore", "pipe", "pipe"] };
37747
+ const present = /* @__PURE__ */ new Set();
37748
+ for (const value of readWindowsHostValues(hostName, run2)) {
37749
+ if (!value.present) continue;
37750
+ const { view } = value;
37751
+ present.add(view);
37752
+ const current = value.kind === "String" && typeof value.value === "string" ? value.value : null;
37753
+ if (!owned || current?.toLowerCase() !== manifest.toLowerCase()) {
37754
+ if (!backup) throw new Error(`Native host registration is not owned by Apilift: ${key}`);
37755
+ backup(view, JSON.stringify(value));
37756
+ }
37757
+ }
37758
+ if (write) for (const view of /* @__PURE__ */ new Set(["32", ...present])) run2("reg.exe", ["add", key, "/ve", "/t", "REG_SZ", "/d", manifest, "/f", `/reg:${view}`], options2);
37759
+ }
37760
+ function manifestDirectory(env = process.env, system = { platform: platform2(), home: homedir3() }) {
37581
37761
  if (env.APILIFT_NATIVE_MESSAGING_DIR) return resolve4(env.APILIFT_NATIVE_MESSAGING_DIR);
37582
- const home = homedir3();
37583
- if (platform2() === "darwin") return join14(home, "Library", "Application Support", "Google", "Chrome", "NativeMessagingHosts");
37584
- if (platform2() === "win32") throw new Error("Windows native-host registration needs a data-root manifest and HKCU registry entry");
37585
- return join14(home, ".config", "google-chrome", "NativeMessagingHosts");
37762
+ const home = system.home;
37763
+ if (system.platform === "darwin") return join14(home, "Library", "Application Support", "Google", "Chrome", "NativeMessagingHosts");
37764
+ if (system.platform === "win32") throw new Error("Windows native-host registration needs a data-root manifest and HKCU registry entry");
37765
+ return join14(absoluteEnv(env.CHROME_CONFIG_HOME) || absoluteEnv(env.XDG_CONFIG_HOME) || join14(home, ".config"), "google-chrome", "NativeMessagingHosts");
37586
37766
  }
37587
37767
  async function registerNativeHost(options2) {
37588
37768
  const env = options2.env ?? process.env;
@@ -37597,7 +37777,24 @@ async function registerNativeHost(options2) {
37597
37777
  const manifestRoot = env.APILIFT_NATIVE_MESSAGING_DIR ? resolve4(env.APILIFT_NATIVE_MESSAGING_DIR) : platform2() === "win32" ? nativeRoot : manifestDirectory(env);
37598
37778
  const launcher = join14(nativeRoot, platform2() === "win32" ? "launcher.cmd" : "launcher.sh");
37599
37779
  const manifest = join14(manifestRoot, `${hostName}.json`);
37780
+ const manifests = [manifest];
37781
+ if (platform2() === "linux" && !env.APILIFT_NATIVE_MESSAGING_DIR) {
37782
+ for (const browser of ["google-chrome-beta", "google-chrome-unstable", "chromium"]) {
37783
+ const profile = join14(dirname8(dirname8(manifestRoot)), browser);
37784
+ if (await stat(profile).then((value) => value.isDirectory()).catch(() => false)) manifests.push(join14(profile, "NativeMessagingHosts", `${hostName}.json`));
37785
+ }
37786
+ }
37600
37787
  const receiptPath = join14(nativeRoot, "registration.json");
37788
+ const registryBackup = options2.allowReplaceUnowned ? (view, value) => {
37789
+ const directory = join14(nativeRoot, "backup");
37790
+ mkdirSync10(directory, { recursive: true, mode: 448 });
37791
+ const path2 = join14(directory, `${hostName}-registry-${view}-${fingerprint(value)}.json`);
37792
+ try {
37793
+ writeFileSync9(path2, JSON.stringify({ hostName, view, query: value }), { flag: "wx", mode: 384 });
37794
+ } catch (error62) {
37795
+ if (error62.code !== "EEXIST") throw error62;
37796
+ }
37797
+ } : void 0;
37601
37798
  const previous = await readFile(receiptPath, "utf8").then((value) => JSON.parse(value)).catch(() => ({}));
37602
37799
  const guard = async (path2) => {
37603
37800
  const bytes = await readFile(path2).catch(() => null);
@@ -37607,36 +37804,36 @@ async function registerNativeHost(options2) {
37607
37804
  await copyFile(path2, join14(nativeRoot, "backup", `${fingerprint(bytes)}-${Date.now()}`));
37608
37805
  };
37609
37806
  await guard(launcher);
37610
- await guard(manifest);
37807
+ for (const path2 of manifests) await guard(path2);
37808
+ if (platform2() === "win32" && !env.APILIFT_NATIVE_MESSAGING_DIR) {
37809
+ const bytes = await readFile(manifest).catch(() => null);
37810
+ registerWindowsHost(hostName, manifest, Boolean(bytes && previous[manifest] === fingerprint(bytes)), execFileSync2, false, registryBackup);
37811
+ }
37611
37812
  await mkdir(nativeRoot, { recursive: true, mode: 448 });
37612
37813
  await mkdir(manifestRoot, { recursive: true, mode: 448 });
37613
- const launcherText = platform2() === "win32" ? `@echo off\r
37614
- set "APILIFT_HOME=${root}"\r
37615
- set "APILIFT_NATIVE_HOST_NAME=${hostName}"\r
37616
- ${siteUrl2 ? `set "APILIFT_URL=${siteUrl2.origin}"\r
37617
- ` : ""}${env.APILIFT_NATIVE_MESSAGING_DIR ? `set "APILIFT_NATIVE_MESSAGING_DIR=${manifestRoot}"\r
37618
- ` : ""}"${nodePath}" "${hostScript}" %*\r
37619
- ` : `#!/bin/sh
37620
- APILIFT_HOME=${posixQuote(root)} APILIFT_NATIVE_HOST_NAME=${posixQuote(hostName)} ${siteUrl2 ? `APILIFT_URL=${posixQuote(siteUrl2.origin)} ` : ""}${env.APILIFT_NATIVE_MESSAGING_DIR ? `APILIFT_NATIVE_MESSAGING_DIR=${posixQuote(manifestRoot)} ` : ""}exec ${posixQuote(nodePath)} ${posixQuote(hostScript)} "$@"
37621
- `;
37814
+ const launcherText = nativeLauncher({ root, hostName, nodePath, hostScript, manifestRoot, site: siteUrl2?.origin }, platform2() === "win32");
37622
37815
  const manifestText = JSON.stringify({ name: hostName, description: "Apilift browser bridge", path: launcher, type: "stdio", allowed_origins: [`chrome-extension://${options2.extensionId}/`] }, null, 2) + "\n";
37623
37816
  const atomicWrite = async (path2, bytes, mode) => {
37624
- const temp = join14(dirname8(path2), `.${hostName}-${process.pid}-${Date.now()}.tmp`);
37625
- await writeFile(temp, bytes, { mode });
37626
- await chmod2(temp, mode);
37627
- await rename2(temp, path2);
37817
+ const temp = join14(dirname8(path2), `.${hostName}-${randomUUID11()}.tmp`);
37818
+ try {
37819
+ await writeFile(temp, bytes, { mode, flag: "wx" });
37820
+ await chmod2(temp, mode);
37821
+ await rename2(temp, path2);
37822
+ } finally {
37823
+ await rm(temp, { force: true });
37824
+ }
37628
37825
  };
37629
37826
  await atomicWrite(launcher, launcherText, platform2() === "win32" ? 384 : 448);
37630
- await atomicWrite(manifest, manifestText, 384);
37631
- const receipt = { ...previous, [launcher]: fingerprint(launcherText), [manifest]: fingerprint(manifestText) };
37632
- await atomicWrite(receiptPath, JSON.stringify(receipt, null, 2) + "\n", 384);
37633
- if (platform2() === "win32" && !env.APILIFT_NATIVE_MESSAGING_DIR) {
37634
- const { execFileSync: execFileSync3 } = await import("node:child_process");
37635
- execFileSync3("reg.exe", ["add", `HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\${hostName}`, "/ve", "/t", "REG_SZ", "/d", manifest, "/f"], { stdio: "ignore" });
37827
+ for (const path2 of manifests) {
37828
+ await mkdir(dirname8(path2), { recursive: true, mode: 448 });
37829
+ await atomicWrite(path2, manifestText, 384);
37636
37830
  }
37831
+ const receipt = { ...previous, [launcher]: fingerprint(launcherText), ...Object.fromEntries(manifests.map((path2) => [path2, fingerprint(manifestText)])) };
37832
+ await atomicWrite(receiptPath, JSON.stringify(receipt, null, 2) + "\n", 384);
37833
+ if (platform2() === "win32" && !env.APILIFT_NATIVE_MESSAGING_DIR) registerWindowsHost(hostName, manifest, true, execFileSync2, true, registryBackup);
37637
37834
  return { launcher, manifest, hostName };
37638
37835
  }
37639
- var fingerprint, hostNames, validId, posixQuote;
37836
+ var fingerprint, hostNames, validId, posixQuote, absoluteEnv;
37640
37837
  var init_registration = __esm({
37641
37838
  "src/native-host/registration.ts"() {
37642
37839
  "use strict";
@@ -37644,13 +37841,14 @@ var init_registration = __esm({
37644
37841
  hostNames = /* @__PURE__ */ new Set(["com.apilift.browser", "com.apilift.browser.dev"]);
37645
37842
  validId = /^[a-p]{32}$/;
37646
37843
  posixQuote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
37844
+ absoluteEnv = (value) => value && isAbsolute4(value) ? value : void 0;
37647
37845
  }
37648
37846
  });
37649
37847
 
37650
37848
  // src/update.ts
37651
37849
  import { fileURLToPath as fileURLToPath4 } from "node:url";
37652
- import { dirname as dirname9, join as join15, resolve as resolve5, sep } from "node:path";
37653
- import { mkdirSync as mkdirSync9, realpathSync as realpathSync2, writeFileSync as writeFileSync9 } from "node:fs";
37850
+ import { dirname as dirname9, join as join15, resolve as resolve5, sep, isAbsolute as isAbsolute5 } from "node:path";
37851
+ import { mkdirSync as mkdirSync11, realpathSync as realpathSync2, writeFileSync as writeFileSync10, readFileSync as readFileSync10 } from "node:fs";
37654
37852
  import { DatabaseSync as DatabaseSync6 } from "node:sqlite";
37655
37853
  function globalPackage(script, prefix) {
37656
37854
  const canonical = (path2) => {
@@ -37664,22 +37862,52 @@ function globalPackage(script, prefix) {
37664
37862
  const base2 = canonical(prefix).toLowerCase();
37665
37863
  return [join15(base2, "lib", "node_modules", "@apilift", "cli"), join15(base2, "node_modules", "@apilift", "cli")].some((path2) => normalized.startsWith(`${path2}${sep}`));
37666
37864
  }
37865
+ async function pnpmInstallations(runner, env) {
37866
+ const listed = await runner("pnpm", ["list", "-g", "@apilift/cli", "--depth", "0", "--json"], 1e4, env).catch(() => null);
37867
+ if (listed?.exitCode !== 0) return [];
37868
+ try {
37869
+ const groups = JSON.parse(listed.stdout);
37870
+ if (!Array.isArray(groups)) return [];
37871
+ return groups.flatMap((group) => {
37872
+ const pkg = group?.dependencies?.["@apilift/cli"];
37873
+ return pkg && typeof pkg.path === "string" && isAbsolute5(pkg.path) && typeof pkg.version === "string" ? [{ entrypoint: join15(pkg.path, "dist", "apilift.js"), version: pkg.version }] : [];
37874
+ });
37875
+ } catch {
37876
+ return [];
37877
+ }
37878
+ }
37879
+ async function installationManager(script, runner, env) {
37880
+ const prefix = await runner("npm", ["prefix", "-g"], 1e4, env).catch(() => null);
37881
+ if (prefix?.exitCode === 0 && globalPackage(script, prefix.stdout.trim())) return { manager: "npm", entrypoint: script };
37882
+ const canonical = (path2) => {
37883
+ try {
37884
+ return realpathSync2(path2);
37885
+ } catch {
37886
+ return resolve5(path2);
37887
+ }
37888
+ };
37889
+ const selected = (await pnpmInstallations(runner, env)).find((candidate2) => canonical(candidate2.entrypoint) === canonical(script));
37890
+ return selected ? { manager: "pnpm", entrypoint: selected.entrypoint } : null;
37891
+ }
37667
37892
  async function maybeAutoUpdate(options2) {
37668
37893
  if (options2.env.APILIFT_AUTO_UPDATE === "0" || options2.env.CI || options2.env.APILIFT_DEV_PLUGIN_ROOT || options2.env.APILIFT_UPDATE_REEXEC === "1") return null;
37669
37894
  const current = options2.current || CURRENT, runner = options2.runner || run, script = options2.script || fileURLToPath4(import.meta.url);
37670
37895
  if (!shouldCheckUpdate(options2.state, current)) return null;
37671
- const prefix = await runner("npm", ["prefix", "-g"], 1e4).catch(() => null);
37672
- if (!prefix || prefix.exitCode !== 0 || !globalPackage(script, prefix.stdout.trim())) return null;
37896
+ const installation = await installationManager(script, runner, options2.env);
37897
+ if (!installation) return null;
37673
37898
  markUpdateChecked(options2.state, current);
37674
- const update = await updateCli({ ...options2, current, runner, script, manual: false });
37899
+ let updatedEntry = installation.entrypoint;
37900
+ const update = await updateCli({ ...options2, current, runner, script, manual: false }, (entrypoint) => {
37901
+ updatedEntry = entrypoint;
37902
+ });
37675
37903
  if (update.stderr.startsWith("update-incomplete")) return update;
37676
37904
  if (!update.stdout.startsWith("Apilift ") || !update.stdout.includes(" was installed.")) return null;
37677
- const result3 = await runner(process.execPath, [script, ...options2.argv], 18e4, { ...process.env, ...options2.env, APILIFT_UPDATE_REEXEC: "1" });
37905
+ const result3 = await runner(process.execPath, [updatedEntry, ...options2.argv], 18e4, { ...process.env, ...options2.env, APILIFT_UPDATE_REEXEC: "1" });
37678
37906
  return { stdout: result3.stdout, stderr: result3.stderr, exitCode: result3.exitCode ?? 1, dispatched: false };
37679
37907
  }
37680
37908
  function lock(root) {
37681
37909
  const dir = join15(root, "locks");
37682
- mkdirSync9(dir, { recursive: true, mode: 448 });
37910
+ mkdirSync11(dir, { recursive: true, mode: 448 });
37683
37911
  const db = new DatabaseSync6(join15(dir, "self-update.sqlite"));
37684
37912
  db.exec("PRAGMA busy_timeout=0");
37685
37913
  try {
@@ -37693,15 +37921,15 @@ function lock(root) {
37693
37921
  function message(stdout, exitCode = 0, stderr = "") {
37694
37922
  return { stdout, stderr, exitCode, dispatched: false };
37695
37923
  }
37696
- async function updateCli(options2) {
37924
+ async function updateCli(options2, onInstalled) {
37697
37925
  const current = options2.current || CURRENT;
37698
37926
  const runner = options2.runner || run;
37699
37927
  let latest;
37700
37928
  let linkedAgents = [];
37701
37929
  try {
37702
- const installation = options2.state.installation();
37703
- if (installation) {
37704
- const me2 = await createCliApi(options2.site, options2.fetcher || fetch, installation.token).call("me");
37930
+ const installation2 = options2.state.installation();
37931
+ if (installation2) {
37932
+ const me2 = await createCliApi(options2.site, options2.fetcher || fetch, installation2.token).call("me");
37705
37933
  latest = me2.compatibility.latest.cli;
37706
37934
  linkedAgents = me2.installation.agents;
37707
37935
  } else latest = (await createCliApi(options2.site, options2.fetcher || fetch).call("currentRelease")).components.cli.version;
@@ -37713,30 +37941,41 @@ async function updateCli(options2) {
37713
37941
  if (compatible2(current, latest)) return message("The installed CLI and its required local components are up to date.\nNo installation changes were made. Continue the app work you were working on.\n");
37714
37942
  if (!options2.manual && (options2.env.APILIFT_AUTO_UPDATE === "0" || options2.env.CI || options2.env.APILIFT_DEV_PLUGIN_ROOT || options2.env.npm_execpath?.includes("npx"))) return message("", 5, `client-upgrade-required \u2014 Install Apilift ${latest} through the supported package installation, then retry.
37715
37943
  `);
37716
- const prefix = await runner("npm", ["prefix", "-g"], 1e4);
37717
37944
  const script = options2.script || fileURLToPath4(import.meta.url);
37718
- if (prefix.exitCode !== 0 || !globalPackage(script, prefix.stdout.trim())) return message("", 5, `client-upgrade-required \u2014 This CLI is not a global npm installation. Install Apilift ${latest} through your installation method, then retry.
37945
+ const installation = await installationManager(script, runner, options2.env);
37946
+ if (!installation) return message("", 5, `client-upgrade-required \u2014 This CLI is not a global npm or pnpm installation. Install Apilift ${latest} through your installation method, then retry.
37719
37947
  `);
37948
+ const { manager } = installation;
37949
+ let entrypoint = installation.entrypoint;
37720
37950
  const held = lock(options2.root);
37721
37951
  if (!held) return message("", 5, "update-in-progress \u2014 Another Apilift update is running. Retry after it finishes.\n");
37722
37952
  try {
37723
- const installed = await runner("npm", ["install", "-g", `@apilift/cli@${latest}`], 18e4);
37724
- if (installed.exitCode !== 0) return message("", 5, "update-failed \u2014 npm could not install the requested version. The previous CLI remains available. Check the npm installation and retry apilift update.\n");
37953
+ const installed = await runner(manager, [manager === "pnpm" ? "add" : "install", "-g", `@apilift/cli@${latest}`], 18e4, options2.env).catch(() => null);
37954
+ if (installed?.exitCode !== 0) return message("", 5, `update-failed \u2014 ${manager} could not finish installing the requested version. Run apilift --version to check the installed version, then retry apilift update.
37955
+ `);
37725
37956
  options2.state.db.exec("CREATE TABLE IF NOT EXISTS cli_update_state (id INTEGER PRIMARY KEY CHECK(id=1), version TEXT NOT NULL, updated_at TEXT NOT NULL)");
37726
37957
  options2.state.db.prepare("INSERT INTO cli_update_state (id,version,updated_at) VALUES (1,?,?) ON CONFLICT(id) DO UPDATE SET version=excluded.version,updated_at=excluded.updated_at").run(latest, (/* @__PURE__ */ new Date()).toISOString());
37727
37958
  const nativeRoot = join15(options2.root, "native-host");
37728
- mkdirSync9(nativeRoot, { recursive: true, mode: 448 });
37729
- writeFileSync9(join15(nativeRoot, "restart-required.json"), `${JSON.stringify({ version: latest })}
37959
+ mkdirSync11(nativeRoot, { recursive: true, mode: 448 });
37960
+ writeFileSync10(join15(nativeRoot, "restart-required.json"), `${JSON.stringify({ version: latest })}
37730
37961
  `, { mode: 384 });
37731
37962
  try {
37732
- await registerNativeHost({ root: options2.root, hostScript: join15(dirname9(script), "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 });
37963
+ if (manager === "pnpm") {
37964
+ const candidates = (await pnpmInstallations(runner, options2.env)).filter((candidate2) => candidate2.version === latest);
37965
+ if (candidates.length !== 1) throw new Error("Updated pnpm installation is ambiguous or unavailable");
37966
+ entrypoint = candidates[0].entrypoint;
37967
+ const pkg = JSON.parse(readFileSync10(join15(dirname9(entrypoint), "..", "package.json"), "utf8"));
37968
+ if (pkg.name !== "@apilift/cli" || pkg.version !== latest) throw new Error("Updated pnpm package could not be verified");
37969
+ }
37970
+ await registerNativeHost({ root: options2.root, hostScript: join15(dirname9(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 });
37733
37971
  for (const agent2 of linkedAgents) {
37734
37972
  if (!ensureAgentPlugin(agent2, options2.env).ready) throw new Error("Updated agent plugin activation is unverified");
37735
37973
  }
37736
37974
  } catch {
37737
- return message("", 5, `update-incomplete \u2014 Apilift ${latest} was installed, but local component registration needs repair. Run apilift setup continue, then apilift doctor. Do not repeat the npm install.
37975
+ return message("", 5, `update-incomplete \u2014 Apilift ${latest} was installed, but local component registration needs repair. Run apilift setup continue, then apilift doctor. Do not repeat the package installation.
37738
37976
  `);
37739
37977
  }
37978
+ onInstalled?.(entrypoint);
37740
37979
  return message(`Apilift ${latest} was installed. Continue the app work you were working on.
37741
37980
  `);
37742
37981
  } finally {
@@ -37762,15 +38001,15 @@ var init_update = __esm({
37762
38001
  init_version();
37763
38002
  init_registration();
37764
38003
  init_plugin();
37765
- run = async (command6, args, timeout, env) => await x(command6, args, { timeout, throwOnError: false, ...env ? { nodeOptions: { env } } : {} });
38004
+ run = async (command6, args, timeout, env) => await x(command6, args, { timeout, throwOnError: false, nodeOptions: { windowsHide: true, env: { ...process.env, ...env } } });
37766
38005
  CURRENT = cliVersion;
37767
38006
  STORE_EXTENSION_ID = "fcndhdoedflmfmckkgpkdhmglgepbhnd";
37768
38007
  }
37769
38008
  });
37770
38009
 
37771
38010
  // src/setup-check.ts
37772
- import { createHash as createHash7, randomUUID as randomUUID11 } from "node:crypto";
37773
- import { existsSync as existsSync6, readFileSync as readFileSync9, realpathSync as realpathSync3, writeFileSync as writeFileSync10 } from "node:fs";
38011
+ import { createHash as createHash7, randomUUID as randomUUID12 } from "node:crypto";
38012
+ import { existsSync as existsSync6, readFileSync as readFileSync11, realpathSync as realpathSync3, writeFileSync as writeFileSync11 } from "node:fs";
37774
38013
  import { dirname as dirname10, join as join16 } from "node:path";
37775
38014
  async function setupCheckBody(client, scopeId, value) {
37776
38015
  if (typeof value.body === "string") return Buffer.byteLength(value.body, "utf8") <= maxCheckBodyBytes ? value.body : null;
@@ -37797,12 +38036,12 @@ function challengeFile(root, agent2) {
37797
38036
  function setupChallenge(root, agent2, site) {
37798
38037
  const file2 = challengeFile(root, agent2);
37799
38038
  try {
37800
- const saved = JSON.parse(readFileSync9(file2, "utf8"));
38039
+ const saved = JSON.parse(readFileSync11(file2, "utf8"));
37801
38040
  if (new URL(saved.url).origin === site && Number.isFinite(saved.createdAt) && Date.now() - saved.createdAt < 10 * 6e4) return saved;
37802
38041
  } catch {
37803
38042
  }
37804
- const challenge = { url: `${site}/setup-check?challenge=${randomUUID11()}`, createdAt: Date.now() };
37805
- writeFileSync10(file2, JSON.stringify(challenge), { mode: 384 });
38043
+ const challenge = { url: `${site}/setup-check?challenge=${randomUUID12()}`, createdAt: Date.now() };
38044
+ writeFileSync11(file2, JSON.stringify(challenge), { mode: 384 });
37806
38045
  return challenge;
37807
38046
  }
37808
38047
  async function setupBridge(root) {
@@ -37812,6 +38051,22 @@ async function setupBridge(root) {
37812
38051
  if (!client.legacyBridge) await waitForBridge(root, cliVersion, Math.max(0, 9e4 - (Date.now() - startedAt)));
37813
38052
  return client;
37814
38053
  }
38054
+ async function setupReadiness(api, waitForReport, timeoutMs = 1e4) {
38055
+ const deadline = Date.now() + timeoutMs;
38056
+ const signal = AbortSignal.timeout(timeoutMs);
38057
+ let readiness2 = await api.call("updateReadiness", { body: {}, ...waitForReport ? { signal } : {} });
38058
+ while (waitForReport && (!readiness2.machine.nativeHostReady || !readiness2.machine.extensionReady) && Date.now() < deadline) {
38059
+ await new Promise((resolve6) => setTimeout(resolve6, Math.min(250, Math.max(0, deadline - Date.now()))));
38060
+ if (signal.aborted || Date.now() >= deadline) break;
38061
+ try {
38062
+ readiness2 = await api.call("updateReadiness", { body: {}, signal });
38063
+ } catch (error62) {
38064
+ if (signal.aborted) break;
38065
+ throw error62;
38066
+ }
38067
+ }
38068
+ return readiness2;
38069
+ }
37815
38070
  async function verifySetupCheck(root, challenge) {
37816
38071
  const client = await setupBridge(root);
37817
38072
  const pageOrigin = new URL(challenge.url).origin;
@@ -37820,7 +38075,7 @@ async function verifySetupCheck(root, challenge) {
37820
38075
  const inspect = await client.openScope({ kind: "inspect", ...scopeInput });
37821
38076
  let candidates = [];
37822
38077
  try {
37823
- const deadline = Date.now() + (client.legacyBridge ? 9e4 : 1e4);
38078
+ const deadline = Date.now() + 1e4;
37824
38079
  do {
37825
38080
  candidates = (await client.candidates(inspect.id)).candidates;
37826
38081
  if (candidates.length) break;
@@ -37831,7 +38086,7 @@ async function verifySetupCheck(root, challenge) {
37831
38086
  }
37832
38087
  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" };
37833
38088
  const execute = await client.openScope({ kind: "execute", tabSelector: candidates[0].selector, ...scopeInput });
37834
- const id2 = randomUUID11();
38089
+ const id2 = randomUUID12();
37835
38090
  try {
37836
38091
  await client.enqueueJob(execute.id, { job: { id: id2, kind: "replay", effect: "read", method: "GET", url: challenge.url, headers: {}, pageOrigin, allowedRequestOrigin: pageOrigin, timeoutMs: 2e4 } });
37837
38092
  const deadline = Date.now() + 3e4;
@@ -38065,13 +38320,15 @@ Fix the local Chrome registration and retry:
38065
38320
  ${prefix} setup continue
38066
38321
  `);
38067
38322
  }
38068
- let bridgeError;
38323
+ let bridgeError, connected = false;
38069
38324
  try {
38070
- await setupBridge(dataRoot(env));
38325
+ connected = !(await setupBridge(dataRoot(env))).legacyBridge;
38071
38326
  } catch (error62) {
38072
38327
  bridgeError = error62;
38073
38328
  }
38074
- const readiness2 = await api.call("updateReadiness", { body: {} });
38329
+ const readiness2 = await setupReadiness(api, connected);
38330
+ if (connected && (!readiness2.machine.nativeHostReady || !readiness2.machine.extensionReady)) return result2("", 5, `registry-readiness-pending \u2014 Chrome connected, but this installation's readiness report is still pending. Keep Chrome open and retry ${prefix} setup continue.
38331
+ `);
38075
38332
  if (!readiness2.machine.nativeHostReady || !readiness2.machine.extensionReady) return { ...result2(`${JSON.stringify({ action: "user", userMessage: extensionUserMessage(env.APILIFT_DEV_EXTENSION_PATH), resumeCommand: `${prefix} setup continue` })}
38076
38333
  `), stdoutJson: true };
38077
38334
  if (bridgeError) return result2("", 5, `extension-unavailable \u2014 ${bridgeError instanceof Error ? bridgeError.message : "Chrome did not respond"}. Check native host and extension setup with ${prefix} doctor.
@@ -38228,7 +38485,7 @@ var init_cli = __esm({
38228
38485
  init_state();
38229
38486
  init_analytics2();
38230
38487
  init_ledger();
38231
- init_process();
38488
+ init_process2();
38232
38489
  init_update();
38233
38490
  init_version();
38234
38491
  init_setup_check();