@apilift/cli 1.5.3 → 1.5.4

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.
@@ -28013,11 +28013,13 @@ var init_schema = __esm({
28013
28013
  });
28014
28014
  proofPointer = pointerSchema.refine((value) => value.split("/").every((part) => !/\{(?:input|result|generated)\./u.test(part) || /^\{input\.[A-Za-z][A-Za-z0-9_-]*\}$/u.test(part)), "A dynamic JSON Pointer segment must be exactly {input.name}");
28015
28015
  proofComparison = external_exports.union([external_exports.strictObject({ path: proofPointer, input: inputNameSchema }), external_exports.strictObject({ path: proofPointer, result: pointerSchema }), external_exports.strictObject({ path: proofPointer, value: valueSchema })]);
28016
- proofSchema = external_exports.strictObject({ request: requestSchema, effect: external_exports.literal("read").optional(), select: external_exports.strictObject({ path: proofPointer, where: external_exports.array(proofComparison).min(1) }).optional(), equals: external_exports.array(proofComparison).min(1).optional(), absent: external_exports.literal(true).optional() }).superRefine((v2, ctx) => {
28016
+ proofSchema = external_exports.strictObject({ request: requestSchema, effect: external_exports.literal("read").optional(), select: external_exports.strictObject({ path: proofPointer, where: external_exports.array(proofComparison).min(1) }).optional(), equals: external_exports.array(proofComparison).min(1).optional(), response: external_exports.strictObject({ path: proofPointer.optional(), schema: jsonSchema }).optional(), absent: external_exports.literal(true).optional() }).superRefine((v2, ctx) => {
28017
28017
  if (!!v2.equals === !!v2.absent)
28018
28018
  ctx.addIssue({ code: "custom", message: "Exactly one of equals and absent is required" });
28019
28019
  if (v2.select && v2.absent)
28020
28020
  ctx.addIssue({ code: "custom", path: ["select"], message: "Collection selection cannot prove absence; use a targeted HTTP-404 read" });
28021
+ if (v2.response && v2.absent)
28022
+ ctx.addIssue({ code: "custom", path: ["response"], message: "An HTTP-404 absence proof has no response schema" });
28021
28023
  if (!v2.effect && !["GET", "HEAD"].includes(v2.request.method))
28022
28024
  ctx.addIssue({ code: "custom", path: ["request", "method"], message: "Proof request must be GET or HEAD unless effect is read" });
28023
28025
  });
@@ -29459,11 +29459,13 @@ var responseSchema = external_exports.strictObject({ schema: jsonSchema.optional
29459
29459
  });
29460
29460
  var proofPointer = pointerSchema.refine((value) => value.split("/").every((part) => !/\{(?:input|result|generated)\./u.test(part) || /^\{input\.[A-Za-z][A-Za-z0-9_-]*\}$/u.test(part)), "A dynamic JSON Pointer segment must be exactly {input.name}");
29461
29461
  var proofComparison = external_exports.union([external_exports.strictObject({ path: proofPointer, input: inputNameSchema }), external_exports.strictObject({ path: proofPointer, result: pointerSchema }), external_exports.strictObject({ path: proofPointer, value: valueSchema })]);
29462
- var proofSchema = external_exports.strictObject({ request: requestSchema, effect: external_exports.literal("read").optional(), select: external_exports.strictObject({ path: proofPointer, where: external_exports.array(proofComparison).min(1) }).optional(), equals: external_exports.array(proofComparison).min(1).optional(), absent: external_exports.literal(true).optional() }).superRefine((v, ctx) => {
29462
+ var proofSchema = external_exports.strictObject({ request: requestSchema, effect: external_exports.literal("read").optional(), select: external_exports.strictObject({ path: proofPointer, where: external_exports.array(proofComparison).min(1) }).optional(), equals: external_exports.array(proofComparison).min(1).optional(), response: external_exports.strictObject({ path: proofPointer.optional(), schema: jsonSchema }).optional(), absent: external_exports.literal(true).optional() }).superRefine((v, ctx) => {
29463
29463
  if (!!v.equals === !!v.absent)
29464
29464
  ctx.addIssue({ code: "custom", message: "Exactly one of equals and absent is required" });
29465
29465
  if (v.select && v.absent)
29466
29466
  ctx.addIssue({ code: "custom", path: ["select"], message: "Collection selection cannot prove absence; use a targeted HTTP-404 read" });
29467
+ if (v.response && v.absent)
29468
+ ctx.addIssue({ code: "custom", path: ["response"], message: "An HTTP-404 absence proof has no response schema" });
29467
29469
  if (!v.effect && !["GET", "HEAD"].includes(v.request.method))
29468
29470
  ctx.addIssue({ code: "custom", path: ["request", "method"], message: "Proof request must be GET or HEAD unless effect is read" });
29469
29471
  });
@@ -29644,7 +29646,7 @@ function validateDefinition(kind, name, value) {
29644
29646
  for (const [i, comparison] of (definition2.proof?.select?.where ?? []).entries())
29645
29647
  if ("input" in comparison && !names2.includes(comparison.input))
29646
29648
  issues.push({ path: `/proof/select/where/${i}/input`, message: "Unknown input" });
29647
- const proofPaths = [...definition2.proof?.equals ?? [], ...definition2.proof?.select?.where ?? [], ...definition2.proof?.select ? [definition2.proof.select] : []];
29649
+ const proofPaths = [...definition2.proof?.equals ?? [], ...definition2.proof?.select?.where ?? [], ...definition2.proof?.select ? [definition2.proof.select] : [], ...definition2.proof?.response?.path !== void 0 ? [{ path: definition2.proof.response.path }] : []];
29648
29650
  for (const item of proofPaths)
29649
29651
  for (const match of item.path.matchAll(/\{input\.([^{}]+)\}/gu))
29650
29652
  if (!names2.includes(match[1]))
package/dist/apilift.js CHANGED
@@ -28077,11 +28077,13 @@ var init_schema = __esm({
28077
28077
  });
28078
28078
  proofPointer = pointerSchema.refine((value) => value.split("/").every((part) => !/\{(?:input|result|generated)\./u.test(part) || /^\{input\.[A-Za-z][A-Za-z0-9_-]*\}$/u.test(part)), "A dynamic JSON Pointer segment must be exactly {input.name}");
28079
28079
  proofComparison = external_exports.union([external_exports.strictObject({ path: proofPointer, input: inputNameSchema }), external_exports.strictObject({ path: proofPointer, result: pointerSchema }), external_exports.strictObject({ path: proofPointer, value: valueSchema })]);
28080
- proofSchema = external_exports.strictObject({ request: requestSchema, effect: external_exports.literal("read").optional(), select: external_exports.strictObject({ path: proofPointer, where: external_exports.array(proofComparison).min(1) }).optional(), equals: external_exports.array(proofComparison).min(1).optional(), absent: external_exports.literal(true).optional() }).superRefine((v, ctx) => {
28080
+ proofSchema = external_exports.strictObject({ request: requestSchema, effect: external_exports.literal("read").optional(), select: external_exports.strictObject({ path: proofPointer, where: external_exports.array(proofComparison).min(1) }).optional(), equals: external_exports.array(proofComparison).min(1).optional(), response: external_exports.strictObject({ path: proofPointer.optional(), schema: jsonSchema }).optional(), absent: external_exports.literal(true).optional() }).superRefine((v, ctx) => {
28081
28081
  if (!!v.equals === !!v.absent)
28082
28082
  ctx.addIssue({ code: "custom", message: "Exactly one of equals and absent is required" });
28083
28083
  if (v.select && v.absent)
28084
28084
  ctx.addIssue({ code: "custom", path: ["select"], message: "Collection selection cannot prove absence; use a targeted HTTP-404 read" });
28085
+ if (v.response && v.absent)
28086
+ ctx.addIssue({ code: "custom", path: ["response"], message: "An HTTP-404 absence proof has no response schema" });
28085
28087
  if (!v.effect && !["GET", "HEAD"].includes(v.request.method))
28086
28088
  ctx.addIssue({ code: "custom", path: ["request", "method"], message: "Proof request must be GET or HEAD unless effect is read" });
28087
28089
  });
@@ -28496,7 +28498,7 @@ function validateDefinition(kind, name, value) {
28496
28498
  for (const [i, comparison] of (definition2.proof?.select?.where ?? []).entries())
28497
28499
  if ("input" in comparison && !names2.includes(comparison.input))
28498
28500
  issues.push({ path: `/proof/select/where/${i}/input`, message: "Unknown input" });
28499
- const proofPaths = [...definition2.proof?.equals ?? [], ...definition2.proof?.select?.where ?? [], ...definition2.proof?.select ? [definition2.proof.select] : []];
28501
+ const proofPaths = [...definition2.proof?.equals ?? [], ...definition2.proof?.select?.where ?? [], ...definition2.proof?.select ? [definition2.proof.select] : [], ...definition2.proof?.response?.path !== void 0 ? [{ path: definition2.proof.response.path }] : []];
28500
28502
  for (const item of proofPaths)
28501
28503
  for (const match of item.path.matchAll(/\{input\.([^{}]+)\}/gu))
28502
28504
  if (!names2.includes(match[1]))
@@ -28797,346 +28799,10 @@ var init_api2 = __esm({
28797
28799
  }
28798
28800
  });
28799
28801
 
28800
- // src/version.ts
28801
- import { readFileSync as readFileSync3 } from "node:fs";
28802
- var cliVersion;
28803
- var init_version = __esm({
28804
- "src/version.ts"() {
28805
- "use strict";
28806
- cliVersion = JSON.parse(readFileSync3(new URL("../package.json", import.meta.url), "utf8")).version;
28807
- }
28808
- });
28809
-
28810
- // src/api.ts
28811
- function retryDelay(response, attempt) {
28812
- const value = response.headers.get("retry-after");
28813
- if (value) {
28814
- const seconds = Number(value);
28815
- if (Number.isFinite(seconds) && seconds >= 0) return Math.min(seconds * 1e3, 5e3);
28816
- const date5 = Date.parse(value);
28817
- if (Number.isFinite(date5)) return Math.min(Math.max(date5 - Date.now(), 0), 5e3);
28818
- }
28819
- return 250 * 2 ** attempt;
28820
- }
28821
- function sleep(ms) {
28822
- return new Promise((resolve10) => setTimeout(resolve10, ms));
28823
- }
28824
- function createCliApi(baseUrl, fetcher = fetch, token, sessionId) {
28825
- const transport = async (input2, init) => {
28826
- const method = init?.method ?? "GET";
28827
- const headers = new Headers(init?.headers);
28828
- headers.set("Apilift-Capabilities", "distributed-rollouts-v1,discovery-proof-v2,dynamic-bindings-v1");
28829
- headers.set("Apilift-Client", `cli/${cliVersion}`);
28830
- if (token) headers.set("Authorization", `Bearer ${token}`);
28831
- if (sessionId) headers.set("Apilift-Session", sessionId);
28832
- const path2 = new URL(input2 instanceof Request ? input2.url : String(input2), baseUrl).pathname;
28833
- const timeoutMs = method === "POST" && path2 === "/api/v1/installations" ? 3e4 : 1e4;
28834
- for (let attempt = 0; ; attempt++) {
28835
- try {
28836
- const response = await fetcher(input2, { ...init, headers, signal: init?.signal ?? AbortSignal.timeout(timeoutMs) });
28837
- if (method !== "GET" || attempt >= 2 || !retryStatuses.has(response.status)) {
28838
- if (response.status !== 204 && !response.headers.get("content-type")?.toLowerCase().includes("json")) throw new ApiClientError(response.status, null);
28839
- if (response.status !== 204) {
28840
- try {
28841
- await response.clone().json();
28842
- } catch {
28843
- throw new ApiClientError(response.status, null);
28844
- }
28845
- }
28846
- return response;
28847
- }
28848
- await sleep(retryDelay(response, attempt));
28849
- } catch (error62) {
28850
- if (error62 instanceof ApiClientError) throw error62;
28851
- if (method !== "GET") throw new ApiDeliveryUncertainError(error62);
28852
- if (attempt >= 2) throw new ApiReadUnavailableError(error62);
28853
- await sleep(250 * 2 ** attempt);
28854
- }
28855
- }
28856
- };
28857
- return createApiClient(baseUrl, transport);
28858
- }
28859
- var retryStatuses, ApiDeliveryUncertainError, ApiReadUnavailableError;
28860
- var init_api3 = __esm({
28861
- "src/api.ts"() {
28862
- "use strict";
28863
- init_version();
28864
- init_api2();
28865
- retryStatuses = /* @__PURE__ */ new Set([408, 429, 502, 503, 504]);
28866
- ApiDeliveryUncertainError = class extends Error {
28867
- constructor(cause) {
28868
- super("The Registry did not confirm whether the mutation was applied");
28869
- this.cause = cause;
28870
- }
28871
- };
28872
- ApiReadUnavailableError = class extends Error {
28873
- constructor(cause) {
28874
- super("The Registry could not be reached after three attempts");
28875
- this.cause = cause;
28876
- }
28877
- };
28878
- }
28879
- });
28880
-
28881
- // src/commands/registry.ts
28882
- function parseInvocation(argv) {
28883
- const issues = [];
28884
- let cursor = 0;
28885
- let sessionLabel;
28886
- let version2 = false;
28887
- while (cursor < argv.length) {
28888
- const token = argv[cursor];
28889
- if (token === "--session") {
28890
- const value = argv[cursor + 1];
28891
- if (!value || value.startsWith("--")) issues.push("--session needs a worker label");
28892
- else if (sessionLabel) issues.push("--session may appear only once");
28893
- else sessionLabel = value;
28894
- cursor += value && !value.startsWith("--") ? 2 : 1;
28895
- } else if (token === "--version") {
28896
- version2 = true;
28897
- cursor++;
28898
- } else break;
28899
- }
28900
- if (sessionLabel && (sessionLabel.length > 80 || !/^[A-Za-z0-9_-]+$/u.test(sessionLabel))) issues.push("--session must be 1 to 80 letters, digits, hyphens or underscores");
28901
- const base2 = { sessionLabel, positionals: [], options: {}, flags: [], repeats: {}, actionArgs: [] };
28902
- if (version2) {
28903
- if (cursor < argv.length) issues.push("--version takes no command");
28904
- return { ...issues.length ? {} : { invocation: { key: "version", ...base2 } }, issues };
28905
- }
28906
- const remaining = argv.slice(cursor);
28907
- if (remaining.length === 1 && remaining[0] === "--help") return { invocation: { key: "helpCommands", ...base2 }, issues };
28908
- const selected = Object.entries(commandRegistry).filter(([, command7]) => command7.path.every((part, index) => remaining[index] === part)).sort((left, right) => right[1].path.length - left[1].path.length)[0];
28909
- if (!selected) return { issues: [`Unknown command: ${remaining.slice(0, 2).join(" ")}`] };
28910
- const [key, command6] = selected;
28911
- const positionals = [];
28912
- const options2 = {};
28913
- const flags = [];
28914
- const repeats = {};
28915
- const actionArgs = [];
28916
- for (let index = command6.path.length; index < remaining.length; index++) {
28917
- const token = remaining[index];
28918
- if (token.startsWith("--")) {
28919
- const name = token.slice(2);
28920
- if (name === "session") {
28921
- issues.push("--session must appear before the command");
28922
- continue;
28923
- }
28924
- if (command6.values?.includes(name) || command6.repeat?.includes(name)) {
28925
- const value = remaining[index + 1];
28926
- if (!value || value.startsWith("--")) {
28927
- issues.push(`--${name} needs a value`);
28928
- continue;
28929
- }
28930
- if (command6.repeat?.includes(name)) (repeats[name] ??= []).push(value);
28931
- else if (Object.hasOwn(options2, name)) issues.push(`--${name} may appear only once`);
28932
- else options2[name] = value;
28933
- index++;
28934
- } else if (command6.flags?.includes(name)) {
28935
- if (flags.includes(name)) issues.push(`--${name} may appear only once`);
28936
- else flags.push(name);
28937
- } else if (command6.actionFlags && positionals.length >= 2) actionArgs.push(token);
28938
- else issues.push(`Unknown option --${name}`);
28939
- } else if (command6.actionFlags && positionals.length >= 2 && actionArgs.length) actionArgs.push(token);
28940
- else if (positionals.length < command6.max) positionals.push(token);
28941
- else issues.push(`Unexpected argument ${token}`);
28942
- }
28943
- if (positionals.length < command6.min) issues.push(`${command6.path.join(" ") || "command"} needs ${command6.min} argument${command6.min === 1 ? "" : "s"}`);
28944
- return { ...issues.length ? {} : { invocation: { key, sessionLabel, positionals, options: options2, flags, repeats, actionArgs } }, issues };
28945
- }
28946
- var commandRegistry;
28947
- var init_registry = __esm({
28948
- "src/commands/registry.ts"() {
28949
- "use strict";
28950
- commandRegistry = {
28951
- root: { path: [], min: 0, max: 0 },
28952
- setup: { path: ["setup"], min: 0, max: 0, values: ["code"] },
28953
- setupContinue: { path: ["setup", "continue"], min: 0, max: 0 },
28954
- doctor: { path: ["doctor"], min: 0, max: 0, values: ["app"] },
28955
- update: { path: ["update"], min: 0, max: 0 },
28956
- helpCommands: { path: ["help", "commands"], min: 0, max: 0 },
28957
- appsSearch: { path: ["apps", "search"], min: 1, max: 1, values: ["domain", "limit", "cursor"] },
28958
- appsUse: { path: ["apps", "use"], min: 1, max: 1 },
28959
- appsRelease: { path: ["apps", "release"], min: 1, max: 1 },
28960
- appsRefresh: { path: ["apps", "refresh"], min: 1, max: 1, values: ["action"] },
28961
- appsNew: { path: ["apps", "new"], min: 1, max: 1 },
28962
- appsUpdate: { path: ["apps", "update"], min: 1, max: 1 },
28963
- appsHistory: { path: ["apps", "history"], min: 1, max: 1, values: ["resource", "limit", "cursor"] },
28964
- actionsList: { path: ["actions", "list"], min: 1, max: 1, values: ["entity", "limit", "cursor"], flags: ["all"] },
28965
- actionsSearch: { path: ["actions", "search"], min: 2, max: 2, values: ["entity", "limit", "cursor"] },
28966
- actionsShow: { path: ["actions", "show"], min: 2, max: 11, values: ["input-template"], flags: ["schema"] },
28967
- run: { path: ["run"], min: 2, max: 2, values: ["input", "output"], flags: ["check", "no-context"], repeat: ["select"], actionFlags: true },
28968
- credentialsList: { path: ["credentials", "list"], min: 1, max: 2 },
28969
- credentialsUse: { path: ["credentials", "use"], min: 3, max: 3 },
28970
- credentialsAdd: { path: ["credentials", "add"], min: 2, max: 2 },
28971
- credentialsRemove: { path: ["credentials", "remove"], min: 3, max: 3 },
28972
- credentialsUnbound: { path: ["credentials", "unbound"], min: 0, max: 0 },
28973
- credentialsAttach: { path: ["credentials", "attach"], min: 3, max: 3 },
28974
- transportsShow: { path: ["transports", "show"], min: 1, max: 2 },
28975
- browserCheck: { path: ["browser", "check"], min: 1, max: 1 },
28976
- browserTabs: { path: ["browser", "tabs"], min: 1, max: 1, values: ["select"] },
28977
- contextSet: { path: ["context", "set"], min: 2, max: 2, values: ["app"] },
28978
- contextShow: { path: ["context", "show"], min: 0, max: 0, values: ["app"] },
28979
- contextUnset: { path: ["context", "unset"], min: 1, max: 1, values: ["app"] },
28980
- contextClear: { path: ["context", "clear"], min: 0, max: 0, values: ["app"] },
28981
- discoverDraft: { path: ["discover", "draft"], min: 1, max: 2 },
28982
- discoverCheck: { path: ["discover", "check"], min: 0, max: 0 },
28983
- discoverRun: { path: ["discover", "run"], min: 0, max: 0, flags: ["proof-only"] },
28984
- discoverPublish: { path: ["discover", "publish"], min: 0, max: 0 },
28985
- discoverStatus: { path: ["discover", "status"], min: 0, max: 0 },
28986
- discoverDefer: { path: ["discover", "defer"], min: 0, max: 0 },
28987
- discoverDiscard: { path: ["discover", "discard"], min: 0, max: 0 },
28988
- discoverRebase: { path: ["discover", "rebase"], min: 0, max: 0 },
28989
- discoverRemove: { path: ["discover", "remove"], min: 2, max: 2 },
28990
- discoverClose: { path: ["discover", "close"], min: 0, max: 0 },
28991
- discoverApiAssess: { path: ["discover", "api", "assess"], min: 1, max: 1 },
28992
- discoverApiSave: { path: ["discover", "api", "save"], min: 0, max: 0 },
28993
- discoverApiStatus: { path: ["discover", "api", "status"], min: 0, max: 1 },
28994
- discoverCaptureStart: { path: ["discover", "capture", "start"], min: 0, max: 0 },
28995
- discoverCaptureStop: { path: ["discover", "capture", "stop"], min: 0, max: 0 },
28996
- discoverCaptureList: { path: ["discover", "capture", "list"], min: 0, max: 0, values: ["limit", "cursor"] },
28997
- discoverCaptureShow: { path: ["discover", "capture", "show"], min: 1, max: 1, values: ["part", "pointer"] },
28998
- discoverCaptureSelect: { path: ["discover", "capture", "select"], min: 1, max: 1 },
28999
- discoverCaptureAuth: { path: ["discover", "capture", "auth"], min: 1, max: 1 },
29000
- discoverCaptureCancel: { path: ["discover", "capture", "cancel"], min: 0, max: 0 }
29001
- };
29002
- }
29003
- });
29004
-
29005
- // src/session.ts
29006
- import { createHash as createHash2 } from "node:crypto";
29007
- function apiSessionId(installationId, session) {
29008
- if (!session) return void 0;
29009
- const hex3 = createHash2("sha256").update("apilift-api-session/v1\0").update(installationId).update("\0").update(session.id).digest("hex");
29010
- return `${hex3.slice(0, 8)}-${hex3.slice(8, 12)}-5${hex3.slice(13, 16)}-a${hex3.slice(17, 20)}-${hex3.slice(20, 32)}`;
29011
- }
29012
- function agentSession(env, explicitLabel) {
29013
- explicitLabel ??= env.APILIFT_SESSION;
29014
- if (explicitLabel !== void 0 && !/^[A-Za-z0-9_-]{1,80}$/u.test(explicitLabel)) return null;
29015
- const codex = env.CODEX_THREAD_ID || env.CODEX_SESSION_ID;
29016
- const claude = env.CLAUDE_CODE_SESSION_ID;
29017
- if (explicitLabel) {
29018
- const agent2 = claude || env.CLAUDECODE ? "claude" : codex || env.CODEX_HOME ? "codex" : null;
29019
- return agent2 ? { id: `${agent2}:label:${explicitLabel}`, agent: agent2 } : null;
29020
- }
29021
- if (codex && !claude) return { id: `codex:${codex}`, agent: "codex" };
29022
- if (claude && !codex) return { id: `claude:${claude}`, agent: "claude" };
29023
- return null;
29024
- }
29025
- function commandPrefix(label2) {
29026
- return `apilift${label2 ? ` --session ${label2}` : ""}`;
29027
- }
29028
- var init_session = __esm({
29029
- "src/session.ts"() {
29030
- "use strict";
29031
- }
29032
- });
29033
-
29034
- // src/browser-response.ts
29035
- import { createHash as createHash3 } from "node:crypto";
29036
- async function browserResponseBody(client, scopeId, value, maxBytes = 48 * 1024 * 1024) {
29037
- if (value.bodyArtifact !== void 0) {
29038
- const artifact = value.bodyArtifact;
29039
- if (value.body != null || !artifact || typeof artifact.sha256 !== "string" || !/^[0-9a-f]{64}$/.test(artifact.sha256) || !Number.isInteger(artifact.bytes) || Number(artifact.bytes) < 1 || Number(artifact.bytes) > maxBytes || artifact.encoding !== "utf-8") throw new Error("Chrome returned invalid response evidence metadata");
29040
- const bytes = await client.blob(scopeId, artifact.sha256);
29041
- if (!bytes || bytes.length !== artifact.bytes || createHash3("sha256").update(bytes).digest("hex") !== artifact.sha256) throw new Error("Chrome response evidence is missing or failed its integrity check");
29042
- return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
29043
- }
29044
- if (value.body == null) return null;
29045
- if (typeof value.body !== "string" || Buffer.byteLength(value.body, "utf8") > maxBytes) throw new Error("Chrome did not return complete response evidence");
29046
- return value.body;
29047
- }
29048
- var init_browser_response = __esm({
29049
- "src/browser-response.ts"() {
29050
- "use strict";
29051
- }
29052
- });
29053
-
29054
- // src/timings.ts
29055
- import { appendFileSync } from "node:fs";
29056
- import { resolve } from "node:path";
29057
- function executionTimings() {
29058
- return { brokerMs: 0, bridgeMs: 0, registryMs: 0, bindingMs: 0, replayMs: 0, appMs: 0, proofMs: 0, reportingMs: 0 };
29059
- }
29060
- async function timed(timings, stage, work) {
29061
- const started = performance.now();
29062
- try {
29063
- return await work();
29064
- } finally {
29065
- if (timings) timings[stage] += performance.now() - started;
29066
- }
29067
- }
29068
- function saveTimings(timings, commandMs) {
29069
- if (!process.env.APILIFT_TIMING_FILE) return;
29070
- try {
29071
- appendFileSync(resolve(process.env.APILIFT_TIMING_FILE), JSON.stringify({ ...Object.fromEntries(Object.entries(timings).map(([key, value]) => [key, Math.round(value * 100) / 100])), commandMs: Math.round(commandMs * 100) / 100 }) + "\n", { mode: 384 });
29072
- } catch {
29073
- }
29074
- }
29075
- var init_timings = __esm({
29076
- "src/timings.ts"() {
29077
- "use strict";
29078
- }
29079
- });
29080
-
29081
- // src/files.ts
29082
- import { randomUUID } from "node:crypto";
29083
- import { accessSync, constants, existsSync, statSync, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
29084
- import { dirname, join as join2, resolve as resolve2 } from "node:path";
29085
- function outputDestination(file2) {
29086
- const target = resolve2(file2);
29087
- if (existsSync(target)) {
29088
- if (!statSync(target).isFile()) throw new Error("Output destination must be a regular file");
29089
- accessSync(target, constants.W_OK);
29090
- }
29091
- const probe = join2(dirname(target), `.apilift-output-check-${randomUUID()}`);
29092
- try {
29093
- writeFileSync2(probe, "", { flag: "wx", mode: 384 });
29094
- } finally {
29095
- try {
29096
- unlinkSync(probe);
29097
- } catch {
29098
- }
29099
- }
29100
- return target;
29101
- }
29102
- var init_files = __esm({
29103
- "src/files.ts"() {
29104
- "use strict";
29105
- }
29106
- });
29107
-
29108
- // src/execution-reports.ts
29109
- async function flushExecutionReports(api, state, budgetMs = 1e4) {
29110
- const pending = state.pendingExecutionReports();
29111
- if (!pending.length) return;
29112
- const controller = new AbortController();
29113
- let timer;
29114
- try {
29115
- const acknowledgment = await Promise.race([
29116
- api.call("postExecutions", { body: { reports: pending.map((item) => item.report) }, signal: controller.signal }),
29117
- new Promise((_, reject) => {
29118
- timer = setTimeout(() => {
29119
- controller.abort();
29120
- reject(new Error("Report delivery deferred"));
29121
- }, budgetMs);
29122
- })
29123
- ]);
29124
- state.acknowledgeExecutionReports(acknowledgment.results.map((item) => item.id));
29125
- } catch {
29126
- } finally {
29127
- clearTimeout(timer);
29128
- }
29129
- }
29130
- var init_execution_reports = __esm({
29131
- "src/execution-reports.ts"() {
29132
- "use strict";
29133
- }
29134
- });
29135
-
29136
28802
  // src/usage/ledger.ts
29137
- import { createHmac, randomBytes, randomUUID as randomUUID2 } from "node:crypto";
29138
- import { chmodSync as chmodSync2, mkdirSync as mkdirSync3, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
29139
- import { join as join3 } from "node:path";
28803
+ import { createHmac, randomBytes, randomUUID } from "node:crypto";
28804
+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync3, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
28805
+ import { join as join2 } from "node:path";
29140
28806
  import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
29141
28807
  function purposeFor(key) {
29142
28808
  return key.startsWith("discover") || key === "appsNew" || key === "appsUpdate" ? "discovery" : key.startsWith("setup") ? "setup" : "unknown";
@@ -29145,7 +28811,7 @@ function countedCommand(key) {
29145
28811
  return !["root", "version", "helpCommands", "appsSearch", "actionsList", "actionsSearch", "actionsShow", "transportsShow", "appsHistory", "contextShow"].includes(key);
29146
28812
  }
29147
28813
  function newInvocationId() {
29148
- return randomUUID2();
28814
+ return randomUUID();
29149
28815
  }
29150
28816
  var UsageLedger;
29151
28817
  var init_ledger = __esm({
@@ -29155,10 +28821,10 @@ var init_ledger = __esm({
29155
28821
  db;
29156
28822
  root;
29157
28823
  constructor(dataRoot2) {
29158
- this.root = join3(dataRoot2, "usage");
28824
+ this.root = join2(dataRoot2, "usage");
29159
28825
  mkdirSync3(this.root, { recursive: true, mode: 448 });
29160
28826
  chmodSync2(this.root, 448);
29161
- const file2 = join3(this.root, "ledger.sqlite");
28827
+ const file2 = join2(this.root, "ledger.sqlite");
29162
28828
  this.db = new DatabaseSync2(file2);
29163
28829
  chmodSync2(file2, 384);
29164
28830
  this.db.exec("PRAGMA busy_timeout=5000; PRAGMA journal_mode=WAL");
@@ -29182,8 +28848,8 @@ var init_ledger = __esm({
29182
28848
  return createHmac("sha256", this.secret()).update(JSON.stringify([runtime, nativeId])).digest("hex");
29183
28849
  }
29184
28850
  save(value) {
29185
- const path2 = join3(this.root, `pending-${value.id}.json`);
29186
- writeFileSync3(path2, JSON.stringify({ argv: value.argv, stdout: value.stdout, stderr: value.stderr }), { mode: 384, flag: "wx" });
28851
+ const path2 = join2(this.root, `pending-${value.id}.json`);
28852
+ writeFileSync2(path2, JSON.stringify({ argv: value.argv, stdout: value.stdout, stderr: value.stderr }), { mode: 384, flag: "wx" });
29187
28853
  this.db.prepare("INSERT INTO invocation (id,command_key,session_id,agent,started_at,finished_at,duration_ms,argv_json,output_path,purpose,execution_id) VALUES (?,?,?,?,?,?,?,?,?,?,?)").run(value.id, value.commandKey, value.session.id, value.session.agent, value.startedAt, value.finishedAt, value.durationMs, JSON.stringify(value.argv), path2, purposeFor(value.commandKey), value.executionId ?? null);
29188
28854
  }
29189
28855
  attachExecution(invocationId, executionId) {
@@ -29214,7 +28880,7 @@ var init_ledger = __esm({
29214
28880
  this.db.prepare("INSERT INTO usage_meta (key,value) VALUES ('account_id',?) ON CONFLICT(key) DO UPDATE SET value=excluded.value").run(accountId);
29215
28881
  }
29216
28882
  clearForRevocation() {
29217
- this.clearForAccountChange(`revoked:${randomUUID2()}`);
28883
+ this.clearForAccountChange(`revoked:${randomUUID()}`);
29218
28884
  this.db.prepare("DELETE FROM usage_meta WHERE key='account_id'").run();
29219
28885
  }
29220
28886
  prune() {
@@ -29236,9 +28902,9 @@ var init_ledger = __esm({
29236
28902
  });
29237
28903
 
29238
28904
  // src/usage/baselines.ts
29239
- import { randomUUID as randomUUID3 } from "node:crypto";
29240
- import { chmodSync as chmodSync3, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "node:fs";
29241
- import { join as join4 } from "node:path";
28905
+ import { randomUUID as randomUUID2 } from "node:crypto";
28906
+ import { chmodSync as chmodSync3, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "node:fs";
28907
+ import { join as join3 } from "node:path";
29242
28908
  function withBaselines(root, work) {
29243
28909
  try {
29244
28910
  const ledger = new UsageLedger(root);
@@ -29275,10 +28941,10 @@ var init_baselines = __esm({
29275
28941
  finishPreparation(sessionId, appSlug, appId, method, at = /* @__PURE__ */ new Date()) {
29276
28942
  const row = this.ledger.db.prepare("SELECT started_at FROM baseline_preparation_start WHERE session_id=? AND app_slug=?").get(sessionId, appSlug);
29277
28943
  if (!row || this.ledger.db.prepare("SELECT 1 FROM baseline_candidate WHERE draft_id=?").get(`preparation:${sessionId}:${appId}:${method}`)) return;
29278
- this.ledger.db.prepare("INSERT INTO baseline_candidate (id,draft_id,app_id,session_id,method,started_at,stopped_at) VALUES (?,?,?,?,?,?,?)").run(randomUUID3(), `preparation:${sessionId}:${appId}:${method}`, appId, sessionId, method, row.started_at, at.toISOString());
28944
+ this.ledger.db.prepare("INSERT INTO baseline_candidate (id,draft_id,app_id,session_id,method,started_at,stopped_at) VALUES (?,?,?,?,?,?,?)").run(randomUUID2(), `preparation:${sessionId}:${appId}:${method}`, appId, sessionId, method, row.started_at, at.toISOString());
29279
28945
  }
29280
28946
  startCapture(draftId, appId, sessionId, at = /* @__PURE__ */ new Date()) {
29281
- const id2 = randomUUID3();
28947
+ const id2 = randomUUID2();
29282
28948
  this.ledger.db.prepare("UPDATE baseline_candidate SET observation_finished_at=?,observation_reason='next-capture' WHERE session_id=? AND observation_version=1 AND observation_finished_at IS NULL").run(at.toISOString(), sessionId);
29283
28949
  this.ledger.db.prepare("INSERT INTO baseline_candidate (id,draft_id,app_id,session_id,method,started_at,observation_version) VALUES (?,?,?,?,?,?,1)").run(id2, draftId, appId, sessionId, "browser", at.toISOString());
29284
28950
  return id2;
@@ -29293,12 +28959,12 @@ var init_baselines = __esm({
29293
28959
  this.ledger.db.prepare("UPDATE baseline_candidate SET observation_finished_at=?,observation_reason=CASE WHEN stopped_at IS NULL THEN 'cancelled' ELSE 'released' END WHERE draft_id=? AND method='browser' AND observation_version=1 AND observation_finished_at IS NULL").run(at.toISOString(), draftId);
29294
28960
  }
29295
28961
  recordDirect(input2) {
29296
- const id2 = randomUUID3(), at = input2.at ?? /* @__PURE__ */ new Date();
29297
- const dir = join4(this.ledger.root, "pending-baselines");
28962
+ const id2 = randomUUID2(), at = input2.at ?? /* @__PURE__ */ new Date();
28963
+ const dir = join3(this.ledger.root, "pending-baselines");
29298
28964
  mkdirSync4(dir, { recursive: true, mode: 448 });
29299
28965
  chmodSync3(dir, 448);
29300
- const path2 = join4(dir, `${id2}.json`);
29301
- writeFileSync4(path2, JSON.stringify({ request: input2.request, response: input2.response }), { mode: 384, flag: "wx" });
28966
+ const path2 = join3(dir, `${id2}.json`);
28967
+ writeFileSync3(path2, JSON.stringify({ request: input2.request, response: input2.response }), { mode: 384, flag: "wx" });
29302
28968
  this.ledger.db.prepare("INSERT INTO baseline_candidate (id,draft_id,app_id,session_id,method,started_at,stopped_at,duration_ms,payload_path,coverage,time_coverage) VALUES (?,?,?,?,?,?,?,?,?,'final','final')").run(id2, input2.draftId, input2.appId, input2.sessionId, "direct", at.toISOString(), at.toISOString(), Math.max(0, Math.round(input2.durationMs)), path2);
29303
28969
  if (input2.invocationId) this.ledger.db.prepare("UPDATE baseline_candidate SET invocation_id=?,http_started_at=?,http_finished_at=? WHERE id=?").run(input2.invocationId, input2.startedAt ?? null, input2.finishedAt ?? null, id2);
29304
28970
  return id2;
@@ -30826,7 +30492,7 @@ function draftTemplate(kind) {
30826
30492
  ""
30827
30493
  ].join("\n") : "";
30828
30494
  const tabGuidance = ["action", "transport"].includes(kind) ? "// capture.tab is a selector returned by browser tabs; it selects the same signed-in Chrome tab for capture and discover run, including connection probes. Leave other sessions' tabs untouched.\n" : "";
30829
- const captureGuidance = kind === "action" ? '// Public descriptions explain behavior, consequences and limits; do not instruct consumers to repeat runtime proof or browser checks. Author verification is separate from consumer judgment.\n// Request JSON/query uses typed {"$input":"name"} bindings; URL segments use {input.name}. Resolve every {unresolved.N} marker from retained evidence before running.\n// Keep only invariant protocol values constant; selected app values are inputs, even when public. Generalize one action across its supported choices.\n// Before discover run, construct verification.inputs from the consumer contract and published value sources. Use a materially different selection and configuration from the captured or documented example, not just another label. Resolve missing configuration sources first; unexplained JSON is not a substitute.\n// Accept verification.review only when the result confirms that selection and configuration and consumers can obtain the required inputs.\n// A readback through POST requires proof.effect: "read" alongside proof.request; HTTP method alone does not determine semantic effect.\n// JSON object keys may use {input.name}, e.g. {"{input.attribute_id}":{"$input":"value"}}.\n// Discovery-only mutation proof compares a readback path with exactly one input, primary result path or internal JSON value: equals: [{"path":"/ready","value":true}]. Public literals require semantic review; fixed proof expectations are not caller flags.\n// Proof URLs bind returned identifiers with {result./id}; JSON bodies/query use {"$result":"/id"}. Readback pointers may use a complete {input.name} segment for a dynamic key; slashes and tildes in its value are escaped.\n// For one member of a nested list: select: {"path":"/ticket/fields","where":[{"path":"/id","result":"/id"}]}; equals paths then refer to the single matching member. Compare identity and meaningful changed fields, not entire objects.\n// Repair a failed response/proof with discover run --proof-only; it reuses the saved primary response and sends only the readback. Publication still needs a successful test and accepted semantic review.\n// For large collections, investigate native search/filter/detail routes before enumeration. Search saved results locally and inspect only matching records; preserve structured dynamic fields.\n// After selecting a captured request, discover capture auth runs a bounded default search without authField or authScope. Set either only to narrow an observed field or location; browser-managed cookies need no locator.\n' : "";
30495
+ const captureGuidance = kind === "action" ? '// Public descriptions explain behavior, consequences and limits; do not instruct consumers to repeat runtime proof or browser checks. Author verification is separate from consumer judgment.\n// Request JSON/query uses typed {"$input":"name"} bindings; URL segments use {input.name}. Resolve every {unresolved.N} marker from retained evidence before running.\n// App-owned input source annotation: {"x-source":{"action":"ticket.list","path":"/*/id"}}. Run the source explicitly; it never runs implicitly.\n// Keep only invariant protocol values constant; selected app values are inputs, even when public. Generalize one action across its supported choices.\n// Before discover run, construct verification.inputs from the consumer contract and published value sources. Use a materially different selection and configuration from the captured or documented example, not just another label. Resolve missing configuration sources first; unexplained JSON is not a substitute.\n// Accept verification.review only when the result confirms that selection and configuration and consumers can obtain the required inputs.\n// A readback through POST requires proof.effect: "read" alongside proof.request; HTTP method alone does not determine semantic effect.\n// JSON object keys may use {input.name}, e.g. {"{input.attribute_id}":{"$input":"value"}}.\n// Discovery-only mutation proof compares a readback path with exactly one input, primary result path or internal JSON value: equals: [{"path":"/ready","value":true}]. Public literals require semantic review; fixed proof expectations are not caller flags.\n// Proof URLs bind returned identifiers with {result./id}; JSON bodies/query use {"$result":"/id"}. Readback pointers may use a complete {input.name} segment for a dynamic key; slashes and tildes in its value are escaped.\n// For one member of a nested list: select: {"path":"/ticket/fields","where":[{"path":"/id","result":"/id"}]}; equals paths then refer to the single matching member. Compare identity and meaningful changed fields, not entire objects.\n// For valid readback variants, keep identity equals and add proof.response: {path:"/state",schema:{enum:["ready","empty"]}}, relative to the selected member. Exclude pending states; use proof-only reads after completion.\n// Repair a failed response/proof with discover run --proof-only; it reuses the saved primary response and sends only the readback. Publication still needs a successful test and accepted semantic review.\n// For large collections, investigate native search/filter/detail routes before enumeration. Search saved results locally and inspect only matching records; preserve structured dynamic fields.\n// After selecting a captured request, discover capture auth runs a bounded default search without authField or authScope. Set either only to narrow an observed field or location; browser-managed cookies need no locator.\n' : "";
30830
30496
  return `${head}${probeGuidance}${tabGuidance}${captureGuidance}${JSON.stringify(templates[kind], null, 2)}
30831
30497
  `;
30832
30498
  }
@@ -30844,13 +30510,13 @@ var init_envelope = __esm({
30844
30510
  });
30845
30511
 
30846
30512
  // src/discovery/drafts.ts
30847
- import { randomUUID as randomUUID4 } from "node:crypto";
30848
- import { chmodSync as chmodSync4, existsSync as existsSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync4, renameSync, rmSync as rmSync3, rmdirSync, writeFileSync as writeFileSync5 } from "node:fs";
30849
- import { basename, dirname as dirname2, join as join5 } from "node:path";
30513
+ import { randomUUID as randomUUID3 } from "node:crypto";
30514
+ import { chmodSync as chmodSync4, existsSync, mkdirSync as mkdirSync5, readFileSync as readFileSync3, renameSync, rmSync as rmSync3, rmdirSync, writeFileSync as writeFileSync4 } from "node:fs";
30515
+ import { basename, dirname, join as join4 } from "node:path";
30850
30516
  function pathFor(root, appSlug, kind, name) {
30851
30517
  if (!slugPattern.test(appSlug)) throw new Error("App slug is invalid");
30852
30518
  if (kind === "action" ? !actionPattern.test(name) : kind !== "app" && !resourcePattern.test(name)) throw new Error("Resource name is invalid");
30853
- return kind === "app" ? join5(root, "drafts", diskName(appSlug), "app.jsonc") : join5(root, "drafts", diskName(appSlug), paths[kind], `${diskName(name)}.jsonc`);
30519
+ return kind === "app" ? join4(root, "drafts", diskName(appSlug), "app.jsonc") : join4(root, "drafts", diskName(appSlug), paths[kind], `${diskName(name)}.jsonc`);
30854
30520
  }
30855
30521
  function record2(row) {
30856
30522
  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) };
@@ -30917,12 +30583,12 @@ var init_drafts = __esm({
30917
30583
  }
30918
30584
  const previousSessions = this.state.db.prepare("SELECT session_id FROM discovery_session WHERE app_slug=? AND app_id IS NOT ?").all(appSlug, appId);
30919
30585
  const changed = stale.length > 0 || previousSessions.length > 0;
30920
- const archivedDrafts = changed ? join5(this.root, "retired-drafts", randomUUID4()) : void 0;
30586
+ const archivedDrafts = changed ? join4(this.root, "retired-drafts", randomUUID3()) : void 0;
30921
30587
  if (archivedDrafts) mkdirSync5(archivedDrafts, { recursive: true, mode: 448 });
30922
30588
  for (const draft of stale) {
30923
- if (existsSync2(draft.filePath)) {
30924
- const archived = join5(archivedDrafts, paths[draft.kind], basename(draft.filePath));
30925
- mkdirSync5(dirname2(archived), { recursive: true, mode: 448 });
30589
+ if (existsSync(draft.filePath)) {
30590
+ const archived = join4(archivedDrafts, paths[draft.kind], basename(draft.filePath));
30591
+ mkdirSync5(dirname(archived), { recursive: true, mode: 448 });
30926
30592
  renameSync(draft.filePath, archived);
30927
30593
  moved.push({ from: draft.filePath, to: archived });
30928
30594
  }
@@ -30933,9 +30599,9 @@ var init_drafts = __esm({
30933
30599
  }
30934
30600
  if (changed && this.hasTable("discovery_research")) {
30935
30601
  const research = this.state.db.prepare("SELECT file_path FROM discovery_research WHERE app_slug=?").all(appSlug);
30936
- for (const row of research) if (existsSync2(row.file_path)) {
30937
- const archived = join5(archivedDrafts, "api", basename(row.file_path));
30938
- mkdirSync5(dirname2(archived), { recursive: true, mode: 448 });
30602
+ for (const row of research) if (existsSync(row.file_path)) {
30603
+ const archived = join4(archivedDrafts, "api", basename(row.file_path));
30604
+ mkdirSync5(dirname(archived), { recursive: true, mode: 448 });
30939
30605
  renameSync(row.file_path, archived);
30940
30606
  moved.push({ from: row.file_path, to: archived });
30941
30607
  }
@@ -30998,7 +30664,7 @@ var init_drafts = __esm({
30998
30664
  const serialized = JSON.stringify(body);
30999
30665
  const existing = this.publication(draftId);
31000
30666
  if (existing) return existing;
31001
- const intent = { requestId: randomUUID4(), requestBody: serialized };
30667
+ const intent = { requestId: randomUUID3(), requestBody: serialized };
31002
30668
  this.state.db.prepare("INSERT INTO discovery_publication (draft_id,request_id,request_body) VALUES (?,?,?)").run(draftId, intent.requestId, serialized);
31003
30669
  return intent;
31004
30670
  }
@@ -31035,9 +30701,9 @@ var init_drafts = __esm({
31035
30701
  this.state.db.prepare("INSERT INTO discovery_request_attempt (draft_id,fingerprint,state,response_path,response_digest) VALUES (?,?,?,?,?) ON CONFLICT(draft_id,fingerprint) DO UPDATE SET state=excluded.state,response_path=CASE WHEN excluded.state='possibly-dispatched' AND excluded.response_path IS NULL THEN NULL ELSE COALESCE(excluded.response_path,discovery_request_attempt.response_path) END,response_digest=CASE WHEN excluded.state='possibly-dispatched' AND excluded.response_path IS NULL THEN NULL ELSE COALESCE(excluded.response_digest,discovery_request_attempt.response_digest) END").run(draftId, fingerprint2, state, responsePath ?? null, responseDigest ?? null);
31036
30702
  }
31037
30703
  artifactPath(draftId, name) {
31038
- const directory = join5(this.root, "draft-evidence", draftId, randomUUID4());
30704
+ const directory = join4(this.root, "draft-evidence", draftId, randomUUID3());
31039
30705
  mkdirSync5(directory, { recursive: true, mode: 448 });
31040
- return join5(directory, name);
30706
+ return join4(directory, name);
31041
30707
  }
31042
30708
  finished(sessionId) {
31043
30709
  const row = this.state.db.prepare("SELECT result_json FROM discovery_finished WHERE session_id=?").get(sessionId);
@@ -31048,7 +30714,7 @@ var init_drafts = __esm({
31048
30714
  if (!draft) return null;
31049
30715
  if (publishedResult === void 0 && this.publication(draft.id)) throw new DraftConflict("selected-draft", "A publication request may have been sent; reconcile it before discarding this draft");
31050
30716
  withBaselines(this.root, (baselines) => baselines.closeObservation(draft.id, "released"));
31051
- const paths2 = [draft.filePath, `${draft.filePath}.current.json`, join5(this.root, "draft-evidence", draft.id)];
30717
+ const paths2 = [draft.filePath, `${draft.filePath}.current.json`, join4(this.root, "draft-evidence", draft.id)];
31052
30718
  const evidence = this.evidence(draft.id);
31053
30719
  if (evidence) paths2.push(evidence.resultPath);
31054
30720
  if (this.hasTable("discovery_request_attempt")) for (const row of this.state.db.prepare("SELECT response_path FROM discovery_request_attempt WHERE draft_id=? AND response_path IS NOT NULL").all(draft.id)) paths2.push(row.response_path);
@@ -31079,12 +30745,12 @@ var init_drafts = __esm({
31079
30745
  async cleanup(closeScope) {
31080
30746
  for (const row of this.state.db.prepare("SELECT * FROM discovery_cleanup").all()) {
31081
30747
  for (const path2 of new Set(JSON.parse(row.paths_json))) {
31082
- rmSync3(path2, { force: true, recursive: path2 === join5(this.root, "draft-evidence", row.draft_id) });
31083
- let parent = dirname2(path2);
31084
- while (parent !== this.root && parent.startsWith(`${join5(this.root, "drafts")}/`)) {
30748
+ rmSync3(path2, { force: true, recursive: path2 === join4(this.root, "draft-evidence", row.draft_id) });
30749
+ let parent = dirname(path2);
30750
+ while (parent !== this.root && parent.startsWith(`${join4(this.root, "drafts")}/`)) {
31085
30751
  try {
31086
30752
  rmdirSync(parent);
31087
- parent = dirname2(parent);
30753
+ parent = dirname(parent);
31088
30754
  } catch {
31089
30755
  break;
31090
30756
  }
@@ -31110,13 +30776,13 @@ var init_drafts = __esm({
31110
30776
  if (existing?.owner_session_id && existing.owner_session_id !== input2.sessionId) throw new DraftConflict("owned-draft", `${input2.kind} ${name} is owned by another agent session; its file was preserved`);
31111
30777
  if (existing && existing.app_id !== (input2.appId ?? null)) throw new DraftConflict("selected-draft", "The retained draft belongs to a different app; reopen the app from its current registry identity before editing");
31112
30778
  if (existing) filePath = String(existing.file_path);
31113
- const id2 = existing ? String(existing.id) : randomUUID4();
30779
+ const id2 = existing ? String(existing.id) : randomUUID3();
31114
30780
  if (!existing) this.state.db.prepare("INSERT INTO discovery_draft (id,app_slug,app_id,kind,name,owner_session_id,file_path,base_version) VALUES (?,?,?,?,?,?,?,?)").run(id2, input2.appSlug, input2.appId ?? null, input2.kind, name, input2.sessionId, filePath, input2.baseVersion ?? null);
31115
30781
  else this.state.db.prepare("UPDATE discovery_draft SET owner_session_id=? WHERE id=?").run(input2.sessionId, id2);
31116
30782
  this.state.db.prepare("DELETE FROM discovery_deferred WHERE draft_id=?").run(id2);
31117
30783
  this.state.db.prepare("INSERT INTO selected_discovery_draft (session_id,draft_id) VALUES (?,?) ON CONFLICT(session_id) DO UPDATE SET draft_id=excluded.draft_id").run(input2.sessionId, id2);
31118
- mkdirSync5(dirname2(filePath), { recursive: true, mode: 448 });
31119
- if (!existsSync2(filePath) && existing?.intent !== "remove") writeFileSync5(filePath, draftTemplate(input2.kind), { flag: "wx", mode: 384 });
30784
+ mkdirSync5(dirname(filePath), { recursive: true, mode: 448 });
30785
+ if (!existsSync(filePath) && existing?.intent !== "remove") writeFileSync4(filePath, draftTemplate(input2.kind), { flag: "wx", mode: 384 });
31120
30786
  chmodSync4(filePath, 384);
31121
30787
  const result3 = this.selected(input2.sessionId);
31122
30788
  this.state.db.exec("COMMIT");
@@ -31142,11 +30808,11 @@ var init_drafts = __esm({
31142
30808
  this.state.db.exec("COMMIT");
31143
30809
  return this.selected(input2.sessionId);
31144
30810
  }
31145
- const id2 = randomUUID4();
30811
+ const id2 = randomUUID3();
31146
30812
  this.state.db.prepare("INSERT INTO discovery_draft (id,app_slug,app_id,kind,name,owner_session_id,file_path,base_version,intent) VALUES (?,?,?,?,?,?,?,?,'remove')").run(id2, input2.appSlug, input2.appId, input2.kind, input2.name, input2.sessionId, filePath, input2.baseVersion);
31147
30813
  this.state.db.prepare("INSERT INTO selected_discovery_draft (session_id,draft_id) VALUES (?,?)").run(input2.sessionId, id2);
31148
- mkdirSync5(dirname2(filePath), { recursive: true, mode: 448 });
31149
- writeFileSync5(filePath, `// Publish to remove this resource. No records inside the app are changed.
30814
+ mkdirSync5(dirname(filePath), { recursive: true, mode: 448 });
30815
+ writeFileSync4(filePath, `// Publish to remove this resource. No records inside the app are changed.
31150
30816
  ${JSON.stringify({ remove: true, expectedVersion: input2.baseVersion }, null, 2)}
31151
30817
  `, { flag: "wx", mode: 384 });
31152
30818
  this.state.db.exec("COMMIT");
@@ -31156,31 +30822,367 @@ ${JSON.stringify({ remove: true, expectedVersion: input2.baseVersion }, null, 2)
31156
30822
  throw error62;
31157
30823
  }
31158
30824
  }
31159
- defer(sessionId) {
31160
- this.state.db.exec("BEGIN IMMEDIATE");
31161
- try {
31162
- const draft = this.selected(sessionId);
31163
- if (draft) {
31164
- withBaselines(this.root, (baselines) => baselines.closeObservation(draft.id, "released"));
31165
- this.state.db.prepare("INSERT INTO discovery_deferred (draft_id,session_id) VALUES (?,?) ON CONFLICT(draft_id) DO UPDATE SET session_id=excluded.session_id").run(draft.id, sessionId);
31166
- this.state.db.prepare("DELETE FROM selected_discovery_draft WHERE session_id=?").run(sessionId);
31167
- this.state.db.prepare("UPDATE discovery_draft SET owner_session_id=NULL WHERE id=? AND owner_session_id=?").run(draft.id, sessionId);
31168
- }
31169
- this.state.db.exec("COMMIT");
31170
- return draft;
31171
- } catch (error62) {
31172
- this.state.db.exec("ROLLBACK");
31173
- throw error62;
30825
+ defer(sessionId) {
30826
+ this.state.db.exec("BEGIN IMMEDIATE");
30827
+ try {
30828
+ const draft = this.selected(sessionId);
30829
+ if (draft) {
30830
+ withBaselines(this.root, (baselines) => baselines.closeObservation(draft.id, "released"));
30831
+ this.state.db.prepare("INSERT INTO discovery_deferred (draft_id,session_id) VALUES (?,?) ON CONFLICT(draft_id) DO UPDATE SET session_id=excluded.session_id").run(draft.id, sessionId);
30832
+ this.state.db.prepare("DELETE FROM selected_discovery_draft WHERE session_id=?").run(sessionId);
30833
+ this.state.db.prepare("UPDATE discovery_draft SET owner_session_id=NULL WHERE id=? AND owner_session_id=?").run(draft.id, sessionId);
30834
+ }
30835
+ this.state.db.exec("COMMIT");
30836
+ return draft;
30837
+ } catch (error62) {
30838
+ this.state.db.exec("ROLLBACK");
30839
+ throw error62;
30840
+ }
30841
+ }
30842
+ readSelected(sessionId) {
30843
+ const draft = this.selected(sessionId);
30844
+ return draft ? { draft, source: readFileSync3(draft.filePath, "utf8") } : null;
30845
+ }
30846
+ };
30847
+ }
30848
+ });
30849
+
30850
+ // src/version.ts
30851
+ import { readFileSync as readFileSync4 } from "node:fs";
30852
+ var cliVersion;
30853
+ var init_version = __esm({
30854
+ "src/version.ts"() {
30855
+ "use strict";
30856
+ cliVersion = JSON.parse(readFileSync4(new URL("../package.json", import.meta.url), "utf8")).version;
30857
+ }
30858
+ });
30859
+
30860
+ // src/api.ts
30861
+ function retryDelay(response, attempt) {
30862
+ const value = response.headers.get("retry-after");
30863
+ if (value) {
30864
+ const seconds = Number(value);
30865
+ if (Number.isFinite(seconds) && seconds >= 0) return Math.min(seconds * 1e3, 5e3);
30866
+ const date5 = Date.parse(value);
30867
+ if (Number.isFinite(date5)) return Math.min(Math.max(date5 - Date.now(), 0), 5e3);
30868
+ }
30869
+ return 250 * 2 ** attempt;
30870
+ }
30871
+ function sleep(ms) {
30872
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
30873
+ }
30874
+ function createCliApi(baseUrl, fetcher = fetch, token, sessionId) {
30875
+ const transport = async (input2, init) => {
30876
+ const method = init?.method ?? "GET";
30877
+ const headers = new Headers(init?.headers);
30878
+ headers.set("Apilift-Capabilities", "distributed-rollouts-v1,discovery-proof-v2,dynamic-bindings-v1");
30879
+ headers.set("Apilift-Client", `cli/${cliVersion}`);
30880
+ if (token) headers.set("Authorization", `Bearer ${token}`);
30881
+ if (sessionId) headers.set("Apilift-Session", sessionId);
30882
+ const path2 = new URL(input2 instanceof Request ? input2.url : String(input2), baseUrl).pathname;
30883
+ const timeoutMs = method === "POST" && path2 === "/api/v1/installations" ? 3e4 : 1e4;
30884
+ for (let attempt = 0; ; attempt++) {
30885
+ try {
30886
+ const response = await fetcher(input2, { ...init, headers, signal: init?.signal ?? AbortSignal.timeout(timeoutMs) });
30887
+ if (method !== "GET" || attempt >= 2 || !retryStatuses.has(response.status)) {
30888
+ if (response.status !== 204 && !response.headers.get("content-type")?.toLowerCase().includes("json")) throw new ApiClientError(response.status, null);
30889
+ if (response.status !== 204) {
30890
+ try {
30891
+ await response.clone().json();
30892
+ } catch {
30893
+ throw new ApiClientError(response.status, null);
30894
+ }
30895
+ }
30896
+ return response;
30897
+ }
30898
+ await sleep(retryDelay(response, attempt));
30899
+ } catch (error62) {
30900
+ if (error62 instanceof ApiClientError) throw error62;
30901
+ if (method !== "GET") throw new ApiDeliveryUncertainError(error62);
30902
+ if (attempt >= 2) throw new ApiReadUnavailableError(error62);
30903
+ await sleep(250 * 2 ** attempt);
30904
+ }
30905
+ }
30906
+ };
30907
+ return createApiClient(baseUrl, transport);
30908
+ }
30909
+ var retryStatuses, ApiDeliveryUncertainError, ApiReadUnavailableError;
30910
+ var init_api3 = __esm({
30911
+ "src/api.ts"() {
30912
+ "use strict";
30913
+ init_version();
30914
+ init_api2();
30915
+ retryStatuses = /* @__PURE__ */ new Set([408, 429, 502, 503, 504]);
30916
+ ApiDeliveryUncertainError = class extends Error {
30917
+ constructor(cause) {
30918
+ super("The Registry did not confirm whether the mutation was applied");
30919
+ this.cause = cause;
30920
+ }
30921
+ };
30922
+ ApiReadUnavailableError = class extends Error {
30923
+ constructor(cause) {
30924
+ super("The Registry could not be reached after three attempts");
30925
+ this.cause = cause;
30926
+ }
30927
+ };
30928
+ }
30929
+ });
30930
+
30931
+ // src/commands/registry.ts
30932
+ function parseInvocation(argv) {
30933
+ const issues = [];
30934
+ let cursor = 0;
30935
+ let sessionLabel;
30936
+ let version2 = false;
30937
+ while (cursor < argv.length) {
30938
+ const token = argv[cursor];
30939
+ if (token === "--session") {
30940
+ const value = argv[cursor + 1];
30941
+ if (!value || value.startsWith("--")) issues.push("--session needs a worker label");
30942
+ else if (sessionLabel) issues.push("--session may appear only once");
30943
+ else sessionLabel = value;
30944
+ cursor += value && !value.startsWith("--") ? 2 : 1;
30945
+ } else if (token === "--version") {
30946
+ version2 = true;
30947
+ cursor++;
30948
+ } else break;
30949
+ }
30950
+ if (sessionLabel && (sessionLabel.length > 80 || !/^[A-Za-z0-9_-]+$/u.test(sessionLabel))) issues.push("--session must be 1 to 80 letters, digits, hyphens or underscores");
30951
+ const base2 = { sessionLabel, positionals: [], options: {}, flags: [], repeats: {}, actionArgs: [] };
30952
+ if (version2) {
30953
+ if (cursor < argv.length) issues.push("--version takes no command");
30954
+ return { ...issues.length ? {} : { invocation: { key: "version", ...base2 } }, issues };
30955
+ }
30956
+ const remaining = argv.slice(cursor);
30957
+ if (remaining.length === 1 && remaining[0] === "--help") return { invocation: { key: "helpCommands", ...base2 }, issues };
30958
+ const selected = Object.entries(commandRegistry).filter(([, command7]) => command7.path.every((part, index) => remaining[index] === part)).sort((left, right) => right[1].path.length - left[1].path.length)[0];
30959
+ if (!selected) return { issues: [`Unknown command: ${remaining.slice(0, 2).join(" ")}`] };
30960
+ const [key, command6] = selected;
30961
+ const positionals = [];
30962
+ const options2 = {};
30963
+ const flags = [];
30964
+ const repeats = {};
30965
+ const actionArgs = [];
30966
+ for (let index = command6.path.length; index < remaining.length; index++) {
30967
+ const token = remaining[index];
30968
+ if (token.startsWith("--")) {
30969
+ const name = token.slice(2);
30970
+ if (name === "session") {
30971
+ issues.push("--session must appear before the command");
30972
+ continue;
30973
+ }
30974
+ if (command6.values?.includes(name) || command6.repeat?.includes(name)) {
30975
+ const value = remaining[index + 1];
30976
+ if (!value || value.startsWith("--")) {
30977
+ issues.push(`--${name} needs a value`);
30978
+ continue;
31174
30979
  }
31175
- }
31176
- readSelected(sessionId) {
31177
- const draft = this.selected(sessionId);
31178
- return draft ? { draft, source: readFileSync4(draft.filePath, "utf8") } : null;
31179
- }
30980
+ if (command6.repeat?.includes(name)) (repeats[name] ??= []).push(value);
30981
+ else if (Object.hasOwn(options2, name)) issues.push(`--${name} may appear only once`);
30982
+ else options2[name] = value;
30983
+ index++;
30984
+ } else if (command6.flags?.includes(name)) {
30985
+ if (flags.includes(name)) issues.push(`--${name} may appear only once`);
30986
+ else flags.push(name);
30987
+ } else if (command6.actionFlags && positionals.length >= 2) actionArgs.push(token);
30988
+ else issues.push(`Unknown option --${name}`);
30989
+ } else if (command6.actionFlags && positionals.length >= 2 && actionArgs.length) actionArgs.push(token);
30990
+ else if (positionals.length < command6.max) positionals.push(token);
30991
+ else issues.push(`Unexpected argument ${token}`);
30992
+ }
30993
+ if (positionals.length < command6.min) issues.push(`${command6.path.join(" ") || "command"} needs ${command6.min} argument${command6.min === 1 ? "" : "s"}`);
30994
+ return { ...issues.length ? {} : { invocation: { key, sessionLabel, positionals, options: options2, flags, repeats, actionArgs } }, issues };
30995
+ }
30996
+ var commandRegistry;
30997
+ var init_registry = __esm({
30998
+ "src/commands/registry.ts"() {
30999
+ "use strict";
31000
+ commandRegistry = {
31001
+ root: { path: [], min: 0, max: 0 },
31002
+ setup: { path: ["setup"], min: 0, max: 0, values: ["code"] },
31003
+ setupContinue: { path: ["setup", "continue"], min: 0, max: 0 },
31004
+ doctor: { path: ["doctor"], min: 0, max: 0, values: ["app"] },
31005
+ update: { path: ["update"], min: 0, max: 0 },
31006
+ helpCommands: { path: ["help", "commands"], min: 0, max: 0 },
31007
+ appsSearch: { path: ["apps", "search"], min: 1, max: 1, values: ["domain", "limit", "cursor"] },
31008
+ appsUse: { path: ["apps", "use"], min: 1, max: 1 },
31009
+ appsRelease: { path: ["apps", "release"], min: 1, max: 1 },
31010
+ appsRefresh: { path: ["apps", "refresh"], min: 1, max: 1, values: ["action"] },
31011
+ appsNew: { path: ["apps", "new"], min: 1, max: 1 },
31012
+ appsUpdate: { path: ["apps", "update"], min: 1, max: 1 },
31013
+ appsHistory: { path: ["apps", "history"], min: 1, max: 1, values: ["resource", "limit", "cursor"] },
31014
+ actionsList: { path: ["actions", "list"], min: 1, max: 1, values: ["entity", "limit", "cursor"], flags: ["all"] },
31015
+ actionsSearch: { path: ["actions", "search"], min: 2, max: 2, values: ["entity", "limit", "cursor"] },
31016
+ actionsShow: { path: ["actions", "show"], min: 2, max: 11, values: ["input-template"], flags: ["schema"] },
31017
+ run: { path: ["run"], min: 2, max: 2, values: ["input", "output"], flags: ["check", "no-context"], repeat: ["select"], actionFlags: true },
31018
+ credentialsList: { path: ["credentials", "list"], min: 1, max: 2 },
31019
+ credentialsUse: { path: ["credentials", "use"], min: 3, max: 3 },
31020
+ credentialsAdd: { path: ["credentials", "add"], min: 2, max: 2 },
31021
+ credentialsRemove: { path: ["credentials", "remove"], min: 3, max: 3 },
31022
+ credentialsUnbound: { path: ["credentials", "unbound"], min: 0, max: 0 },
31023
+ credentialsAttach: { path: ["credentials", "attach"], min: 3, max: 3 },
31024
+ transportsShow: { path: ["transports", "show"], min: 1, max: 2 },
31025
+ browserCheck: { path: ["browser", "check"], min: 1, max: 1 },
31026
+ browserTabs: { path: ["browser", "tabs"], min: 1, max: 1, values: ["select"] },
31027
+ contextSet: { path: ["context", "set"], min: 2, max: 2, values: ["app"] },
31028
+ contextShow: { path: ["context", "show"], min: 0, max: 0, values: ["app"] },
31029
+ contextUnset: { path: ["context", "unset"], min: 1, max: 1, values: ["app"] },
31030
+ contextClear: { path: ["context", "clear"], min: 0, max: 0, values: ["app"] },
31031
+ discoverDraft: { path: ["discover", "draft"], min: 1, max: 2 },
31032
+ discoverCheck: { path: ["discover", "check"], min: 0, max: 0 },
31033
+ discoverRun: { path: ["discover", "run"], min: 0, max: 0, flags: ["proof-only"] },
31034
+ discoverPublish: { path: ["discover", "publish"], min: 0, max: 0 },
31035
+ discoverStatus: { path: ["discover", "status"], min: 0, max: 0 },
31036
+ discoverDefer: { path: ["discover", "defer"], min: 0, max: 0 },
31037
+ discoverDiscard: { path: ["discover", "discard"], min: 0, max: 0 },
31038
+ discoverRebase: { path: ["discover", "rebase"], min: 0, max: 0 },
31039
+ discoverRemove: { path: ["discover", "remove"], min: 2, max: 2 },
31040
+ discoverClose: { path: ["discover", "close"], min: 0, max: 0 },
31041
+ discoverApiAssess: { path: ["discover", "api", "assess"], min: 1, max: 1 },
31042
+ discoverApiSave: { path: ["discover", "api", "save"], min: 0, max: 0 },
31043
+ discoverApiStatus: { path: ["discover", "api", "status"], min: 0, max: 1 },
31044
+ discoverCaptureStart: { path: ["discover", "capture", "start"], min: 0, max: 0 },
31045
+ discoverCaptureStop: { path: ["discover", "capture", "stop"], min: 0, max: 0 },
31046
+ discoverCaptureList: { path: ["discover", "capture", "list"], min: 0, max: 0, values: ["limit", "cursor"] },
31047
+ discoverCaptureShow: { path: ["discover", "capture", "show"], min: 1, max: 1, values: ["part", "pointer"] },
31048
+ discoverCaptureSelect: { path: ["discover", "capture", "select"], min: 1, max: 1 },
31049
+ discoverCaptureAuth: { path: ["discover", "capture", "auth"], min: 1, max: 1 },
31050
+ discoverCaptureCancel: { path: ["discover", "capture", "cancel"], min: 0, max: 0 }
31180
31051
  };
31181
31052
  }
31182
31053
  });
31183
31054
 
31055
+ // src/session.ts
31056
+ import { createHash as createHash2 } from "node:crypto";
31057
+ function apiSessionId(installationId, session) {
31058
+ if (!session) return void 0;
31059
+ const hex3 = createHash2("sha256").update("apilift-api-session/v1\0").update(installationId).update("\0").update(session.id).digest("hex");
31060
+ return `${hex3.slice(0, 8)}-${hex3.slice(8, 12)}-5${hex3.slice(13, 16)}-a${hex3.slice(17, 20)}-${hex3.slice(20, 32)}`;
31061
+ }
31062
+ function agentSession(env, explicitLabel) {
31063
+ explicitLabel ??= env.APILIFT_SESSION;
31064
+ if (explicitLabel !== void 0 && !/^[A-Za-z0-9_-]{1,80}$/u.test(explicitLabel)) return null;
31065
+ const codex = env.CODEX_THREAD_ID || env.CODEX_SESSION_ID;
31066
+ const claude = env.CLAUDE_CODE_SESSION_ID;
31067
+ if (explicitLabel) {
31068
+ const agent2 = claude || env.CLAUDECODE ? "claude" : codex || env.CODEX_HOME ? "codex" : null;
31069
+ return agent2 ? { id: `${agent2}:label:${explicitLabel}`, agent: agent2 } : null;
31070
+ }
31071
+ if (codex && !claude) return { id: `codex:${codex}`, agent: "codex" };
31072
+ if (claude && !codex) return { id: `claude:${claude}`, agent: "claude" };
31073
+ return null;
31074
+ }
31075
+ function commandPrefix(label2) {
31076
+ return `apilift${label2 ? ` --session ${label2}` : ""}`;
31077
+ }
31078
+ var init_session = __esm({
31079
+ "src/session.ts"() {
31080
+ "use strict";
31081
+ }
31082
+ });
31083
+
31084
+ // src/browser-response.ts
31085
+ import { createHash as createHash3 } from "node:crypto";
31086
+ async function browserResponseBody(client, scopeId, value, maxBytes = 48 * 1024 * 1024) {
31087
+ if (value.bodyArtifact !== void 0) {
31088
+ const artifact = value.bodyArtifact;
31089
+ if (value.body != null || !artifact || typeof artifact.sha256 !== "string" || !/^[0-9a-f]{64}$/.test(artifact.sha256) || !Number.isInteger(artifact.bytes) || Number(artifact.bytes) < 1 || Number(artifact.bytes) > maxBytes || artifact.encoding !== "utf-8") throw new Error("Chrome returned invalid response evidence metadata");
31090
+ const bytes = await client.blob(scopeId, artifact.sha256);
31091
+ if (!bytes || bytes.length !== artifact.bytes || createHash3("sha256").update(bytes).digest("hex") !== artifact.sha256) throw new Error("Chrome response evidence is missing or failed its integrity check");
31092
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
31093
+ }
31094
+ if (value.body == null) return null;
31095
+ if (typeof value.body !== "string" || Buffer.byteLength(value.body, "utf8") > maxBytes) throw new Error("Chrome did not return complete response evidence");
31096
+ return value.body;
31097
+ }
31098
+ var init_browser_response = __esm({
31099
+ "src/browser-response.ts"() {
31100
+ "use strict";
31101
+ }
31102
+ });
31103
+
31104
+ // src/timings.ts
31105
+ import { appendFileSync } from "node:fs";
31106
+ import { resolve } from "node:path";
31107
+ function executionTimings() {
31108
+ return { brokerMs: 0, bridgeMs: 0, registryMs: 0, bindingMs: 0, replayMs: 0, appMs: 0, proofMs: 0, reportingMs: 0 };
31109
+ }
31110
+ async function timed(timings, stage, work) {
31111
+ const started = performance.now();
31112
+ try {
31113
+ return await work();
31114
+ } finally {
31115
+ if (timings) timings[stage] += performance.now() - started;
31116
+ }
31117
+ }
31118
+ function saveTimings(timings, commandMs) {
31119
+ if (!process.env.APILIFT_TIMING_FILE) return;
31120
+ try {
31121
+ appendFileSync(resolve(process.env.APILIFT_TIMING_FILE), JSON.stringify({ ...Object.fromEntries(Object.entries(timings).map(([key, value]) => [key, Math.round(value * 100) / 100])), commandMs: Math.round(commandMs * 100) / 100 }) + "\n", { mode: 384 });
31122
+ } catch {
31123
+ }
31124
+ }
31125
+ var init_timings = __esm({
31126
+ "src/timings.ts"() {
31127
+ "use strict";
31128
+ }
31129
+ });
31130
+
31131
+ // src/files.ts
31132
+ import { randomUUID as randomUUID4 } from "node:crypto";
31133
+ import { accessSync, constants, existsSync as existsSync2, statSync, unlinkSync, writeFileSync as writeFileSync5 } from "node:fs";
31134
+ import { dirname as dirname2, join as join5, resolve as resolve2 } from "node:path";
31135
+ function outputDestination(file2) {
31136
+ const target = resolve2(file2);
31137
+ if (existsSync2(target)) {
31138
+ if (!statSync(target).isFile()) throw new Error("Output destination must be a regular file");
31139
+ accessSync(target, constants.W_OK);
31140
+ }
31141
+ const probe = join5(dirname2(target), `.apilift-output-check-${randomUUID4()}`);
31142
+ try {
31143
+ writeFileSync5(probe, "", { flag: "wx", mode: 384 });
31144
+ } finally {
31145
+ try {
31146
+ unlinkSync(probe);
31147
+ } catch {
31148
+ }
31149
+ }
31150
+ return target;
31151
+ }
31152
+ var init_files = __esm({
31153
+ "src/files.ts"() {
31154
+ "use strict";
31155
+ }
31156
+ });
31157
+
31158
+ // src/execution-reports.ts
31159
+ async function flushExecutionReports(api, state, budgetMs = 1e4) {
31160
+ const pending = state.pendingExecutionReports();
31161
+ if (!pending.length) return;
31162
+ const controller = new AbortController();
31163
+ let timer;
31164
+ try {
31165
+ const acknowledgment = await Promise.race([
31166
+ api.call("postExecutions", { body: { reports: pending.map((item) => item.report) }, signal: controller.signal }),
31167
+ new Promise((_, reject) => {
31168
+ timer = setTimeout(() => {
31169
+ controller.abort();
31170
+ reject(new Error("Report delivery deferred"));
31171
+ }, budgetMs);
31172
+ })
31173
+ ]);
31174
+ state.acknowledgeExecutionReports(acknowledgment.results.map((item) => item.id));
31175
+ } catch {
31176
+ } finally {
31177
+ clearTimeout(timer);
31178
+ }
31179
+ }
31180
+ var init_execution_reports = __esm({
31181
+ "src/execution-reports.ts"() {
31182
+ "use strict";
31183
+ }
31184
+ });
31185
+
31184
31186
  // src/discovery/credential-draft.ts
31185
31187
  import { createHash as createHash4 } from "node:crypto";
31186
31188
  import { readFileSync as readFileSync5 } from "node:fs";
@@ -32908,6 +32910,11 @@ function confirmProof(proof, body, primary, inputs, status) {
32908
32910
  for (const comparison of proof.equals ?? []) {
32909
32911
  if (!matches(comparison, selected)) throw new ResponseContractFailure(`Readback comparison failed at ${comparison.path || "/"}`);
32910
32912
  }
32913
+ if (proof.response) {
32914
+ const value = proof.response.path !== void 0 ? pointer2(selected, proofPath(proof.response.path, inputs)) : selected;
32915
+ const validate2 = ajv3.compile(proof.response.schema);
32916
+ if (!validate2(value)) throw new ResponseContractFailure(`Readback response contract mismatch: ${ajv3.errorsText(validate2.errors, { dataVar: "readback" })}`);
32917
+ }
32911
32918
  }
32912
32919
  async function handleRun(invocation, api, state, session, fetcher, root, stdin = () => readFileSync7(0, "utf8"), onReport) {
32913
32920
  const [appName, actionName] = invocation.positionals;
@@ -33152,7 +33159,7 @@ async function executeDraftVerification(input2) {
33152
33159
  const generated = input2.primaryEvidence?.generated ?? (input2.kind === "action" && action && "generated" in action ? Object.fromEntries(Object.entries(action.generated ?? {}).map(([name, spec]) => [name, `${spec.prefix ?? ""}${randomUUID7()}`])) : {});
33153
33160
  try {
33154
33161
  values = effectiveInputs(schema, input2.primaryEvidence?.inputs ?? input2.sampleInputs, {}, true).values;
33155
- if (action && "proof" in action && action.proof) for (const item of [...action.proof.equals ?? [], ...action.proof.select?.where ?? [], ...action.proof.select ? [action.proof.select] : []]) proofPath(item.path, values);
33162
+ if (action && "proof" in action && action.proof) for (const item of [...action.proof.equals ?? [], ...action.proof.select?.where ?? [], ...action.proof.select ? [action.proof.select] : [], ...action.proof.response?.path !== void 0 ? [{ path: action.proof.response.path }] : []]) proofPath(item.path, values);
33156
33163
  const request2 = input2.kind === "action" ? action.request : probe.request;
33157
33164
  prepared = prepareRequest(request2, input2.kind === "transport" && input2.probeOrigin ? { ...transport, baseUrl: input2.probeOrigin } : transport, values, generated);
33158
33165
  } catch (error62) {
@@ -34245,7 +34252,7 @@ Publish removal: ${command4(invocation, "discover publish")}
34245
34252
  Abandon removal: ${command4(invocation, "discover discard")}
34246
34253
  `);
34247
34254
  }
34248
- function draftGuidance(invocation, draft, unavailable = false) {
34255
+ function draftGuidance(invocation, draft, unavailable = false, resumed = false) {
34249
34256
  if (draft.intent === "remove") return `Selected the retained removal of ${label(draft.kind)} ${draft.name}. Nothing has been removed.
34250
34257
  Review: ${draft.filePath}
34251
34258
  Check dependencies: ${command4(invocation, "discover check")}
@@ -34259,6 +34266,10 @@ Recover this file: ${command4(invocation, "discover status")}
34259
34266
  Keep for later (including evidence): ${command4(invocation, "discover defer")}
34260
34267
  Abandon unpublished edits: ${command4(invocation, "discover discard")}
34261
34268
  `;
34269
+ if (resumed) return `Resumed ${prefix}. Existing edits and evidence were retained.
34270
+ Edit: ${draft.filePath}
34271
+
34272
+ ${local}`;
34262
34273
  if (draft.kind === "app") return `Created an app draft for ${draft.appSlug}. Nothing has been published.
34263
34274
  Edit: ${draft.filePath}
34264
34275
 
@@ -34322,6 +34333,7 @@ Create or edit it with ${command4(invocation, "discover draft action <entity.ver
34322
34333
  File: ${selected.filePath}
34323
34334
  `, 12);
34324
34335
  const resource = current.appId ? await currentResource(api, current.appId, kind, name ?? "app") : null;
34336
+ const resumed = store.retained(current.appSlug).some((item) => item.kind === kind && item.name === (name ?? "app") && existsSync4(item.filePath));
34325
34337
  const draft = store.open({ appSlug: current.appSlug, appId: current.appId ?? void 0, kind, name, sessionId: session.id, baseVersion: resource?.version });
34326
34338
  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.
34327
34339
  File: ${draft.filePath}
@@ -34334,7 +34346,7 @@ File: ${draft.filePath}
34334
34346
  `, { mode: 384 });
34335
34347
  }
34336
34348
  void root;
34337
- return ok4(draftGuidance(invocation, draft, Boolean(parseDraftEnvelope(readFileSync8(draft.filePath, "utf8")).envelope?.definition.unavailable)));
34349
+ return ok4(draftGuidance(invocation, draft, Boolean(parseDraftEnvelope(readFileSync8(draft.filePath, "utf8")).envelope?.definition.unavailable), resumed));
34338
34350
  }
34339
34351
  function requireDiscovery(store, session, invocation) {
34340
34352
  const current = store.session(session.id);
@@ -35502,7 +35514,7 @@ async function finalizeOutput(output2, sink, directory = tmpdir2()) {
35502
35514
  const size = () => Buffer.byteLength(stdout) + Buffer.byteLength(stderr);
35503
35515
  try {
35504
35516
  if (size() > displayBudget) {
35505
- const root = await mkdtemp(join13(directory, "apilift-output-"));
35517
+ const root = await mkdtemp(join13(output2.outputDirectory ?? directory, "apilift-output-"));
35506
35518
  await chmod(root, 448);
35507
35519
  const streams = ["stdout", "stderr"];
35508
35520
  streams.sort((left, right) => Buffer.byteLength(output2[right]) - Buffer.byteLength(output2[left]));
@@ -38892,6 +38904,7 @@ __export(cli_exports, {
38892
38904
  runCli: () => runCli
38893
38905
  });
38894
38906
  import { hostname as hostname3, platform as platform3, arch } from "node:os";
38907
+ import { dirname as dirname13 } from "node:path";
38895
38908
  function result2(stdout = "", exitCode = 0, stderr = "", dispatched = false) {
38896
38909
  return { stdout, stderr, exitCode, dispatched };
38897
38910
  }
@@ -39009,7 +39022,14 @@ Open ${site}/dashboard/get-started and then run:
39009
39022
  }
39010
39023
  if (invocation.key.startsWith("credentials") || invocation.key === "transportsShow") return await handleCredentials(invocation, api, state, session, site, dataRoot(env));
39011
39024
  if (invocation.key.startsWith("browser")) return await handleBrowser(invocation, api, state, session, dataRoot(env));
39012
- if (invocation.key.startsWith("discover") || ["appsNew", "appsUpdate", "appsHistory"].includes(invocation.key)) return await handleDiscover(invocation, api, state, session, fetcher, dataRoot(env), invocationId);
39025
+ if (invocation.key.startsWith("discover") || ["appsNew", "appsUpdate", "appsHistory"].includes(invocation.key)) {
39026
+ const output2 = await handleDiscover(invocation, api, state, session, fetcher, dataRoot(env), invocationId);
39027
+ if (session && Buffer.byteLength(output2.stdout) + Buffer.byteLength(output2.stderr) > displayBudget) {
39028
+ const store = new DraftStore(state, dataRoot(env)), draft = store.selected(session.id);
39029
+ if (draft?.ownerSessionId === session.id) output2.outputDirectory = dirname13(store.artifactPath(draft.id, "output"));
39030
+ }
39031
+ return output2;
39032
+ }
39013
39033
  if (invocation.key === "run") return await handleRun(invocation, api, state, session, fetcher, dataRoot(env), void 0, onReport);
39014
39034
  if (invocation.key === "setupContinue") {
39015
39035
  await api.call("me");
@@ -39200,6 +39220,7 @@ var init_cli = __esm({
39200
39220
  "use strict";
39201
39221
  init_receipts();
39202
39222
  init_lock();
39223
+ init_drafts();
39203
39224
  init_api2();
39204
39225
  init_api3();
39205
39226
  init_registry();
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apilift",
3
- "version": "1.5.3",
3
+ "version": "1.5.4",
4
4
  "description": "Use logged-in apps and discover missing actions with Apilift.",
5
5
  "author": {
6
6
  "name": "Apilift"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apilift",
3
- "version": "1.5.3",
3
+ "version": "1.5.4",
4
4
  "description": "Use logged-in apps and discover missing actions with Apilift.",
5
5
  "author": {
6
6
  "name": "Apilift"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apilift/cli",
3
- "version": "1.5.3",
3
+ "version": "1.5.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -36,7 +36,7 @@
36
36
  "engines": {
37
37
  "node": ">=24.16.0 <27"
38
38
  },
39
- "gitHead": "35e5b85595e90273e189e53c1f0f3cf89400cdae",
39
+ "gitHead": "77f4acfa3f2652928fea0a82245cc55b52a84e64",
40
40
  "repository": {
41
41
  "type": "git",
42
42
  "url": "git+https://github.com/egor-sergeev/apilift.git",