@plune-ai/cli 0.15.0 → 0.16.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
@@ -75,8 +75,8 @@ var env_exports = {};
75
75
  __export(env_exports, {
76
76
  loadEnv: () => loadEnv
77
77
  });
78
- function loadEnv(configDir) {
79
- (0, import_dotenv.config)({ path: path.join(configDir, ".env"), override: false });
78
+ function loadEnv(dir) {
79
+ (0, import_dotenv.config)({ path: path.join(dir, ".env"), override: false });
80
80
  }
81
81
  var import_dotenv, path;
82
82
  var init_env = __esm({
@@ -523,11 +523,25 @@ var init_prices = __esm({
523
523
  "use strict";
524
524
  init_cjs_shims();
525
525
  PRICE_TABLE = {
526
- // Anthropic
526
+ // Anthropic, current models. Source: platform.claude.com/docs/en/about-claude/pricing — base
527
+ // input and output tokens per million, read on 2026-09-29, kept here per thousand.
528
+ "claude-fable-5-1": { input_per_1k_usd: 0.01, output_per_1k_usd: 0.05 },
529
+ "claude-opus-5-5": { input_per_1k_usd: 4e-3, output_per_1k_usd: 0.02 },
530
+ "claude-sonnet-5-5": { input_per_1k_usd: 2e-3, output_per_1k_usd: 0.01 },
531
+ "claude-haiku-4-5": { input_per_1k_usd: 1e-3, output_per_1k_usd: 5e-3 },
532
+ // The dated id of the same model; a config may name either.
533
+ "claude-haiku-4-5-20251001": { input_per_1k_usd: 1e-3, output_per_1k_usd: 5e-3 },
534
+ // Anthropic, older ids: unchanged, so a config that still names one prices as it did. Anthropic has
535
+ // retired these models — a new project should not start on them.
527
536
  "claude-3-5-haiku-latest": { input_per_1k_usd: 8e-4, output_per_1k_usd: 4e-3 },
528
537
  "claude-3-5-sonnet-latest": { input_per_1k_usd: 3e-3, output_per_1k_usd: 0.015 },
529
538
  "claude-3-opus-latest": { input_per_1k_usd: 0.015, output_per_1k_usd: 0.075 },
530
- // OpenAI
539
+ // OpenAI, current models. Source: developers.openai.com/api/docs/pricing — standard tier, the
540
+ // short-context rates (a long-context request costs more), read on 2026-09-29.
541
+ "gpt-6-astra": { input_per_1k_usd: 0.01, output_per_1k_usd: 0.05 },
542
+ "gpt-6.1-sol": { input_per_1k_usd: 2e-3, output_per_1k_usd: 0.01 },
543
+ "gpt-6-luna": { input_per_1k_usd: 1e-4, output_per_1k_usd: 5e-4 },
544
+ // OpenAI, older ids: unchanged (the same rates the page above still lists for both).
531
545
  "gpt-4o": { input_per_1k_usd: 25e-4, output_per_1k_usd: 0.01 },
532
546
  "gpt-4o-mini": { input_per_1k_usd: 15e-5, output_per_1k_usd: 6e-4 },
533
547
  // OpenAI via OpenRouter (namespaced ids). OpenRouter passes OpenAI list price through for
@@ -776,6 +790,25 @@ var init_providers = __esm({
776
790
  }
777
791
  });
778
792
 
793
+ // src/providers/estimate.ts
794
+ function makeEstimateProvider(config, pricing) {
795
+ return {
796
+ complete: () => Promise.reject(
797
+ new Error("The --dry-run provider only estimates cost; it never calls a model.")
798
+ ),
799
+ estimateCost: (usage, reportedCostUsd) => ({
800
+ cost_usd: resolveCost(usage, config.model, reportedCostUsd, pricing)
801
+ })
802
+ };
803
+ }
804
+ var init_estimate = __esm({
805
+ "src/providers/estimate.ts"() {
806
+ "use strict";
807
+ init_cjs_shims();
808
+ init_cost();
809
+ }
810
+ });
811
+
779
812
  // src/providers/mock.ts
780
813
  function makeMockProvider() {
781
814
  return {
@@ -1878,11 +1911,16 @@ __export(run_exports, {
1878
1911
  function isMockMode(env) {
1879
1912
  return env["PLUNE_MOCK_PROVIDER"] === "1";
1880
1913
  }
1914
+ function resolveProviderFor(config, dryRun) {
1915
+ if (isMockMode(process.env)) return () => makeMockProvider();
1916
+ if (dryRun) return (cfg) => makeEstimateProvider(cfg, config.pricing);
1917
+ return (cfg) => getProvider(cfg, process.env, config.pricing);
1918
+ }
1881
1919
  function buildRealDeps(config, baseDir, dryRun) {
1882
1920
  const dir = path5.join(baseDir, ".plune");
1883
1921
  fs5.mkdirSync(dir, { recursive: true });
1884
1922
  return {
1885
- resolveProvider: isMockMode(process.env) ? () => makeMockProvider() : (cfg) => getProvider(cfg, process.env, config.pricing),
1923
+ resolveProvider: resolveProviderFor(config, dryRun),
1886
1924
  embedder: getDefaultEmbedder(),
1887
1925
  cache: dryRun ? NOOP_CACHE : openCache(path5.join(dir, "cache.db")),
1888
1926
  now: Date.now,
@@ -1929,6 +1967,7 @@ var init_run2 = __esm({
1929
1967
  path5 = __toESM(require("path"), 1);
1930
1968
  init_loader();
1931
1969
  init_providers();
1970
+ init_estimate();
1932
1971
  init_mock();
1933
1972
  init_embeddings();
1934
1973
  init_cache2();
@@ -2241,7 +2280,7 @@ function createClient(opts) {
2241
2280
  const wait = opts.wait ?? sleep;
2242
2281
  const base = opts.apiUrl.replace(/\/+$/, "");
2243
2282
  const scrub = (text2) => opts.token === "" ? text2 : text2.split(opts.token).join("***");
2244
- async function send(method, path14, payload) {
2283
+ async function send(method, path15, payload) {
2245
2284
  let last = {
2246
2285
  kind: "unavailable",
2247
2286
  status: null,
@@ -2250,7 +2289,7 @@ function createClient(opts) {
2250
2289
  for (let attempt = 0; attempt <= RETRY_DELAYS_MS.length; attempt += 1) {
2251
2290
  let res;
2252
2291
  try {
2253
- res = await doFetch(`${base}${path14}`, {
2292
+ res = await doFetch(`${base}${path15}`, {
2254
2293
  method,
2255
2294
  headers: { "content-type": payload?.type ?? "application/json", authorization: `Bearer ${opts.token}` },
2256
2295
  // A DELETE carries none. Sending `"undefined"` as a body is what a naive
@@ -2282,9 +2321,9 @@ function createClient(opts) {
2282
2321
  return { ok: false, ...last };
2283
2322
  }
2284
2323
  return {
2285
- post: (path14, body) => send("POST", path14, { body: JSON.stringify(body), type: "application/json" }),
2286
- upload: (path14, bytes, contentType) => send("POST", path14, { body: bytes, type: contentType }),
2287
- del: (path14) => send("DELETE", path14)
2324
+ post: (path15, body) => send("POST", path15, { body: JSON.stringify(body), type: "application/json" }),
2325
+ upload: (path15, bytes, contentType) => send("POST", path15, { body: bytes, type: contentType }),
2326
+ del: (path15) => send("DELETE", path15)
2288
2327
  };
2289
2328
  }
2290
2329
  var import_node_http, import_node_https, RETRY_DELAYS_MS, IDLE_MS, sleep;
@@ -2360,13 +2399,35 @@ function defaultRunTitle(env = process.env, now = /* @__PURE__ */ new Date(), cw
2360
2399
  const where = flag(env, "CI") || flag(env, "GITHUB_ACTIONS") ? "ci" : "local";
2361
2400
  return `${path6.basename(cwd)} \xB7 ${stamp} \xB7 ${where}`;
2362
2401
  }
2363
- var path6, UNSUPPORTED_VARS;
2402
+ function ciCommit(reported, env = process.env) {
2403
+ const said = typeof reported === "object" && reported !== null ? reported : {};
2404
+ const text2 = (raw) => typeof raw === "string" && raw.trim() !== "" ? raw.trim() : void 0;
2405
+ const provider = PROVIDERS.find((p) => value(env, p.is) !== void 0);
2406
+ const job = {
2407
+ sha: provider === void 0 ? void 0 : value(env, provider.sha),
2408
+ branch: provider?.branch.map((name) => value(env, name)).find((v) => v !== void 0)
2409
+ };
2410
+ const sha = text2(said["commitHash"]);
2411
+ const sameCommit = sha === void 0 || sha === job.sha;
2412
+ const commit = sha ?? job.sha;
2413
+ const branch = text2(said["branch"]) ?? (sameCommit ? job.branch : void 0);
2414
+ return {
2415
+ ...commit !== void 0 ? { sha: commit } : {},
2416
+ ...branch !== void 0 ? { branch } : {}
2417
+ };
2418
+ }
2419
+ var path6, UNSUPPORTED_VARS, PROVIDERS;
2364
2420
  var init_env2 = __esm({
2365
2421
  "src/reporter-core/env.ts"() {
2366
2422
  "use strict";
2367
2423
  init_cjs_shims();
2368
2424
  path6 = __toESM(require("path"), 1);
2369
2425
  UNSUPPORTED_VARS = ["PLUNE_GROUP"];
2426
+ PROVIDERS = [
2427
+ { is: "GITHUB_ACTIONS", sha: "GITHUB_SHA", branch: ["GITHUB_HEAD_REF", "GITHUB_REF_NAME"] },
2428
+ { is: "GITLAB_CI", sha: "CI_COMMIT_SHA", branch: ["CI_COMMIT_REF_NAME"] },
2429
+ { is: "JENKINS_URL", sha: "GIT_COMMIT", branch: ["GIT_BRANCH"] }
2430
+ ];
2370
2431
  }
2371
2432
  });
2372
2433
 
@@ -2377,6 +2438,8 @@ function appendBatch(file, batch) {
2377
2438
  ts: (/* @__PURE__ */ new Date()).toISOString(),
2378
2439
  runId: batch.runId,
2379
2440
  externalKey: batch.externalKey,
2441
+ ...batch.session !== void 0 ? { session: batch.session } : {},
2442
+ ...batch.meta !== void 0 ? { meta: batch.meta } : {},
2380
2443
  results: batch.results
2381
2444
  };
2382
2445
  fs6.appendFileSync(file, `${JSON.stringify(line)}
@@ -2414,6 +2477,12 @@ function boundKey(key) {
2414
2477
  if (/[\uD800-\uDBFF]$/.test(head)) head = head.slice(0, -1);
2415
2478
  return { ...key, value: `${head}#${digest}` };
2416
2479
  }
2480
+ function whyUnoffered(out) {
2481
+ if (out.ok || out.status !== 429) return "other";
2482
+ if (/review queue quota reached/i.test(out.detail)) return "queue";
2483
+ if (/test case quota reached/i.test(out.detail)) return "cases";
2484
+ return "other";
2485
+ }
2417
2486
  function boundTitle(title) {
2418
2487
  return title.length <= TITLE_MAX ? title : `${title.slice(0, TITLE_MAX - 1)}\u2026`;
2419
2488
  }
@@ -2455,6 +2524,7 @@ async function startRun(passed, declared) {
2455
2524
  `));
2456
2525
  const fallbackPath = cfg.fallbackPath ?? DEFAULT_FALLBACK_PATH;
2457
2526
  const externalKey = cfg.externalKey ?? null;
2527
+ const sessionMarker = (0, import_node_crypto2.randomUUID)();
2458
2528
  const batchSize = Math.min(cfg.batchSize ?? BATCH_DEFAULT, BATCH_MAX);
2459
2529
  const offerDiscovered = cfg.offerDiscovered ?? false;
2460
2530
  const token = cfg.token ?? loadToken() ?? "";
@@ -2506,7 +2576,13 @@ async function startRun(passed, declared) {
2506
2576
  log(`plune: ignoring ${env.ignored.join(", ")} \u2014 a run has nowhere to store them yet.`);
2507
2577
  }
2508
2578
  function defer(results) {
2509
- appendBatch(fallbackPath, { runId, externalKey, results });
2579
+ appendBatch(fallbackPath, {
2580
+ runId,
2581
+ externalKey,
2582
+ session: sessionMarker,
2583
+ ...cfg.meta !== void 0 ? { meta: cfg.meta } : {},
2584
+ results
2585
+ });
2510
2586
  stats.deferred += results.length;
2511
2587
  }
2512
2588
  async function resolveKeys(values) {
@@ -2603,6 +2679,7 @@ async function startRun(passed, declared) {
2603
2679
  });
2604
2680
  if (!out.ok || !Array.isArray(out.body?.results)) {
2605
2681
  stats.unoffered = batches.slice(index).reduce((n, rest) => n + rest.length, 0);
2682
+ stats.unofferedWhy = whyUnoffered(out);
2606
2683
  log(
2607
2684
  `plune: could not offer ${stats.unoffered} unknown test(s) for review (${out.ok ? "the answer had no results" : out.detail || out.kind}).`
2608
2685
  );
@@ -2959,23 +3036,42 @@ async function handleRunReport(options = {}) {
2959
3036
  if (lines.length === 0) throw new RunCommandError(`Nothing to send \u2014 ${file} is empty.`);
2960
3037
  const { client } = connect(options);
2961
3038
  const result = { batches: lines.length, sent: 0, failed: 0 };
3039
+ const groups = [];
3040
+ const bySession = /* @__PURE__ */ new Map();
2962
3041
  for (const line of lines) {
2963
- const pending = line.results.filter(isPending);
2964
- const ready = line.results.filter((r) => !isPending(r));
2965
- if (line.runId !== null && ready.length > 0) {
2966
- const out = await client.post(`/v1/runs/${line.runId}/results`, { results: ready });
2967
- if (out.ok) result.sent += ready.length;
2968
- else {
2969
- result.failed += ready.length;
2970
- write(`Could not send ${ready.length} result(s) to run ${line.runId} \u2014 ${out.detail || out.kind}.`);
3042
+ const group = line.session === void 0 ? void 0 : bySession.get(line.session);
3043
+ if (group !== void 0) {
3044
+ group.push(line);
3045
+ continue;
3046
+ }
3047
+ const alone = [line];
3048
+ groups.push(alone);
3049
+ if (line.session !== void 0) bySession.set(line.session, alone);
3050
+ }
3051
+ for (const group of groups) {
3052
+ for (const line of group) {
3053
+ const ready = line.results.filter((r) => !isPending(r));
3054
+ if (line.runId !== null && ready.length > 0) {
3055
+ const out = await client.post(`/v1/runs/${line.runId}/results`, { results: ready });
3056
+ if (out.ok) result.sent += ready.length;
3057
+ else {
3058
+ result.failed += ready.length;
3059
+ write(
3060
+ `Could not send ${ready.length} result(s) to run ${line.runId} \u2014 ${out.detail || out.kind}.`
3061
+ );
3062
+ }
2971
3063
  }
2972
3064
  }
3065
+ const pending = group.flatMap((line) => line.results.filter(isPending));
2973
3066
  if (pending.length > 0) {
3067
+ const first = group[0];
2974
3068
  const session = await startRun({
2975
3069
  ...options.apiUrl !== void 0 ? { apiUrl: options.apiUrl } : {},
2976
3070
  ...options.token !== void 0 ? { token: options.token } : {},
2977
3071
  ...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {},
2978
- ...line.externalKey !== null ? { externalKey: line.externalKey } : {},
3072
+ ...first.externalKey !== null ? { externalKey: first.externalKey } : {},
3073
+ // Where the run came from, as the reporter recorded it (plune#927) — never this machine's own.
3074
+ ...first.meta !== void 0 ? { meta: first.meta } : {},
2979
3075
  // The replay does not know whether the run is complete, so it must not say that it is.
2980
3076
  log: write
2981
3077
  });
@@ -2986,15 +3082,34 @@ async function handleRunReport(options = {}) {
2986
3082
  }
2987
3083
  }
2988
3084
  write(`Replayed ${result.batches} batch(es) from ${file}: ${result.sent} sent, ${result.failed} not.`);
3085
+ if (result.failed === 0) {
3086
+ const sentAs = sentName(file, (options.now ?? Date.now)());
3087
+ try {
3088
+ fs7.renameSync(file, sentAs);
3089
+ write(
3090
+ `Every result was sent \u2014 ${file} is now ${sentAs}, so a later replay does not send them twice.`
3091
+ );
3092
+ } catch (err) {
3093
+ const why = err instanceof Error ? err.message : String(err);
3094
+ write(
3095
+ `Every result was sent, but ${file} could not be renamed (${why}) \u2014 take it away by hand, or a later replay sends them again.`
3096
+ );
3097
+ }
3098
+ }
2989
3099
  return result;
2990
3100
  }
2991
- var import_node_child_process, fs7, NoTokenError, RunCommandError, isPending;
3101
+ function sentName(file, at) {
3102
+ const { dir, name, ext } = path8.parse(file);
3103
+ return path8.join(dir, `${name}.${new Date(at).toISOString().replace(/[:.]/g, "-")}.sent${ext}`);
3104
+ }
3105
+ var import_node_child_process, fs7, path8, NoTokenError, RunCommandError, isPending;
2992
3106
  var init_run_lifecycle = __esm({
2993
3107
  "src/cli/commands/run-lifecycle.ts"() {
2994
3108
  "use strict";
2995
3109
  init_cjs_shims();
2996
3110
  import_node_child_process = require("child_process");
2997
3111
  fs7 = __toESM(require("fs"), 1);
3112
+ path8 = __toESM(require("path"), 1);
2998
3113
  init_api_url();
2999
3114
  init_credentials();
3000
3115
  init_client();
@@ -3202,7 +3317,12 @@ function errorTextOf(detail) {
3202
3317
  const head = [detail.attrs["message"], detail.attrs["type"]].filter((s) => s !== void 0 && s !== "");
3203
3318
  return [...head, detail.text.trim()].filter((s) => s !== "").join("\n\n");
3204
3319
  }
3205
- function keyOf(classname, name) {
3320
+ function playwrightOf(classname, name) {
3321
+ if (!SPEC_FILE.test(classname)) return void 0;
3322
+ return { file: classname.replaceAll("\\", "/"), titles: name.split(" \u203A ") };
3323
+ }
3324
+ function keyOf(classname, name, playwright) {
3325
+ if (playwright !== void 0) return { kind: "path-title", value: [playwright.file, ...playwright.titles].join("#") };
3206
3326
  return { kind: "path-title", value: classname === "" ? name : `${classname}#${name}` };
3207
3327
  }
3208
3328
  function titleOf(classname, name) {
@@ -3242,12 +3362,15 @@ function readJUnit(source, file) {
3242
3362
  const name = test.attrs["name"] ?? "";
3243
3363
  const classname = test.attrs["classname"] ?? test.attrs["class"] ?? "";
3244
3364
  const { status, detail } = outcomeOf(test);
3245
- const key = keyOf(classname, name);
3365
+ const playwright = playwrightOf(classname, name);
3366
+ const key = keyOf(classname, name, playwright);
3367
+ const stated = TOKEN.exec(playwright?.titles.at(-1) ?? name)?.[1];
3246
3368
  const specRef = specRefOf(test, suite, classname);
3247
3369
  const errorContext = errorTextOf(detail);
3248
3370
  const execution = executionOf(test, suite);
3249
3371
  return {
3250
3372
  resultKey: resultKey(key.value, 0),
3373
+ ...stated !== void 0 ? { testCaseId: stated } : {},
3251
3374
  keys: [key],
3252
3375
  source: SOURCE,
3253
3376
  rawStatus: status,
@@ -3261,7 +3384,7 @@ function readJUnit(source, file) {
3261
3384
  function looksLikeJUnit(source) {
3262
3385
  return /<\s*testsuites?\b/.test(source.slice(0, 4096));
3263
3386
  }
3264
- var SOURCE;
3387
+ var SOURCE, TOKEN, SPEC_FILE;
3265
3388
  var init_junit = __esm({
3266
3389
  "src/importers/junit.ts"() {
3267
3390
  "use strict";
@@ -3269,6 +3392,8 @@ var init_junit = __esm({
3269
3392
  init_result_key();
3270
3393
  init_xml();
3271
3394
  SOURCE = "junit";
3395
+ TOKEN = /@P([A-Za-z0-9_-]+)/;
3396
+ SPEC_FILE = /\.(?:spec|test)\.[cm]?[jt]sx?$/;
3272
3397
  }
3273
3398
  });
3274
3399
 
@@ -3312,12 +3437,12 @@ function machineless(raw, repoRoot, home) {
3312
3437
  if (repoRoot === void 0) text2 = text2.replace(ANY_HOME, "~");
3313
3438
  return text2;
3314
3439
  }
3315
- function pathIn(path14) {
3316
- const parts = path14.split(/[\\/]+/);
3440
+ function pathIn(path15) {
3441
+ const parts = path15.split(/[\\/]+/);
3317
3442
  while (parts.length > 1 && parts.at(-1) === "") parts.pop();
3318
3443
  if (!parts.some((part) => part !== "" && !/^[A-Za-z]:$/.test(part))) return void 0;
3319
3444
  const joined = parts.map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("[\\\\/]+");
3320
- const escaped = /^\/(?!\/)/.test(path14) ? joined.replace("[\\\\/]+", "\\\\*\\/") : joined;
3445
+ const escaped = /^\/(?!\/)/.test(path15) ? joined.replace("[\\\\/]+", "\\\\*\\/") : joined;
3321
3446
  const url = "(?:(?:file|webpack|webpack-internal):(?:\\\\*\\/){2,3})?";
3322
3447
  return new RegExp(`${url}${escaped}`, /^[A-Za-z]:$/.test(parts[0]) ? "gi" : "g");
3323
3448
  }
@@ -3362,12 +3487,12 @@ function placeOf(error, testFile) {
3362
3487
  }
3363
3488
  function fileOf(raw) {
3364
3489
  if (!raw.startsWith("file://")) return raw;
3365
- let path14 = raw.slice("file://".length);
3490
+ let path15 = raw.slice("file://".length);
3366
3491
  try {
3367
- path14 = decodeURIComponent(path14);
3492
+ path15 = decodeURIComponent(path15);
3368
3493
  } catch {
3369
3494
  }
3370
- return /^\/[A-Za-z]:\//.test(path14) ? path14.slice(1) : path14;
3495
+ return /^\/[A-Za-z]:\//.test(path15) ? path15.slice(1) : path15;
3371
3496
  }
3372
3497
  function fromRoot(place, root) {
3373
3498
  if (place === void 0 || root === void 0 || !Number.isInteger(place.line) || place.line < 1) return void 0;
@@ -3403,7 +3528,7 @@ var init_failure_detail = __esm({
3403
3528
  FRAME = /^\s+at (?:.*? \()?(.+?):(\d+):(\d+)\)?$/;
3404
3529
  FRAME_MAX = 8192;
3405
3530
  firstLine = (text2) => (0, import_node_util.stripVTControlCharacters)(text2).split("\n").map((line) => line.trim()).find((line) => line !== "");
3406
- normal = (path14) => import_node_path3.posix.normalize(path14.replace(/\\/g, "/")).replace(/^([a-z]):/, (_, drive) => `${drive.toUpperCase()}:`);
3531
+ normal = (path15) => import_node_path3.posix.normalize(path15.replace(/\\/g, "/")).replace(/^([a-z]):/, (_, drive) => `${drive.toUpperCase()}:`);
3407
3532
  }
3408
3533
  });
3409
3534
 
@@ -3411,7 +3536,7 @@ var init_failure_detail = __esm({
3411
3536
  function statedIdOf(test, title) {
3412
3537
  const annotation = test.annotations?.find((a) => a.type === "PluneId")?.description;
3413
3538
  if (annotation !== void 0 && annotation !== "") return annotation;
3414
- return TOKEN.exec(title)?.[1];
3539
+ return TOKEN2.exec(title)?.[1];
3415
3540
  }
3416
3541
  function attemptOf(result, file, ctx) {
3417
3542
  const steps = (list2) => (list2 ?? []).map((s) => ({ title: s.title ?? "", failed: s.error !== void 0, steps: steps(s.steps) }));
@@ -3422,10 +3547,10 @@ function attemptOf(result, file, ctx) {
3422
3547
  ...typeof at?.file === "string" && typeof at.line === "number" ? { location: { file: at.file, line: at.line, ...typeof at.column === "number" ? { column: at.column } : {} } } : {}
3423
3548
  })),
3424
3549
  steps: steps(result.steps),
3425
- attachments: (result.attachments ?? []).map(({ name, contentType, path: path14 }) => ({
3550
+ attachments: (result.attachments ?? []).map(({ name, contentType, path: path15 }) => ({
3426
3551
  name: name ?? "",
3427
3552
  ...contentType !== void 0 ? { contentType } : {},
3428
- ...path14 !== void 0 ? { path: path14 } : {}
3553
+ ...path15 !== void 0 ? { path: path15 } : {}
3429
3554
  })),
3430
3555
  ...ctx.buildHref !== void 0 ? { buildHref: ctx.buildHref } : {},
3431
3556
  // Spec files are posix and relative to the root dir; the core reads either slash.
@@ -3507,12 +3632,17 @@ function readPlaywrightJson(source, file) {
3507
3632
  const out = [];
3508
3633
  for (const suite of suites) walk(suite, [], 0, ctx, out);
3509
3634
  const ciUrl = webLink(ctx.buildHref);
3510
- return ciUrl === void 0 ? { results: out } : { results: out, ciUrl };
3635
+ const ci = config?.metadata?.ci;
3636
+ return {
3637
+ results: out,
3638
+ ...ciUrl !== void 0 ? { ciUrl } : {},
3639
+ ...ci !== void 0 ? { ci } : {}
3640
+ };
3511
3641
  }
3512
3642
  function looksLikePlaywrightJson(source) {
3513
3643
  return /^\s*\{/.test(source) && source.includes('"suites"');
3514
3644
  }
3515
- var import_node_fs4, SOURCE2, TOKEN, NOT_FAILED, JsonReportError;
3645
+ var import_node_fs4, SOURCE2, TOKEN2, NOT_FAILED, JsonReportError;
3516
3646
  var init_playwright_json = __esm({
3517
3647
  "src/importers/playwright-json.ts"() {
3518
3648
  "use strict";
@@ -3521,7 +3651,7 @@ var init_playwright_json = __esm({
3521
3651
  init_failure_detail();
3522
3652
  init_result_key();
3523
3653
  SOURCE2 = "playwright";
3524
- TOKEN = /@P([A-Za-z0-9_-]+)/;
3654
+ TOKEN2 = /@P([A-Za-z0-9_-]+)/;
3525
3655
  NOT_FAILED = /* @__PURE__ */ new Set(["passed", "skipped"]);
3526
3656
  JsonReportError = class extends Error {
3527
3657
  constructor(file, detail) {
@@ -3590,7 +3720,7 @@ async function handleRunImport(options) {
3590
3720
  } catch {
3591
3721
  throw new RunCommandError(`Cannot read ${options.file}.`);
3592
3722
  }
3593
- const { format, results, ciUrl } = readReport(source, options.file, options.format);
3723
+ const { format, results, ciUrl, ci } = readReport(source, options.file, options.format);
3594
3724
  if (results.length === 0) {
3595
3725
  throw new RunCommandError(`No test cases in ${options.file} \u2014 it parsed, but there is nothing in it.`);
3596
3726
  }
@@ -3607,7 +3737,10 @@ async function handleRunImport(options) {
3607
3737
  ...options.create === true ? { offerDiscovered: true } : {},
3608
3738
  kind: "automated",
3609
3739
  // The CI run the report names, never this machine's: whoever imports may be another job (#790).
3610
- meta: { runner: format, ...ciUrl !== void 0 ? { ciUrl } : {} },
3740
+ // The commit and branch likewise, where the report names a commit; a report that names none —
3741
+ // JUnit's never does — takes this job's own, which is where the tests ran when import follows
3742
+ // them in one job (plune-ai/plune#927).
3743
+ meta: { runner: format, ...ciUrl !== void 0 ? { ciUrl } : {}, ...ciCommit(ci) },
3611
3744
  log: write
3612
3745
  },
3613
3746
  // The file is the complete list of what ran, so the platform can resolve every test in one
@@ -3631,6 +3764,7 @@ async function handleRunImport(options) {
3631
3764
  offered: stats.offered,
3632
3765
  created: stats.created,
3633
3766
  unoffered: stats.unoffered,
3767
+ ...stats.unofferedWhy !== void 0 ? { unofferedWhy: stats.unofferedWhy } : {},
3634
3768
  deferred: stats.deferred,
3635
3769
  screenshots: { ...stats.screenshots },
3636
3770
  unlocatable: results.filter((r) => r.specRef === void 0).length,
@@ -3681,7 +3815,7 @@ function describe(result, apiUrl) {
3681
3815
  }
3682
3816
  if (result.unoffered > 0) {
3683
3817
  lines.push(
3684
- `${result.unoffered} more could not be offered \u2014 the review queue is full. Approve or reject what is waiting, then import this report again to offer the rest.`
3818
+ `${result.unoffered} more could not be offered \u2014 ${NEXT[result.unofferedWhy ?? "other"]}`
3685
3819
  );
3686
3820
  }
3687
3821
  if (result.unlocatable > 0) {
@@ -3692,7 +3826,7 @@ function describe(result, apiUrl) {
3692
3826
  if (result.runId !== null) lines.push(runUrl2(apiUrl, result.runId));
3693
3827
  return lines;
3694
3828
  }
3695
- var fs8;
3829
+ var fs8, NEXT;
3696
3830
  var init_run_import = __esm({
3697
3831
  "src/cli/commands/run-import.ts"() {
3698
3832
  "use strict";
@@ -3704,6 +3838,11 @@ var init_run_import = __esm({
3704
3838
  init_api_url();
3705
3839
  init_credentials();
3706
3840
  init_env2();
3841
+ NEXT = {
3842
+ queue: "the review queue is full. Approve or reject what is waiting, then import this report again to offer the rest.",
3843
+ cases: "the project is at its test case limit. Delete cases you no longer need, or ask an operator to raise the limit, then import this report again to offer the rest.",
3844
+ other: 'the reason is in the "could not offer" line above.'
3845
+ };
3707
3846
  }
3708
3847
  });
3709
3848
 
@@ -3720,7 +3859,7 @@ function isRunResult(v) {
3720
3859
  }
3721
3860
  function handleReport(opts = {}) {
3722
3861
  const cwd = opts.cwd ?? process.cwd();
3723
- const file = path8.join(cwd, ".plune", "last-run.json");
3862
+ const file = path9.join(cwd, ".plune", "last-run.json");
3724
3863
  let raw;
3725
3864
  try {
3726
3865
  raw = fs9.readFileSync(file, "utf8");
@@ -3738,13 +3877,13 @@ function handleReport(opts = {}) {
3738
3877
  }
3739
3878
  return parsed;
3740
3879
  }
3741
- var fs9, path8, ReportNotFoundError;
3880
+ var fs9, path9, ReportNotFoundError;
3742
3881
  var init_report = __esm({
3743
3882
  "src/cli/commands/report.ts"() {
3744
3883
  "use strict";
3745
3884
  init_cjs_shims();
3746
3885
  fs9 = __toESM(require("fs"), 1);
3747
- path8 = __toESM(require("path"), 1);
3886
+ path9 = __toESM(require("path"), 1);
3748
3887
  ReportNotFoundError = class extends Error {
3749
3888
  code = "NO_SAVED_RUN";
3750
3889
  constructor(message) {
@@ -3948,7 +4087,7 @@ async function runInitWizard(cwd) {
3948
4087
  process.exit(1);
3949
4088
  return;
3950
4089
  }
3951
- const defaultModel = provider === "anthropic" ? "claude-3-opus" : "gpt-4o";
4090
+ const defaultModel = provider === "anthropic" ? "claude-sonnet-5-5" : "gpt-4o-mini";
3952
4091
  const model = await clack.text({
3953
4092
  message: "Model name:",
3954
4093
  defaultValue: defaultModel,
@@ -3968,7 +4107,7 @@ async function runInitWizard(cwd) {
3968
4107
  process.exit(1);
3969
4108
  return;
3970
4109
  }
3971
- const outputPath = path9.join(cwd, "plune.yaml");
4110
+ const outputPath = path10.join(cwd, "plune.yaml");
3972
4111
  if (fs11.existsSync(outputPath)) {
3973
4112
  const overwrite = await clack.confirm({
3974
4113
  message: "plune.yaml already exists. Overwrite?"
@@ -4012,14 +4151,14 @@ async function runInitWizard(cwd) {
4012
4151
  spin.stop("plune.yaml created!");
4013
4152
  clack.outro("plune.yaml is ready.");
4014
4153
  }
4015
- var clack, fs11, path9, import_yaml2;
4154
+ var clack, fs11, path10, import_yaml2;
4016
4155
  var init_wizard = __esm({
4017
4156
  "src/config/init/wizard.ts"() {
4018
4157
  "use strict";
4019
4158
  init_cjs_shims();
4020
4159
  clack = __toESM(require("@clack/prompts"), 1);
4021
4160
  fs11 = __toESM(require("fs"), 1);
4022
- path9 = __toESM(require("path"), 1);
4161
+ path10 = __toESM(require("path"), 1);
4023
4162
  import_yaml2 = require("yaml");
4024
4163
  init_errors();
4025
4164
  }
@@ -4027,21 +4166,21 @@ var init_wizard = __esm({
4027
4166
 
4028
4167
  // src/cli/templates/index.ts
4029
4168
  function writeTemplateFile(cwd, rel, content, force) {
4030
- const target = path10.join(cwd, rel);
4169
+ const target = path11.join(cwd, rel);
4031
4170
  if (!force && fs12.existsSync(target)) {
4032
4171
  return "skipped";
4033
4172
  }
4034
- fs12.mkdirSync(path10.dirname(target), { recursive: true });
4173
+ fs12.mkdirSync(path11.dirname(target), { recursive: true });
4035
4174
  fs12.writeFileSync(target, content, "utf8");
4036
4175
  return "written";
4037
4176
  }
4038
- var fs12, path10, ENV_EXAMPLE_TPL, EXAMPLE_JSONL_TPL, PLUNE_YAML_TPL;
4177
+ var fs12, path11, ENV_EXAMPLE_TPL, EXAMPLE_JSONL_TPL, PLUNE_YAML_TPL;
4039
4178
  var init_templates = __esm({
4040
4179
  "src/cli/templates/index.ts"() {
4041
4180
  "use strict";
4042
4181
  init_cjs_shims();
4043
4182
  fs12 = __toESM(require("fs"), 1);
4044
- path10 = __toESM(require("path"), 1);
4183
+ path11 = __toESM(require("path"), 1);
4045
4184
  ENV_EXAMPLE_TPL = `# Copy this file to .env and replace the placeholders with your real keys.
4046
4185
  # .env is gitignored; .env.example is safe to commit.
4047
4186
  # Plune reads whichever key matches your configured provider.
@@ -4062,7 +4201,7 @@ OPENROUTER_API_KEY=YOUR_OPENROUTER_API_KEY_HERE
4062
4201
  version: 1
4063
4202
  provider:
4064
4203
  type: anthropic
4065
- model: claude-3-5-sonnet-latest
4204
+ model: claude-sonnet-5-5
4066
4205
  evals:
4067
4206
  - id: example
4068
4207
  prompt: "Answer concisely. {{question}}"
@@ -4297,7 +4436,7 @@ __export(sync_exports, {
4297
4436
  reportSyncFailure: () => reportSyncFailure
4298
4437
  });
4299
4438
  function readRun(cwd, file) {
4300
- const target = file !== void 0 ? path11.resolve(cwd, file) : path11.join(cwd, ".plune", "last-run.json");
4439
+ const target = file !== void 0 ? path12.resolve(cwd, file) : path12.join(cwd, ".plune", "last-run.json");
4301
4440
  let raw;
4302
4441
  try {
4303
4442
  raw = fs13.readFileSync(target, "utf8");
@@ -4364,13 +4503,13 @@ function reportSyncFailure(err, write) {
4364
4503
  }
4365
4504
  return null;
4366
4505
  }
4367
- var fs13, path11, NotLoggedInError, TokenRejectedError2, SyncFileError, SyncNetworkError, SyncHttpError;
4506
+ var fs13, path12, NotLoggedInError, TokenRejectedError2, SyncFileError, SyncNetworkError, SyncHttpError;
4368
4507
  var init_sync = __esm({
4369
4508
  "src/cli/commands/sync.ts"() {
4370
4509
  "use strict";
4371
4510
  init_cjs_shims();
4372
4511
  fs13 = __toESM(require("fs"), 1);
4373
- path11 = __toESM(require("path"), 1);
4512
+ path12 = __toESM(require("path"), 1);
4374
4513
  init_run_result_schema();
4375
4514
  init_api_url();
4376
4515
  init_credentials();
@@ -4420,9 +4559,9 @@ async function findLatestCairnRun(runsDir = "runs") {
4420
4559
  let newest;
4421
4560
  for (const entry of entries) {
4422
4561
  if (!entry.isDirectory()) continue;
4423
- const dir = path12.join(runsDir, entry.name);
4562
+ const dir = path13.join(runsDir, entry.name);
4424
4563
  try {
4425
- const report = await fs14.lstat(path12.join(dir, "report.json"));
4564
+ const report = await fs14.lstat(path13.join(dir, "report.json"));
4426
4565
  if (!report.isFile()) continue;
4427
4566
  if (newest === void 0 || report.mtimeMs > newest.at) {
4428
4567
  newest = { dir, at: report.mtimeMs };
@@ -4441,7 +4580,7 @@ async function readJson(file) {
4441
4580
  }
4442
4581
  }
4443
4582
  async function defaultReadEvidence(dir) {
4444
- const raw = await readJson(path12.join(dir, "api-evidence.json"));
4583
+ const raw = await readJson(path13.join(dir, "api-evidence.json"));
4445
4584
  if (!Array.isArray(raw)) return void 0;
4446
4585
  return raw.map((r) => {
4447
4586
  const rec = asRecord(r);
@@ -4483,7 +4622,7 @@ function casesFromApi(report, verdicts) {
4483
4622
  });
4484
4623
  }
4485
4624
  async function readCairnRun(dir, deps = {}) {
4486
- const raw = await readJson(path12.join(dir, "report.json"));
4625
+ const raw = await readJson(path13.join(dir, "report.json"));
4487
4626
  if (raw === void 0) throw new CairnRunNotFoundError(dir);
4488
4627
  const report = asRecord(raw);
4489
4628
  if (report["schemaVersion"] !== SUPPORTED_ARTIFACT_VERSION) {
@@ -4513,20 +4652,20 @@ async function readCairnRun(dir, deps = {}) {
4513
4652
  return {
4514
4653
  schemaVersion: INGEST_SCHEMA_VERSION,
4515
4654
  run: {
4516
- runId: str(report["runId"], path12.basename(dir)),
4655
+ runId: str(report["runId"], path13.basename(dir)),
4517
4656
  mode,
4518
4657
  target: str(report["url"], "unknown")
4519
4658
  },
4520
4659
  cases
4521
4660
  };
4522
4661
  }
4523
- var fs14, path12, SUPPORTED_ARTIFACT_VERSION, INGEST_SCHEMA_VERSION, NoCairnRunsError, CairnRunNotFoundError, CairnRunVersionError, CairnRunPartialError, CairnEvidenceAmbiguousError, asRecord, str;
4662
+ var fs14, path13, SUPPORTED_ARTIFACT_VERSION, INGEST_SCHEMA_VERSION, NoCairnRunsError, CairnRunNotFoundError, CairnRunVersionError, CairnRunPartialError, CairnEvidenceAmbiguousError, asRecord, str;
4524
4663
  var init_cairn_artifact = __esm({
4525
4664
  "src/cli/commands/cairn-artifact.ts"() {
4526
4665
  "use strict";
4527
4666
  init_cjs_shims();
4528
4667
  fs14 = __toESM(require("fs/promises"), 1);
4529
- path12 = __toESM(require("path"), 1);
4668
+ path13 = __toESM(require("path"), 1);
4530
4669
  SUPPORTED_ARTIFACT_VERSION = 1;
4531
4670
  INGEST_SCHEMA_VERSION = 1;
4532
4671
  NoCairnRunsError = class extends Error {
@@ -4693,7 +4832,7 @@ __export(cases_exports, {
4693
4832
  function gitDirty(file) {
4694
4833
  try {
4695
4834
  const out = (0, import_node_child_process2.execFileSync)("git", ["status", "--porcelain", "--", file], {
4696
- cwd: path13.dirname(file),
4835
+ cwd: path14.dirname(file),
4697
4836
  stdio: ["ignore", "pipe", "ignore"],
4698
4837
  encoding: "utf8"
4699
4838
  });
@@ -4719,9 +4858,9 @@ async function handlePull(deps = {}) {
4719
4858
  const cwd = deps.cwd ?? process.cwd();
4720
4859
  const apiUrl = resolveApiUrl(deps.apiUrl);
4721
4860
  const token = tokenOf(deps);
4722
- const file = path13.resolve(cwd, deps.file ?? DEFAULT_CASES_FILE);
4861
+ const file = path14.resolve(cwd, deps.file ?? DEFAULT_CASES_FILE);
4723
4862
  if (fs15.existsSync(file) && !deps.force && (deps.isDirty ?? gitDirty)(file))
4724
- throw new DirtyFileError(path13.relative(cwd, file) || file);
4863
+ throw new DirtyFileError(path14.relative(cwd, file) || file);
4725
4864
  const doFetch = deps.fetchImpl ?? fetch;
4726
4865
  const endpoint = deps.suiteId !== void 0 ? `${apiUrl}/v1/suites/${encodeURIComponent(deps.suiteId)}/markdown` : `${apiUrl}/v1/test-cases/markdown`;
4727
4866
  let res;
@@ -4736,7 +4875,7 @@ async function handlePull(deps = {}) {
4736
4875
  throw new SyncHttpError(res.status, detail);
4737
4876
  }
4738
4877
  const text2 = await res.text();
4739
- fs15.mkdirSync(path13.dirname(file), { recursive: true });
4878
+ fs15.mkdirSync(path14.dirname(file), { recursive: true });
4740
4879
  fs15.writeFileSync(file, text2, "utf8");
4741
4880
  return { file, cases: Number(res.headers.get("x-plune-cases") ?? "0") };
4742
4881
  }
@@ -4744,7 +4883,7 @@ async function handlePush(deps = {}) {
4744
4883
  const cwd = deps.cwd ?? process.cwd();
4745
4884
  const apiUrl = resolveApiUrl(deps.apiUrl);
4746
4885
  const token = tokenOf(deps);
4747
- const file = path13.resolve(cwd, deps.file ?? DEFAULT_CASES_FILE);
4886
+ const file = path14.resolve(cwd, deps.file ?? DEFAULT_CASES_FILE);
4748
4887
  let text2;
4749
4888
  try {
4750
4889
  text2 = fs15.readFileSync(file, "utf8");
@@ -4798,18 +4937,18 @@ function reportCasesFailure(err, write) {
4798
4937
  }
4799
4938
  return null;
4800
4939
  }
4801
- var import_node_child_process2, fs15, path13, DEFAULT_CASES_FILE, DirtyFileError;
4940
+ var import_node_child_process2, fs15, path14, DEFAULT_CASES_FILE, DirtyFileError;
4802
4941
  var init_cases = __esm({
4803
4942
  "src/cli/commands/cases.ts"() {
4804
4943
  "use strict";
4805
4944
  init_cjs_shims();
4806
4945
  import_node_child_process2 = require("child_process");
4807
4946
  fs15 = __toESM(require("fs"), 1);
4808
- path13 = __toESM(require("path"), 1);
4947
+ path14 = __toESM(require("path"), 1);
4809
4948
  init_api_url();
4810
4949
  init_credentials();
4811
4950
  init_sync();
4812
- DEFAULT_CASES_FILE = path13.join("plune", "cases.md");
4951
+ DEFAULT_CASES_FILE = path14.join("plune", "cases.md");
4813
4952
  DirtyFileError = class extends Error {
4814
4953
  constructor(file) {
4815
4954
  super(`${file} has uncommitted changes; commit them or pass --force to overwrite.`);
@@ -4945,6 +5084,12 @@ function createProgram() {
4945
5084
  const program = new import_commander.Command();
4946
5085
  program.name("plune").description("Plune \u2014 AI-powered assertion testing CLI").version(pkg.version);
4947
5086
  program.option("-c, --config <path>", "Path to plune.yaml config file (applies to all commands)").option("-v, --verbose", "Verbose output, including stack traces on unexpected errors", false).option("--no-color", "Disable colored output regardless of TTY");
5087
+ program.hook("preAction", async (_program, action) => {
5088
+ const { loadEnv: loadEnv2 } = await Promise.resolve().then(() => (init_env(), env_exports));
5089
+ const { config } = action.optsWithGlobals();
5090
+ if (config !== void 0) loadEnv2((0, import_node_path4.dirname)((0, import_node_path4.resolve)(config)));
5091
+ loadEnv2(process.cwd());
5092
+ });
4948
5093
  const runCommand = program.command("run").description("Run assertions against a dataset").option("--dry-run", "Estimate cost/tokens; do not call the model", false).option("--config <path>", "Path to plune.yaml config file").option(
4949
5094
  "--only <selector>",
4950
5095
  "Run a subset by eval id or tag (repeatable)",
@@ -4955,21 +5100,18 @@ function createProgram() {
4955
5100
  const globals = command.optsWithGlobals();
4956
5101
  const configPath = globals.config;
4957
5102
  const [
4958
- { loadEnv: loadEnv2 },
4959
5103
  { handleRun: handleRun2 },
4960
5104
  { exitCodeFor: exitCodeFor2, RunConfigError: RunConfigError2 },
4961
5105
  { renderReport: renderReport2 },
4962
5106
  { ConfigNotFoundError: ConfigNotFoundError2, ConfigValidationError: ConfigValidationError2, YamlParseError: YamlParseError2 },
4963
5107
  { AuthError: AuthError2 }
4964
5108
  ] = await Promise.all([
4965
- Promise.resolve().then(() => (init_env(), env_exports)),
4966
5109
  Promise.resolve().then(() => (init_run2(), run_exports)),
4967
5110
  Promise.resolve().then(() => (init_orchestrator(), orchestrator_exports)),
4968
5111
  Promise.resolve().then(() => (init_reporters(), reporters_exports)),
4969
5112
  Promise.resolve().then(() => (init_errors(), errors_exports)),
4970
5113
  Promise.resolve().then(() => (init_errors2(), errors_exports2))
4971
5114
  ]);
4972
- loadEnv2(configPath !== void 0 ? (0, import_node_path4.dirname)((0, import_node_path4.resolve)(configPath)) : process.cwd());
4973
5115
  try {
4974
5116
  const result = await handleRun2({
4975
5117
  dryRun: options.dryRun,
@@ -5129,13 +5271,11 @@ function createProgram() {
5129
5271
  );
5130
5272
  program.command("report").description("Render the most recent run in a chosen format").option("--format <fmt>", "console | json | markdown", "console").option("-o, --output <path>", "Write the report to a file instead of stdout").action(async (options, command) => {
5131
5273
  const globals = command.optsWithGlobals();
5132
- const [{ loadEnv: loadEnv2 }, { handleReport: handleReport2, ReportNotFoundError: ReportNotFoundError2 }, { renderReport: renderReport2 }] = await Promise.all([
5133
- Promise.resolve().then(() => (init_env(), env_exports)),
5274
+ const [{ handleReport: handleReport2, ReportNotFoundError: ReportNotFoundError2 }, { renderReport: renderReport2 }] = await Promise.all([
5134
5275
  Promise.resolve().then(() => (init_report(), report_exports)),
5135
5276
  Promise.resolve().then(() => (init_reporters(), reporters_exports))
5136
5277
  ]);
5137
5278
  const cwd = globals.config !== void 0 ? (0, import_node_path4.dirname)((0, import_node_path4.resolve)(globals.config)) : process.cwd();
5138
- loadEnv2(cwd);
5139
5279
  try {
5140
5280
  const result = handleReport2({ cwd });
5141
5281
  const text2 = renderReport2(result, toFormat(options.format), {
@@ -5199,10 +5339,10 @@ function createProgram() {
5199
5339
  const { handleLogin: handleLogin2, reportLoginFailure: reportLoginFailure2 } = await Promise.resolve().then(() => (init_login(), login_exports));
5200
5340
  try {
5201
5341
  const token = options.token ?? await readTokenFromStdin();
5202
- const { path: path14, verified } = await handleLogin2({ token, skipVerify: options.skipVerify });
5342
+ const { path: path15, verified } = await handleLogin2({ token, skipVerify: options.skipVerify });
5203
5343
  process.stdout.write(
5204
- verified ? `Logged in. Token checked against the API and saved to ${path14}
5205
- ` : `Token saved to ${path14} WITHOUT being checked (--skip-verify).
5344
+ verified ? `Logged in. Token checked against the API and saved to ${path15}
5345
+ ` : `Token saved to ${path15} WITHOUT being checked (--skip-verify).
5206
5346
  `
5207
5347
  );
5208
5348
  } catch (err) {
@@ -5219,9 +5359,9 @@ function createProgram() {
5219
5359
  const verbose = command.optsWithGlobals().verbose === true;
5220
5360
  const { handleLogout: handleLogout2 } = await Promise.resolve().then(() => (init_logout(), logout_exports));
5221
5361
  try {
5222
- const { removed, path: path14 } = handleLogout2();
5362
+ const { removed, path: path15 } = handleLogout2();
5223
5363
  process.stdout.write(
5224
- removed ? `Logged out. Removed ${path14}
5364
+ removed ? `Logged out. Removed ${path15}
5225
5365
  ` : "Not logged in \u2014 nothing to remove.\n"
5226
5366
  );
5227
5367
  } catch (err) {