@plune-ai/cli 0.5.0 → 0.6.0

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/cli.cjs CHANGED
@@ -808,9 +808,11 @@ var init_embedder = __esm({
808
808
  return rows.map((row) => Float32Array.from(row));
809
809
  }
810
810
  load() {
811
- this.extractor ??= import("@huggingface/transformers").then(
812
- ({ pipeline }) => pipeline("feature-extraction", MODEL_ID)
813
- );
811
+ this.extractor ??= import("@huggingface/transformers").catch(() => {
812
+ throw new Error(
813
+ "The `semantic-similarity` and RAG assertions need local embeddings, which ship separately: install @huggingface/transformers to enable them. Every other assertion works without it."
814
+ );
815
+ }).then(({ pipeline }) => pipeline("feature-extraction", MODEL_ID));
814
816
  return this.extractor;
815
817
  }
816
818
  };
@@ -2796,6 +2798,247 @@ var init_sync = __esm({
2796
2798
  }
2797
2799
  });
2798
2800
 
2801
+ // src/cli/commands/cairn-artifact.ts
2802
+ async function readJson(file) {
2803
+ try {
2804
+ return JSON.parse(await fs11.readFile(file, "utf8"));
2805
+ } catch {
2806
+ return void 0;
2807
+ }
2808
+ }
2809
+ async function defaultReadEvidence(dir) {
2810
+ const raw = await readJson(path10.join(dir, "api-evidence.json"));
2811
+ if (!Array.isArray(raw)) return void 0;
2812
+ return raw.map((r) => {
2813
+ const rec = asRecord(r);
2814
+ return { name: str(rec["name"]), passed: rec["passed"] === true };
2815
+ });
2816
+ }
2817
+ function casesFromDesign(report) {
2818
+ const list = Array.isArray(report["testCases"]) ? report["testCases"] : [];
2819
+ return list.map((raw) => {
2820
+ const c = asRecord(raw);
2821
+ const steps = (Array.isArray(c["steps"]) ? c["steps"] : []).map((s) => str(s)).filter((s) => s !== "");
2822
+ return {
2823
+ stableId: str(c["stableId"]),
2824
+ title: str(c["title"], "untitled case"),
2825
+ execution: c["execution"] === "manual" ? "manual" : "auto",
2826
+ steps: steps.length > 0 ? steps : ["(no steps recorded)"],
2827
+ expected: str(c["expected"], "(no expected result recorded)"),
2828
+ ...typeof c["technique"] === "string" ? { technique: c["technique"] } : {},
2829
+ ...typeof c["priority"] === "string" ? { priority: c["priority"] } : {}
2830
+ };
2831
+ });
2832
+ }
2833
+ function casesFromApi(report, verdicts) {
2834
+ const list = Array.isArray(report["cases"]) ? report["cases"] : [];
2835
+ return list.map((raw) => {
2836
+ const c = asRecord(raw);
2837
+ const name = str(c["name"], "unnamed operation");
2838
+ const request = `${str(c["method"], "GET")} ${str(c["path"], "/")}`;
2839
+ const verdict = verdicts.get(name);
2840
+ return {
2841
+ stableId: str(c["stableId"]),
2842
+ title: name,
2843
+ execution: "auto",
2844
+ steps: [request],
2845
+ expected: `HTTP ${str(c["expectedStatus"], "default")}`,
2846
+ ...verdict !== void 0 ? { verdict } : {},
2847
+ ...typeof c["technique"] === "string" ? { technique: c["technique"] } : {}
2848
+ };
2849
+ });
2850
+ }
2851
+ async function readCairnRun(dir, deps = {}) {
2852
+ const raw = await readJson(path10.join(dir, "report.json"));
2853
+ if (raw === void 0) throw new CairnRunNotFoundError(dir);
2854
+ const report = asRecord(raw);
2855
+ if (report["schemaVersion"] !== SUPPORTED_ARTIFACT_VERSION) {
2856
+ throw new CairnRunVersionError(report["schemaVersion"]);
2857
+ }
2858
+ if (report["partial"] === true) {
2859
+ throw new CairnRunPartialError(str(report["error"], "no reason recorded"));
2860
+ }
2861
+ const mode = report["mode"];
2862
+ if (mode !== "design" && mode !== "explore" && mode !== "api") {
2863
+ throw new CairnRunVersionError(`${SUPPORTED_ARTIFACT_VERSION} (unknown run mode ${String(mode)})`);
2864
+ }
2865
+ let cases;
2866
+ if (mode === "api") {
2867
+ const evidence = await (deps.readEvidence ?? defaultReadEvidence)(dir);
2868
+ const verdicts = /* @__PURE__ */ new Map();
2869
+ for (const e of evidence ?? []) {
2870
+ if (verdicts.has(e.name)) throw new CairnEvidenceAmbiguousError(e.name);
2871
+ verdicts.set(e.name, e.passed ? "passed" : "failed");
2872
+ }
2873
+ cases = casesFromApi(report, verdicts);
2874
+ } else {
2875
+ cases = casesFromDesign(report);
2876
+ }
2877
+ return {
2878
+ schemaVersion: INGEST_SCHEMA_VERSION,
2879
+ run: {
2880
+ runId: str(report["runId"], path10.basename(dir)),
2881
+ mode,
2882
+ target: str(report["url"], "unknown")
2883
+ },
2884
+ cases
2885
+ };
2886
+ }
2887
+ var fs11, path10, SUPPORTED_ARTIFACT_VERSION, INGEST_SCHEMA_VERSION, CairnRunNotFoundError, CairnRunVersionError, CairnRunPartialError, CairnEvidenceAmbiguousError, asRecord, str;
2888
+ var init_cairn_artifact = __esm({
2889
+ "src/cli/commands/cairn-artifact.ts"() {
2890
+ "use strict";
2891
+ init_cjs_shims();
2892
+ fs11 = __toESM(require("fs/promises"), 1);
2893
+ path10 = __toESM(require("path"), 1);
2894
+ SUPPORTED_ARTIFACT_VERSION = 1;
2895
+ INGEST_SCHEMA_VERSION = 1;
2896
+ CairnRunNotFoundError = class extends Error {
2897
+ constructor(dir) {
2898
+ super(`No Cairn run found in ${dir} \u2014 expected a report.json written by "cairn design|explore|api".`);
2899
+ this.name = "CairnRunNotFoundError";
2900
+ }
2901
+ };
2902
+ CairnRunVersionError = class extends Error {
2903
+ constructor(found) {
2904
+ super(
2905
+ found === void 0 ? "This run predates versioned Cairn artifacts. Re-run it with Cairn 0.7.0 or newer, which stamps the format so it can be read safely." : `Unsupported Cairn artifact version ${String(found)} (this CLI reads version ${SUPPORTED_ARTIFACT_VERSION}). Upgrade @plune-ai/cli.`
2906
+ );
2907
+ this.name = "CairnRunVersionError";
2908
+ }
2909
+ };
2910
+ CairnRunPartialError = class extends Error {
2911
+ constructor(reason) {
2912
+ super(`That Cairn run did not finish, so its cases are incomplete: ${reason}`);
2913
+ this.name = "CairnRunPartialError";
2914
+ }
2915
+ };
2916
+ CairnEvidenceAmbiguousError = class extends Error {
2917
+ constructor(name) {
2918
+ super(
2919
+ `Cannot attach results safely: the operation "${name}" appears more than once in this run's evidence, and the evidence file carries no case identity to disambiguate it. Nothing was uploaded.`
2920
+ );
2921
+ this.name = "CairnEvidenceAmbiguousError";
2922
+ }
2923
+ };
2924
+ asRecord = (v) => typeof v === "object" && v !== null ? v : {};
2925
+ str = (v, fallback = "") => typeof v === "string" && v !== "" ? v : fallback;
2926
+ }
2927
+ });
2928
+
2929
+ // src/cli/commands/ingest.ts
2930
+ var ingest_exports = {};
2931
+ __export(ingest_exports, {
2932
+ IngestHttpError: () => IngestHttpError,
2933
+ IngestNetworkError: () => IngestNetworkError,
2934
+ IngestNotLoggedInError: () => IngestNotLoggedInError,
2935
+ IngestTokenRejectedError: () => IngestTokenRejectedError,
2936
+ formatIngestResult: () => formatIngestResult,
2937
+ handleIngest: () => handleIngest,
2938
+ reportIngestFailure: () => reportIngestFailure
2939
+ });
2940
+ function resolveApiUrl2(explicit) {
2941
+ const raw = explicit ?? process.env["PLUNE_API_URL"] ?? DEFAULT_API_URL2;
2942
+ return raw.replace(/\/+$/, "");
2943
+ }
2944
+ async function safeErrorDetail2(res) {
2945
+ try {
2946
+ const body = await res.json();
2947
+ return typeof body.error === "string" ? body.error : "";
2948
+ } catch {
2949
+ return "";
2950
+ }
2951
+ }
2952
+ async function handleIngest(deps) {
2953
+ const apiUrl = resolveApiUrl2(deps.apiUrl);
2954
+ const token = (deps.loadToken ?? loadToken)();
2955
+ if (token === null) throw new IngestNotLoggedInError();
2956
+ const payload = await readCairnRun(deps.dir);
2957
+ const doFetch = deps.fetchImpl ?? fetch;
2958
+ const endpoint = `${apiUrl}/v1/ingest/cairn`;
2959
+ let res;
2960
+ try {
2961
+ res = await doFetch(endpoint, {
2962
+ method: "POST",
2963
+ headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
2964
+ body: JSON.stringify(payload)
2965
+ });
2966
+ } catch {
2967
+ throw new IngestNetworkError(apiUrl);
2968
+ }
2969
+ if (!res.ok) {
2970
+ const detail = await safeErrorDetail2(res);
2971
+ if (res.status === 401) throw new IngestTokenRejectedError();
2972
+ throw new IngestHttpError(res.status, detail);
2973
+ }
2974
+ const body = await res.json().catch(() => ({}));
2975
+ return {
2976
+ runId: typeof body.runId === "string" ? body.runId : "",
2977
+ linked: typeof body.linked === "number" ? body.linked : 0,
2978
+ proposed: typeof body.proposed === "number" ? body.proposed : 0,
2979
+ skipped: typeof body.skipped === "number" ? body.skipped : 0
2980
+ };
2981
+ }
2982
+ function reportIngestFailure(err, write) {
2983
+ if (err instanceof IngestNotLoggedInError || err instanceof IngestTokenRejectedError || err instanceof CairnRunNotFoundError || err instanceof CairnRunVersionError || err instanceof CairnRunPartialError || err instanceof CairnEvidenceAmbiguousError) {
2984
+ write(`${err.message}
2985
+ `);
2986
+ return 2;
2987
+ }
2988
+ if (err instanceof IngestNetworkError || err instanceof IngestHttpError) {
2989
+ write(`${err.message}
2990
+ `);
2991
+ return 1;
2992
+ }
2993
+ return null;
2994
+ }
2995
+ function formatIngestResult(r) {
2996
+ const lines = [
2997
+ `Ingested Cairn run \u2192 ${r.runId}`,
2998
+ ` ${r.linked} result(s) attached to existing cases`,
2999
+ ` ${r.proposed} case(s) proposed \u2014 review them in Plune to accept`,
3000
+ ` ${r.skipped} skipped (already queued, already rejected, repeated, or never executed)`
3001
+ ];
3002
+ return `${lines.join("\n")}
3003
+ `;
3004
+ }
3005
+ var DEFAULT_API_URL2, IngestNotLoggedInError, IngestTokenRejectedError, IngestNetworkError, IngestHttpError;
3006
+ var init_ingest = __esm({
3007
+ "src/cli/commands/ingest.ts"() {
3008
+ "use strict";
3009
+ init_cjs_shims();
3010
+ init_credentials();
3011
+ init_cairn_artifact();
3012
+ DEFAULT_API_URL2 = "https://beta-api.plune.ai";
3013
+ IngestNotLoggedInError = class extends Error {
3014
+ constructor() {
3015
+ super('Not logged in. Run "plune login" first to save your API token.');
3016
+ this.name = "IngestNotLoggedInError";
3017
+ }
3018
+ };
3019
+ IngestTokenRejectedError = class extends Error {
3020
+ constructor() {
3021
+ super('Your API token was rejected (401). Run "plune login" again with a fresh token.');
3022
+ this.name = "IngestTokenRejectedError";
3023
+ }
3024
+ };
3025
+ IngestNetworkError = class extends Error {
3026
+ constructor(apiUrl) {
3027
+ super(`Could not reach the Plune API at ${apiUrl}. Check your connection, then try again.`);
3028
+ this.name = "IngestNetworkError";
3029
+ }
3030
+ };
3031
+ IngestHttpError = class extends Error {
3032
+ constructor(status, detail) {
3033
+ super(`Plune API returned ${status}${detail !== "" ? ` \u2014 ${detail}` : ""}.`);
3034
+ this.status = status;
3035
+ this.name = "IngestHttpError";
3036
+ }
3037
+ status;
3038
+ };
3039
+ }
3040
+ });
3041
+
2799
3042
  // src/cli.ts
2800
3043
  var cli_exports = {};
2801
3044
  __export(cli_exports, {
@@ -2996,8 +3239,8 @@ function createProgram() {
2996
3239
  const { handleLogin: handleLogin2, EmptyTokenError: EmptyTokenError2 } = await Promise.resolve().then(() => (init_login(), login_exports));
2997
3240
  try {
2998
3241
  const token = options.token ?? await readTokenFromStdin();
2999
- const { path: path10 } = handleLogin2({ token });
3000
- process.stdout.write(`Logged in. Token saved to ${path10}
3242
+ const { path: path11 } = handleLogin2({ token });
3243
+ process.stdout.write(`Logged in. Token saved to ${path11}
3001
3244
  `);
3002
3245
  } catch (err) {
3003
3246
  if (err instanceof EmptyTokenError2) {
@@ -3012,9 +3255,9 @@ function createProgram() {
3012
3255
  const verbose = command.optsWithGlobals().verbose === true;
3013
3256
  const { handleLogout: handleLogout2 } = await Promise.resolve().then(() => (init_logout(), logout_exports));
3014
3257
  try {
3015
- const { removed, path: path10 } = handleLogout2();
3258
+ const { removed, path: path11 } = handleLogout2();
3016
3259
  process.stdout.write(
3017
- removed ? `Logged out. Removed ${path10}
3260
+ removed ? `Logged out. Removed ${path11}
3018
3261
  ` : "Not logged in \u2014 nothing to remove.\n"
3019
3262
  );
3020
3263
  } catch (err) {
@@ -3043,6 +3286,22 @@ function createProgram() {
3043
3286
  failUnexpected(err, verbose);
3044
3287
  }
3045
3288
  });
3289
+ program.command("ingest").argument("<dir>", "A Cairn run directory (the one holding report.json)").description("Record a Cairn run in Plune \u2014 generated cases arrive as review proposals").action(async (dir, _options, command) => {
3290
+ const globals = command.optsWithGlobals();
3291
+ const verbose = globals.verbose === true;
3292
+ const { handleIngest: handleIngest2, reportIngestFailure: reportIngestFailure2, formatIngestResult: formatIngestResult2 } = await Promise.resolve().then(() => (init_ingest(), ingest_exports));
3293
+ try {
3294
+ process.stdout.write(formatIngestResult2(await handleIngest2({ dir })));
3295
+ } catch (err) {
3296
+ const code = reportIngestFailure2(err, (s) => process.stderr.write(s));
3297
+ if (code !== null) {
3298
+ maybeStack(err, verbose);
3299
+ process.exitCode = code;
3300
+ return;
3301
+ }
3302
+ failUnexpected(err, verbose);
3303
+ }
3304
+ });
3046
3305
  program.on("command:*", (operands) => {
3047
3306
  process.stderr.write(`Unknown command: ${operands.join(" ")}
3048
3307
  `);
package/dist/index.cjs CHANGED
@@ -39,7 +39,11 @@ __export(src_exports, {
39
39
  assertionConfigSchema: () => assertionConfigSchema,
40
40
  assertionResultRecordSchema: () => assertionResultRecordSchema,
41
41
  binaryVerdictSchema: () => binaryVerdictSchema,
42
+ formatIngestResult: () => formatIngestResult,
43
+ ingest: () => handleIngest,
42
44
  parseRunResult: () => parseRunResult,
45
+ readCairnRun: () => readCairnRun,
46
+ reportIngestFailure: () => reportIngestFailure,
43
47
  reportSyncFailure: () => reportSyncFailure,
44
48
  run: () => handleRun,
45
49
  runResultSchema: () => runResultSchema,
@@ -644,9 +648,11 @@ var XenovaEmbedder = class {
644
648
  return rows.map((row) => Float32Array.from(row));
645
649
  }
646
650
  load() {
647
- this.extractor ??= import("@huggingface/transformers").then(
648
- ({ pipeline }) => pipeline("feature-extraction", MODEL_ID)
649
- );
651
+ this.extractor ??= import("@huggingface/transformers").catch(() => {
652
+ throw new Error(
653
+ "The `semantic-similarity` and RAG assertions need local embeddings, which ship separately: install @huggingface/transformers to enable them. Every other assertion works without it."
654
+ );
655
+ }).then(({ pipeline }) => pipeline("feature-extraction", MODEL_ID));
650
656
  return this.extractor;
651
657
  }
652
658
  };
@@ -1700,6 +1706,221 @@ function reportSyncFailure(err, write) {
1700
1706
  }
1701
1707
  return null;
1702
1708
  }
1709
+
1710
+ // src/cli/commands/cairn-artifact.ts
1711
+ var fs7 = __toESM(require("fs/promises"), 1);
1712
+ var path6 = __toESM(require("path"), 1);
1713
+ var SUPPORTED_ARTIFACT_VERSION = 1;
1714
+ var INGEST_SCHEMA_VERSION = 1;
1715
+ var CairnRunNotFoundError = class extends Error {
1716
+ constructor(dir) {
1717
+ super(`No Cairn run found in ${dir} \u2014 expected a report.json written by "cairn design|explore|api".`);
1718
+ this.name = "CairnRunNotFoundError";
1719
+ }
1720
+ };
1721
+ var CairnRunVersionError = class extends Error {
1722
+ constructor(found) {
1723
+ super(
1724
+ found === void 0 ? "This run predates versioned Cairn artifacts. Re-run it with Cairn 0.7.0 or newer, which stamps the format so it can be read safely." : `Unsupported Cairn artifact version ${String(found)} (this CLI reads version ${SUPPORTED_ARTIFACT_VERSION}). Upgrade @plune-ai/cli.`
1725
+ );
1726
+ this.name = "CairnRunVersionError";
1727
+ }
1728
+ };
1729
+ var CairnRunPartialError = class extends Error {
1730
+ constructor(reason) {
1731
+ super(`That Cairn run did not finish, so its cases are incomplete: ${reason}`);
1732
+ this.name = "CairnRunPartialError";
1733
+ }
1734
+ };
1735
+ var CairnEvidenceAmbiguousError = class extends Error {
1736
+ constructor(name) {
1737
+ super(
1738
+ `Cannot attach results safely: the operation "${name}" appears more than once in this run's evidence, and the evidence file carries no case identity to disambiguate it. Nothing was uploaded.`
1739
+ );
1740
+ this.name = "CairnEvidenceAmbiguousError";
1741
+ }
1742
+ };
1743
+ var asRecord = (v) => typeof v === "object" && v !== null ? v : {};
1744
+ var str = (v, fallback = "") => typeof v === "string" && v !== "" ? v : fallback;
1745
+ async function readJson(file) {
1746
+ try {
1747
+ return JSON.parse(await fs7.readFile(file, "utf8"));
1748
+ } catch {
1749
+ return void 0;
1750
+ }
1751
+ }
1752
+ async function defaultReadEvidence(dir) {
1753
+ const raw = await readJson(path6.join(dir, "api-evidence.json"));
1754
+ if (!Array.isArray(raw)) return void 0;
1755
+ return raw.map((r) => {
1756
+ const rec = asRecord(r);
1757
+ return { name: str(rec["name"]), passed: rec["passed"] === true };
1758
+ });
1759
+ }
1760
+ function casesFromDesign(report) {
1761
+ const list = Array.isArray(report["testCases"]) ? report["testCases"] : [];
1762
+ return list.map((raw) => {
1763
+ const c = asRecord(raw);
1764
+ const steps = (Array.isArray(c["steps"]) ? c["steps"] : []).map((s) => str(s)).filter((s) => s !== "");
1765
+ return {
1766
+ stableId: str(c["stableId"]),
1767
+ title: str(c["title"], "untitled case"),
1768
+ execution: c["execution"] === "manual" ? "manual" : "auto",
1769
+ steps: steps.length > 0 ? steps : ["(no steps recorded)"],
1770
+ expected: str(c["expected"], "(no expected result recorded)"),
1771
+ ...typeof c["technique"] === "string" ? { technique: c["technique"] } : {},
1772
+ ...typeof c["priority"] === "string" ? { priority: c["priority"] } : {}
1773
+ };
1774
+ });
1775
+ }
1776
+ function casesFromApi(report, verdicts) {
1777
+ const list = Array.isArray(report["cases"]) ? report["cases"] : [];
1778
+ return list.map((raw) => {
1779
+ const c = asRecord(raw);
1780
+ const name = str(c["name"], "unnamed operation");
1781
+ const request = `${str(c["method"], "GET")} ${str(c["path"], "/")}`;
1782
+ const verdict = verdicts.get(name);
1783
+ return {
1784
+ stableId: str(c["stableId"]),
1785
+ title: name,
1786
+ execution: "auto",
1787
+ steps: [request],
1788
+ expected: `HTTP ${str(c["expectedStatus"], "default")}`,
1789
+ ...verdict !== void 0 ? { verdict } : {},
1790
+ ...typeof c["technique"] === "string" ? { technique: c["technique"] } : {}
1791
+ };
1792
+ });
1793
+ }
1794
+ async function readCairnRun(dir, deps = {}) {
1795
+ const raw = await readJson(path6.join(dir, "report.json"));
1796
+ if (raw === void 0) throw new CairnRunNotFoundError(dir);
1797
+ const report = asRecord(raw);
1798
+ if (report["schemaVersion"] !== SUPPORTED_ARTIFACT_VERSION) {
1799
+ throw new CairnRunVersionError(report["schemaVersion"]);
1800
+ }
1801
+ if (report["partial"] === true) {
1802
+ throw new CairnRunPartialError(str(report["error"], "no reason recorded"));
1803
+ }
1804
+ const mode = report["mode"];
1805
+ if (mode !== "design" && mode !== "explore" && mode !== "api") {
1806
+ throw new CairnRunVersionError(`${SUPPORTED_ARTIFACT_VERSION} (unknown run mode ${String(mode)})`);
1807
+ }
1808
+ let cases;
1809
+ if (mode === "api") {
1810
+ const evidence = await (deps.readEvidence ?? defaultReadEvidence)(dir);
1811
+ const verdicts = /* @__PURE__ */ new Map();
1812
+ for (const e of evidence ?? []) {
1813
+ if (verdicts.has(e.name)) throw new CairnEvidenceAmbiguousError(e.name);
1814
+ verdicts.set(e.name, e.passed ? "passed" : "failed");
1815
+ }
1816
+ cases = casesFromApi(report, verdicts);
1817
+ } else {
1818
+ cases = casesFromDesign(report);
1819
+ }
1820
+ return {
1821
+ schemaVersion: INGEST_SCHEMA_VERSION,
1822
+ run: {
1823
+ runId: str(report["runId"], path6.basename(dir)),
1824
+ mode,
1825
+ target: str(report["url"], "unknown")
1826
+ },
1827
+ cases
1828
+ };
1829
+ }
1830
+
1831
+ // src/cli/commands/ingest.ts
1832
+ var DEFAULT_API_URL2 = "https://beta-api.plune.ai";
1833
+ var IngestNotLoggedInError = class extends Error {
1834
+ constructor() {
1835
+ super('Not logged in. Run "plune login" first to save your API token.');
1836
+ this.name = "IngestNotLoggedInError";
1837
+ }
1838
+ };
1839
+ var IngestTokenRejectedError = class extends Error {
1840
+ constructor() {
1841
+ super('Your API token was rejected (401). Run "plune login" again with a fresh token.');
1842
+ this.name = "IngestTokenRejectedError";
1843
+ }
1844
+ };
1845
+ var IngestNetworkError = class extends Error {
1846
+ constructor(apiUrl) {
1847
+ super(`Could not reach the Plune API at ${apiUrl}. Check your connection, then try again.`);
1848
+ this.name = "IngestNetworkError";
1849
+ }
1850
+ };
1851
+ var IngestHttpError = class extends Error {
1852
+ constructor(status, detail) {
1853
+ super(`Plune API returned ${status}${detail !== "" ? ` \u2014 ${detail}` : ""}.`);
1854
+ this.status = status;
1855
+ this.name = "IngestHttpError";
1856
+ }
1857
+ status;
1858
+ };
1859
+ function resolveApiUrl2(explicit) {
1860
+ const raw = explicit ?? process.env["PLUNE_API_URL"] ?? DEFAULT_API_URL2;
1861
+ return raw.replace(/\/+$/, "");
1862
+ }
1863
+ async function safeErrorDetail2(res) {
1864
+ try {
1865
+ const body = await res.json();
1866
+ return typeof body.error === "string" ? body.error : "";
1867
+ } catch {
1868
+ return "";
1869
+ }
1870
+ }
1871
+ async function handleIngest(deps) {
1872
+ const apiUrl = resolveApiUrl2(deps.apiUrl);
1873
+ const token = (deps.loadToken ?? loadToken)();
1874
+ if (token === null) throw new IngestNotLoggedInError();
1875
+ const payload = await readCairnRun(deps.dir);
1876
+ const doFetch = deps.fetchImpl ?? fetch;
1877
+ const endpoint = `${apiUrl}/v1/ingest/cairn`;
1878
+ let res;
1879
+ try {
1880
+ res = await doFetch(endpoint, {
1881
+ method: "POST",
1882
+ headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
1883
+ body: JSON.stringify(payload)
1884
+ });
1885
+ } catch {
1886
+ throw new IngestNetworkError(apiUrl);
1887
+ }
1888
+ if (!res.ok) {
1889
+ const detail = await safeErrorDetail2(res);
1890
+ if (res.status === 401) throw new IngestTokenRejectedError();
1891
+ throw new IngestHttpError(res.status, detail);
1892
+ }
1893
+ const body = await res.json().catch(() => ({}));
1894
+ return {
1895
+ runId: typeof body.runId === "string" ? body.runId : "",
1896
+ linked: typeof body.linked === "number" ? body.linked : 0,
1897
+ proposed: typeof body.proposed === "number" ? body.proposed : 0,
1898
+ skipped: typeof body.skipped === "number" ? body.skipped : 0
1899
+ };
1900
+ }
1901
+ function reportIngestFailure(err, write) {
1902
+ if (err instanceof IngestNotLoggedInError || err instanceof IngestTokenRejectedError || err instanceof CairnRunNotFoundError || err instanceof CairnRunVersionError || err instanceof CairnRunPartialError || err instanceof CairnEvidenceAmbiguousError) {
1903
+ write(`${err.message}
1904
+ `);
1905
+ return 2;
1906
+ }
1907
+ if (err instanceof IngestNetworkError || err instanceof IngestHttpError) {
1908
+ write(`${err.message}
1909
+ `);
1910
+ return 1;
1911
+ }
1912
+ return null;
1913
+ }
1914
+ function formatIngestResult(r) {
1915
+ const lines = [
1916
+ `Ingested Cairn run \u2192 ${r.runId}`,
1917
+ ` ${r.linked} result(s) attached to existing cases`,
1918
+ ` ${r.proposed} case(s) proposed \u2014 review them in Plune to accept`,
1919
+ ` ${r.skipped} skipped (already queued, already rejected, repeated, or never executed)`
1920
+ ];
1921
+ return `${lines.join("\n")}
1922
+ `;
1923
+ }
1703
1924
  // Annotate the CommonJS export names for ESM import in node:
1704
1925
  0 && (module.exports = {
1705
1926
  NotLoggedInError,
@@ -1711,7 +1932,11 @@ function reportSyncFailure(err, write) {
1711
1932
  assertionConfigSchema,
1712
1933
  assertionResultRecordSchema,
1713
1934
  binaryVerdictSchema,
1935
+ formatIngestResult,
1936
+ ingest,
1714
1937
  parseRunResult,
1938
+ readCairnRun,
1939
+ reportIngestFailure,
1715
1940
  reportSyncFailure,
1716
1941
  run,
1717
1942
  runResultSchema,
package/dist/index.d.cts CHANGED
@@ -799,4 +799,70 @@ type BinaryVerdict = z.infer<typeof binaryVerdictSchema>;
799
799
  /** Parse + validate an unknown value as a RunResult. Throws ZodError on failure. */
800
800
  declare function parseRunResult(value: unknown): ParsedRunResult;
801
801
 
802
- export { type AssertionResultRecord, type BinaryVerdict, type Config, type EvalResult, NotLoggedInError, type ParsedRunResult, RUN_RESULT_SCHEMA_VERSION, type RowResult, type RunError, type RunOptions, type RunResult, type Summary, type SyncDeps, SyncFileError, SyncHttpError, SyncNetworkError, type SyncResult, TokenRejectedError, type Usage, assertionConfigSchema, assertionResultRecordSchema, binaryVerdictSchema, parseRunResult, reportSyncFailure, handleRun as run, runResultSchema, handleSync as sync };
802
+ interface IngestDeps {
803
+ /** The Cairn run directory to read. */
804
+ dir: string;
805
+ apiUrl?: string;
806
+ loadToken?: () => string | null;
807
+ fetchImpl?: typeof fetch;
808
+ }
809
+ /** What the server recorded — the three counters that account for every case sent. */
810
+ interface IngestResult {
811
+ runId: string;
812
+ linked: number;
813
+ proposed: number;
814
+ skipped: number;
815
+ }
816
+ declare function handleIngest(deps: IngestDeps): Promise<IngestResult>;
817
+ /**
818
+ * Map an ingest failure to a stderr line and an exit code, or `null` when it is not ours to explain.
819
+ *
820
+ * The split follows `sync`: something the user can fix here and now exits 2, something that may work
821
+ * on the next attempt exits 1. A malformed or unfinished run is the user's to fix, so it is 2.
822
+ */
823
+ declare function reportIngestFailure(err: unknown, write: (s: string) => void): number | null;
824
+ /** One line per counter, in the order a reader cares about: what landed, what needs them, what did not. */
825
+ declare function formatIngestResult(r: IngestResult): string;
826
+
827
+ /** The payload version the Plune ingest endpoint validates — ours, not Cairn's. */
828
+ declare const INGEST_SCHEMA_VERSION = 1;
829
+ type CairnRunMode = 'explore' | 'design' | 'api';
830
+ type CairnExecution = 'auto' | 'manual';
831
+ type CairnVerdict = 'passed' | 'failed';
832
+ interface CairnIngestCase {
833
+ stableId: string;
834
+ title: string;
835
+ execution: CairnExecution;
836
+ steps: string[];
837
+ expected: string;
838
+ /** Absent means the run never executed this case — NOT "no result yet". */
839
+ verdict?: CairnVerdict;
840
+ technique?: string;
841
+ priority?: string;
842
+ }
843
+ interface CairnIngestPayload {
844
+ schemaVersion: typeof INGEST_SCHEMA_VERSION;
845
+ run: {
846
+ runId: string;
847
+ mode: CairnRunMode;
848
+ target: string;
849
+ };
850
+ cases: CairnIngestCase[];
851
+ }
852
+ interface EvidenceRecord {
853
+ name: string;
854
+ passed: boolean;
855
+ }
856
+ interface ReadCairnRunDeps {
857
+ /** Injected for tests; returns `undefined` when the run executed nothing and wrote no evidence. */
858
+ readEvidence?: (dir: string) => Promise<EvidenceRecord[] | undefined>;
859
+ }
860
+ /**
861
+ * Read a Cairn run directory into an upload payload, or throw a typed error explaining why not.
862
+ *
863
+ * Pure apart from the two file reads, which are injectable, so the mapping rules can be exercised
864
+ * against real artifacts without a filesystem dance.
865
+ */
866
+ declare function readCairnRun(dir: string, deps?: ReadCairnRunDeps): Promise<CairnIngestPayload>;
867
+
868
+ export { type AssertionResultRecord, type BinaryVerdict, type CairnIngestCase, type CairnIngestPayload, type Config, type EvalResult, type IngestDeps, type IngestResult, NotLoggedInError, type ParsedRunResult, RUN_RESULT_SCHEMA_VERSION, type RowResult, type RunError, type RunOptions, type RunResult, type Summary, type SyncDeps, SyncFileError, SyncHttpError, SyncNetworkError, type SyncResult, TokenRejectedError, type Usage, assertionConfigSchema, assertionResultRecordSchema, binaryVerdictSchema, formatIngestResult, handleIngest as ingest, parseRunResult, readCairnRun, reportIngestFailure, reportSyncFailure, handleRun as run, runResultSchema, handleSync as sync };
package/dist/index.d.ts CHANGED
@@ -799,4 +799,70 @@ type BinaryVerdict = z.infer<typeof binaryVerdictSchema>;
799
799
  /** Parse + validate an unknown value as a RunResult. Throws ZodError on failure. */
800
800
  declare function parseRunResult(value: unknown): ParsedRunResult;
801
801
 
802
- export { type AssertionResultRecord, type BinaryVerdict, type Config, type EvalResult, NotLoggedInError, type ParsedRunResult, RUN_RESULT_SCHEMA_VERSION, type RowResult, type RunError, type RunOptions, type RunResult, type Summary, type SyncDeps, SyncFileError, SyncHttpError, SyncNetworkError, type SyncResult, TokenRejectedError, type Usage, assertionConfigSchema, assertionResultRecordSchema, binaryVerdictSchema, parseRunResult, reportSyncFailure, handleRun as run, runResultSchema, handleSync as sync };
802
+ interface IngestDeps {
803
+ /** The Cairn run directory to read. */
804
+ dir: string;
805
+ apiUrl?: string;
806
+ loadToken?: () => string | null;
807
+ fetchImpl?: typeof fetch;
808
+ }
809
+ /** What the server recorded — the three counters that account for every case sent. */
810
+ interface IngestResult {
811
+ runId: string;
812
+ linked: number;
813
+ proposed: number;
814
+ skipped: number;
815
+ }
816
+ declare function handleIngest(deps: IngestDeps): Promise<IngestResult>;
817
+ /**
818
+ * Map an ingest failure to a stderr line and an exit code, or `null` when it is not ours to explain.
819
+ *
820
+ * The split follows `sync`: something the user can fix here and now exits 2, something that may work
821
+ * on the next attempt exits 1. A malformed or unfinished run is the user's to fix, so it is 2.
822
+ */
823
+ declare function reportIngestFailure(err: unknown, write: (s: string) => void): number | null;
824
+ /** One line per counter, in the order a reader cares about: what landed, what needs them, what did not. */
825
+ declare function formatIngestResult(r: IngestResult): string;
826
+
827
+ /** The payload version the Plune ingest endpoint validates — ours, not Cairn's. */
828
+ declare const INGEST_SCHEMA_VERSION = 1;
829
+ type CairnRunMode = 'explore' | 'design' | 'api';
830
+ type CairnExecution = 'auto' | 'manual';
831
+ type CairnVerdict = 'passed' | 'failed';
832
+ interface CairnIngestCase {
833
+ stableId: string;
834
+ title: string;
835
+ execution: CairnExecution;
836
+ steps: string[];
837
+ expected: string;
838
+ /** Absent means the run never executed this case — NOT "no result yet". */
839
+ verdict?: CairnVerdict;
840
+ technique?: string;
841
+ priority?: string;
842
+ }
843
+ interface CairnIngestPayload {
844
+ schemaVersion: typeof INGEST_SCHEMA_VERSION;
845
+ run: {
846
+ runId: string;
847
+ mode: CairnRunMode;
848
+ target: string;
849
+ };
850
+ cases: CairnIngestCase[];
851
+ }
852
+ interface EvidenceRecord {
853
+ name: string;
854
+ passed: boolean;
855
+ }
856
+ interface ReadCairnRunDeps {
857
+ /** Injected for tests; returns `undefined` when the run executed nothing and wrote no evidence. */
858
+ readEvidence?: (dir: string) => Promise<EvidenceRecord[] | undefined>;
859
+ }
860
+ /**
861
+ * Read a Cairn run directory into an upload payload, or throw a typed error explaining why not.
862
+ *
863
+ * Pure apart from the two file reads, which are injectable, so the mapping rules can be exercised
864
+ * against real artifacts without a filesystem dance.
865
+ */
866
+ declare function readCairnRun(dir: string, deps?: ReadCairnRunDeps): Promise<CairnIngestPayload>;
867
+
868
+ export { type AssertionResultRecord, type BinaryVerdict, type CairnIngestCase, type CairnIngestPayload, type Config, type EvalResult, type IngestDeps, type IngestResult, NotLoggedInError, type ParsedRunResult, RUN_RESULT_SCHEMA_VERSION, type RowResult, type RunError, type RunOptions, type RunResult, type Summary, type SyncDeps, SyncFileError, SyncHttpError, SyncNetworkError, type SyncResult, TokenRejectedError, type Usage, assertionConfigSchema, assertionResultRecordSchema, binaryVerdictSchema, formatIngestResult, handleIngest as ingest, parseRunResult, readCairnRun, reportIngestFailure, reportSyncFailure, handleRun as run, runResultSchema, handleSync as sync };
package/dist/index.js CHANGED
@@ -595,9 +595,11 @@ var XenovaEmbedder = class {
595
595
  return rows.map((row) => Float32Array.from(row));
596
596
  }
597
597
  load() {
598
- this.extractor ??= import("@huggingface/transformers").then(
599
- ({ pipeline }) => pipeline("feature-extraction", MODEL_ID)
600
- );
598
+ this.extractor ??= import("@huggingface/transformers").catch(() => {
599
+ throw new Error(
600
+ "The `semantic-similarity` and RAG assertions need local embeddings, which ship separately: install @huggingface/transformers to enable them. Every other assertion works without it."
601
+ );
602
+ }).then(({ pipeline }) => pipeline("feature-extraction", MODEL_ID));
601
603
  return this.extractor;
602
604
  }
603
605
  };
@@ -1651,6 +1653,221 @@ function reportSyncFailure(err, write) {
1651
1653
  }
1652
1654
  return null;
1653
1655
  }
1656
+
1657
+ // src/cli/commands/cairn-artifact.ts
1658
+ import * as fs7 from "fs/promises";
1659
+ import * as path6 from "path";
1660
+ var SUPPORTED_ARTIFACT_VERSION = 1;
1661
+ var INGEST_SCHEMA_VERSION = 1;
1662
+ var CairnRunNotFoundError = class extends Error {
1663
+ constructor(dir) {
1664
+ super(`No Cairn run found in ${dir} \u2014 expected a report.json written by "cairn design|explore|api".`);
1665
+ this.name = "CairnRunNotFoundError";
1666
+ }
1667
+ };
1668
+ var CairnRunVersionError = class extends Error {
1669
+ constructor(found) {
1670
+ super(
1671
+ found === void 0 ? "This run predates versioned Cairn artifacts. Re-run it with Cairn 0.7.0 or newer, which stamps the format so it can be read safely." : `Unsupported Cairn artifact version ${String(found)} (this CLI reads version ${SUPPORTED_ARTIFACT_VERSION}). Upgrade @plune-ai/cli.`
1672
+ );
1673
+ this.name = "CairnRunVersionError";
1674
+ }
1675
+ };
1676
+ var CairnRunPartialError = class extends Error {
1677
+ constructor(reason) {
1678
+ super(`That Cairn run did not finish, so its cases are incomplete: ${reason}`);
1679
+ this.name = "CairnRunPartialError";
1680
+ }
1681
+ };
1682
+ var CairnEvidenceAmbiguousError = class extends Error {
1683
+ constructor(name) {
1684
+ super(
1685
+ `Cannot attach results safely: the operation "${name}" appears more than once in this run's evidence, and the evidence file carries no case identity to disambiguate it. Nothing was uploaded.`
1686
+ );
1687
+ this.name = "CairnEvidenceAmbiguousError";
1688
+ }
1689
+ };
1690
+ var asRecord = (v) => typeof v === "object" && v !== null ? v : {};
1691
+ var str = (v, fallback = "") => typeof v === "string" && v !== "" ? v : fallback;
1692
+ async function readJson(file) {
1693
+ try {
1694
+ return JSON.parse(await fs7.readFile(file, "utf8"));
1695
+ } catch {
1696
+ return void 0;
1697
+ }
1698
+ }
1699
+ async function defaultReadEvidence(dir) {
1700
+ const raw = await readJson(path6.join(dir, "api-evidence.json"));
1701
+ if (!Array.isArray(raw)) return void 0;
1702
+ return raw.map((r) => {
1703
+ const rec = asRecord(r);
1704
+ return { name: str(rec["name"]), passed: rec["passed"] === true };
1705
+ });
1706
+ }
1707
+ function casesFromDesign(report) {
1708
+ const list = Array.isArray(report["testCases"]) ? report["testCases"] : [];
1709
+ return list.map((raw) => {
1710
+ const c = asRecord(raw);
1711
+ const steps = (Array.isArray(c["steps"]) ? c["steps"] : []).map((s) => str(s)).filter((s) => s !== "");
1712
+ return {
1713
+ stableId: str(c["stableId"]),
1714
+ title: str(c["title"], "untitled case"),
1715
+ execution: c["execution"] === "manual" ? "manual" : "auto",
1716
+ steps: steps.length > 0 ? steps : ["(no steps recorded)"],
1717
+ expected: str(c["expected"], "(no expected result recorded)"),
1718
+ ...typeof c["technique"] === "string" ? { technique: c["technique"] } : {},
1719
+ ...typeof c["priority"] === "string" ? { priority: c["priority"] } : {}
1720
+ };
1721
+ });
1722
+ }
1723
+ function casesFromApi(report, verdicts) {
1724
+ const list = Array.isArray(report["cases"]) ? report["cases"] : [];
1725
+ return list.map((raw) => {
1726
+ const c = asRecord(raw);
1727
+ const name = str(c["name"], "unnamed operation");
1728
+ const request = `${str(c["method"], "GET")} ${str(c["path"], "/")}`;
1729
+ const verdict = verdicts.get(name);
1730
+ return {
1731
+ stableId: str(c["stableId"]),
1732
+ title: name,
1733
+ execution: "auto",
1734
+ steps: [request],
1735
+ expected: `HTTP ${str(c["expectedStatus"], "default")}`,
1736
+ ...verdict !== void 0 ? { verdict } : {},
1737
+ ...typeof c["technique"] === "string" ? { technique: c["technique"] } : {}
1738
+ };
1739
+ });
1740
+ }
1741
+ async function readCairnRun(dir, deps = {}) {
1742
+ const raw = await readJson(path6.join(dir, "report.json"));
1743
+ if (raw === void 0) throw new CairnRunNotFoundError(dir);
1744
+ const report = asRecord(raw);
1745
+ if (report["schemaVersion"] !== SUPPORTED_ARTIFACT_VERSION) {
1746
+ throw new CairnRunVersionError(report["schemaVersion"]);
1747
+ }
1748
+ if (report["partial"] === true) {
1749
+ throw new CairnRunPartialError(str(report["error"], "no reason recorded"));
1750
+ }
1751
+ const mode = report["mode"];
1752
+ if (mode !== "design" && mode !== "explore" && mode !== "api") {
1753
+ throw new CairnRunVersionError(`${SUPPORTED_ARTIFACT_VERSION} (unknown run mode ${String(mode)})`);
1754
+ }
1755
+ let cases;
1756
+ if (mode === "api") {
1757
+ const evidence = await (deps.readEvidence ?? defaultReadEvidence)(dir);
1758
+ const verdicts = /* @__PURE__ */ new Map();
1759
+ for (const e of evidence ?? []) {
1760
+ if (verdicts.has(e.name)) throw new CairnEvidenceAmbiguousError(e.name);
1761
+ verdicts.set(e.name, e.passed ? "passed" : "failed");
1762
+ }
1763
+ cases = casesFromApi(report, verdicts);
1764
+ } else {
1765
+ cases = casesFromDesign(report);
1766
+ }
1767
+ return {
1768
+ schemaVersion: INGEST_SCHEMA_VERSION,
1769
+ run: {
1770
+ runId: str(report["runId"], path6.basename(dir)),
1771
+ mode,
1772
+ target: str(report["url"], "unknown")
1773
+ },
1774
+ cases
1775
+ };
1776
+ }
1777
+
1778
+ // src/cli/commands/ingest.ts
1779
+ var DEFAULT_API_URL2 = "https://beta-api.plune.ai";
1780
+ var IngestNotLoggedInError = class extends Error {
1781
+ constructor() {
1782
+ super('Not logged in. Run "plune login" first to save your API token.');
1783
+ this.name = "IngestNotLoggedInError";
1784
+ }
1785
+ };
1786
+ var IngestTokenRejectedError = class extends Error {
1787
+ constructor() {
1788
+ super('Your API token was rejected (401). Run "plune login" again with a fresh token.');
1789
+ this.name = "IngestTokenRejectedError";
1790
+ }
1791
+ };
1792
+ var IngestNetworkError = class extends Error {
1793
+ constructor(apiUrl) {
1794
+ super(`Could not reach the Plune API at ${apiUrl}. Check your connection, then try again.`);
1795
+ this.name = "IngestNetworkError";
1796
+ }
1797
+ };
1798
+ var IngestHttpError = class extends Error {
1799
+ constructor(status, detail) {
1800
+ super(`Plune API returned ${status}${detail !== "" ? ` \u2014 ${detail}` : ""}.`);
1801
+ this.status = status;
1802
+ this.name = "IngestHttpError";
1803
+ }
1804
+ status;
1805
+ };
1806
+ function resolveApiUrl2(explicit) {
1807
+ const raw = explicit ?? process.env["PLUNE_API_URL"] ?? DEFAULT_API_URL2;
1808
+ return raw.replace(/\/+$/, "");
1809
+ }
1810
+ async function safeErrorDetail2(res) {
1811
+ try {
1812
+ const body = await res.json();
1813
+ return typeof body.error === "string" ? body.error : "";
1814
+ } catch {
1815
+ return "";
1816
+ }
1817
+ }
1818
+ async function handleIngest(deps) {
1819
+ const apiUrl = resolveApiUrl2(deps.apiUrl);
1820
+ const token = (deps.loadToken ?? loadToken)();
1821
+ if (token === null) throw new IngestNotLoggedInError();
1822
+ const payload = await readCairnRun(deps.dir);
1823
+ const doFetch = deps.fetchImpl ?? fetch;
1824
+ const endpoint = `${apiUrl}/v1/ingest/cairn`;
1825
+ let res;
1826
+ try {
1827
+ res = await doFetch(endpoint, {
1828
+ method: "POST",
1829
+ headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
1830
+ body: JSON.stringify(payload)
1831
+ });
1832
+ } catch {
1833
+ throw new IngestNetworkError(apiUrl);
1834
+ }
1835
+ if (!res.ok) {
1836
+ const detail = await safeErrorDetail2(res);
1837
+ if (res.status === 401) throw new IngestTokenRejectedError();
1838
+ throw new IngestHttpError(res.status, detail);
1839
+ }
1840
+ const body = await res.json().catch(() => ({}));
1841
+ return {
1842
+ runId: typeof body.runId === "string" ? body.runId : "",
1843
+ linked: typeof body.linked === "number" ? body.linked : 0,
1844
+ proposed: typeof body.proposed === "number" ? body.proposed : 0,
1845
+ skipped: typeof body.skipped === "number" ? body.skipped : 0
1846
+ };
1847
+ }
1848
+ function reportIngestFailure(err, write) {
1849
+ if (err instanceof IngestNotLoggedInError || err instanceof IngestTokenRejectedError || err instanceof CairnRunNotFoundError || err instanceof CairnRunVersionError || err instanceof CairnRunPartialError || err instanceof CairnEvidenceAmbiguousError) {
1850
+ write(`${err.message}
1851
+ `);
1852
+ return 2;
1853
+ }
1854
+ if (err instanceof IngestNetworkError || err instanceof IngestHttpError) {
1855
+ write(`${err.message}
1856
+ `);
1857
+ return 1;
1858
+ }
1859
+ return null;
1860
+ }
1861
+ function formatIngestResult(r) {
1862
+ const lines = [
1863
+ `Ingested Cairn run \u2192 ${r.runId}`,
1864
+ ` ${r.linked} result(s) attached to existing cases`,
1865
+ ` ${r.proposed} case(s) proposed \u2014 review them in Plune to accept`,
1866
+ ` ${r.skipped} skipped (already queued, already rejected, repeated, or never executed)`
1867
+ ];
1868
+ return `${lines.join("\n")}
1869
+ `;
1870
+ }
1654
1871
  export {
1655
1872
  NotLoggedInError,
1656
1873
  RUN_RESULT_SCHEMA_VERSION,
@@ -1661,7 +1878,11 @@ export {
1661
1878
  assertionConfigSchema,
1662
1879
  assertionResultRecordSchema,
1663
1880
  binaryVerdictSchema,
1881
+ formatIngestResult,
1882
+ handleIngest as ingest,
1664
1883
  parseRunResult,
1884
+ readCairnRun,
1885
+ reportIngestFailure,
1665
1886
  reportSyncFailure,
1666
1887
  handleRun as run,
1667
1888
  runResultSchema,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plune-ai/cli",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "AI-powered assertion testing for LLM apps — write assertions, run them against any provider, get a pass/fail report (local, CI, or diff).",
5
5
  "keywords": [
6
6
  "ai",
@@ -53,8 +53,7 @@
53
53
  "dependencies": {
54
54
  "@anthropic-ai/sdk": "^0.102.0",
55
55
  "@clack/prompts": "^1.5.1",
56
- "@huggingface/transformers": "4.2.0",
57
- "ajv": "^8.20.0",
56
+ "ajv": "^8.17.1",
58
57
  "better-sqlite3": "^12.10.0",
59
58
  "commander": "^12.0.0",
60
59
  "dotenv": "^16.4.0",
@@ -63,6 +62,7 @@
63
62
  "zod": "^3.23.0"
64
63
  },
65
64
  "devDependencies": {
65
+ "@huggingface/transformers": "4.2.0",
66
66
  "@types/better-sqlite3": "^7.6.13",
67
67
  "@types/node": "^25.9.1",
68
68
  "@typescript-eslint/eslint-plugin": "^8.0.0",
@@ -96,5 +96,13 @@
96
96
  "protobufjs",
97
97
  "sharp"
98
98
  ]
99
+ },
100
+ "peerDependencies": {
101
+ "@huggingface/transformers": "4.2.0"
102
+ },
103
+ "peerDependenciesMeta": {
104
+ "@huggingface/transformers": {
105
+ "optional": true
106
+ }
99
107
  }
100
108
  }