@plune-ai/cli 0.10.0 → 0.11.1

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/README.md CHANGED
@@ -158,6 +158,14 @@ job closes the run instead of the explicit `finish`.
158
158
  `plune run exec` sets it for you — anything reporting inside it, this command included, joins
159
159
  without closing.
160
160
 
161
+ ### What the run is called
162
+
163
+ A run nobody named is called after the directory, the minute it started and where it ran —
164
+ `plune · 2026-09-15 15:26 · ci` — so a row in the list reads without being opened. The clock is the
165
+ reporting machine's own; `ci` comes from the `CI` variable every hosted runner sets. To call it
166
+ something else, set `PLUNE_RUN_TITLE`; `PLUNE_ENV` and `PLUNE_LABELS` mark where it ran and how, and
167
+ those stay marks rather than becoming part of the name.
168
+
161
169
  ## Optional: keep a history
162
170
 
163
171
  Everything above works with no account, no network, and no token — that does not change. `run`,
package/dist/cli.cjs CHANGED
@@ -597,7 +597,7 @@ function makeAnthropicProvider(config, env, pricing) {
597
597
  () => client.messages.create({
598
598
  model: req.model,
599
599
  max_tokens: req.max_tokens,
600
- temperature: req.temperature,
600
+ ...req.temperature !== void 0 ? { temperature: req.temperature } : {},
601
601
  messages: [{ role: "user", content: req.prompt_resolved }]
602
602
  }),
603
603
  { max_retries: maxRetries }
@@ -657,7 +657,7 @@ function makeOpenAiCompatibleProvider(opts) {
657
657
  const res = await withRetry(
658
658
  () => client.chat.completions.create({
659
659
  model: req.model,
660
- temperature: req.temperature,
660
+ ...req.temperature !== void 0 ? { temperature: req.temperature } : {},
661
661
  max_tokens: req.max_tokens,
662
662
  messages: [{ role: "user", content: req.prompt_resolved }],
663
663
  // OpenRouter extension: ask it to include the call's actual cost in `usage` (ADR-PRC02).
@@ -1547,7 +1547,7 @@ function buildJudge(provider, cfg, onUsage) {
1547
1547
  const res = await provider.complete({
1548
1548
  provider: cfg.type,
1549
1549
  model: cfg.model,
1550
- temperature: 0,
1550
+ ...cfg.temperature !== void 0 ? { temperature: cfg.temperature } : {},
1551
1551
  max_tokens: cfg.max_tokens ?? DEFAULT_MAX_TOKENS,
1552
1552
  prompt_resolved: prompt5
1553
1553
  });
@@ -1609,7 +1609,7 @@ function estimateUsage(prompt5, maxTokens) {
1609
1609
  async function runRow(p) {
1610
1610
  const t0 = p.now();
1611
1611
  const prompt5 = resolvePrompt(p.template, p.row.vars);
1612
- const temperature = p.providerConfig.temperature ?? 0;
1612
+ const temperature = p.providerConfig.temperature;
1613
1613
  const maxTokens = p.providerConfig.max_tokens ?? DEFAULT_MAX_TOKENS2;
1614
1614
  if (p.dryRun) {
1615
1615
  const est = estimateUsage(prompt5, maxTokens);
@@ -1619,7 +1619,7 @@ async function runRow(p) {
1619
1619
  const key = cacheKey({
1620
1620
  provider: p.providerConfig.type,
1621
1621
  model: p.providerConfig.model,
1622
- temperature,
1622
+ temperature: temperature ?? 0,
1623
1623
  max_tokens: maxTokens,
1624
1624
  prompt_resolved: prompt5
1625
1625
  });
@@ -1640,7 +1640,7 @@ async function runRow(p) {
1640
1640
  const res = await p.provider.complete({
1641
1641
  provider: p.providerConfig.type,
1642
1642
  model: p.providerConfig.model,
1643
- temperature,
1643
+ ...temperature !== void 0 ? { temperature } : {},
1644
1644
  max_tokens: maxTokens,
1645
1645
  prompt_resolved: prompt5
1646
1646
  });
@@ -2210,7 +2210,7 @@ function createClient(opts) {
2210
2210
  const wait = opts.wait ?? sleep;
2211
2211
  const base = opts.apiUrl.replace(/\/+$/, "");
2212
2212
  const scrub = (text2) => opts.token === "" ? text2 : text2.split(opts.token).join("***");
2213
- async function send(method, path12, body) {
2213
+ async function send(method, path13, body) {
2214
2214
  let last = {
2215
2215
  kind: "unavailable",
2216
2216
  status: null,
@@ -2219,7 +2219,7 @@ function createClient(opts) {
2219
2219
  for (let attempt = 0; attempt <= RETRY_DELAYS_MS.length; attempt += 1) {
2220
2220
  let res;
2221
2221
  try {
2222
- res = await doFetch(`${base}${path12}`, {
2222
+ res = await doFetch(`${base}${path13}`, {
2223
2223
  method,
2224
2224
  headers: { "content-type": "application/json", authorization: `Bearer ${opts.token}` },
2225
2225
  // A DELETE carries none. Sending `"undefined"` as a body is what a naive
@@ -2247,8 +2247,8 @@ function createClient(opts) {
2247
2247
  return { ok: false, ...last };
2248
2248
  }
2249
2249
  return {
2250
- post: (path12, body) => send("POST", path12, body),
2251
- del: (path12) => send("DELETE", path12)
2250
+ post: (path13, body) => send("POST", path13, body),
2251
+ del: (path13) => send("DELETE", path13)
2252
2252
  };
2253
2253
  }
2254
2254
  var RETRY_DELAYS_MS, sleep;
@@ -2313,18 +2313,25 @@ function readEnv(env = process.env) {
2313
2313
  ignored: UNSUPPORTED_VARS.filter((name) => value(env, name) !== void 0)
2314
2314
  };
2315
2315
  }
2316
- var UNSUPPORTED_VARS;
2316
+ function defaultRunTitle(env = process.env, now = /* @__PURE__ */ new Date(), cwd = process.cwd()) {
2317
+ const two = (n) => String(n).padStart(2, "0");
2318
+ const stamp = `${now.getFullYear()}-${two(now.getMonth() + 1)}-${two(now.getDate())} ${two(now.getHours())}:${two(now.getMinutes())}`;
2319
+ const where = flag(env, "CI") || flag(env, "GITHUB_ACTIONS") ? "ci" : "local";
2320
+ return `${path6.basename(cwd)} \xB7 ${stamp} \xB7 ${where}`;
2321
+ }
2322
+ var path6, UNSUPPORTED_VARS;
2317
2323
  var init_env2 = __esm({
2318
2324
  "src/reporter-core/env.ts"() {
2319
2325
  "use strict";
2320
2326
  init_cjs_shims();
2327
+ path6 = __toESM(require("path"), 1);
2321
2328
  UNSUPPORTED_VARS = ["PLUNE_GROUP"];
2322
2329
  }
2323
2330
  });
2324
2331
 
2325
2332
  // src/reporter-core/fallback.ts
2326
2333
  function appendBatch(file, batch) {
2327
- fs6.mkdirSync(path6.dirname(file), { recursive: true });
2334
+ fs6.mkdirSync(path7.dirname(file), { recursive: true });
2328
2335
  const line = {
2329
2336
  ts: (/* @__PURE__ */ new Date()).toISOString(),
2330
2337
  runId: batch.runId,
@@ -2334,14 +2341,14 @@ function appendBatch(file, batch) {
2334
2341
  fs6.appendFileSync(file, `${JSON.stringify(line)}
2335
2342
  `, "utf8");
2336
2343
  }
2337
- var fs6, path6, DEFAULT_FALLBACK_PATH;
2344
+ var fs6, path7, DEFAULT_FALLBACK_PATH;
2338
2345
  var init_fallback = __esm({
2339
2346
  "src/reporter-core/fallback.ts"() {
2340
2347
  "use strict";
2341
2348
  init_cjs_shims();
2342
2349
  fs6 = __toESM(require("fs"), 1);
2343
- path6 = __toESM(require("path"), 1);
2344
- DEFAULT_FALLBACK_PATH = path6.join(".plune", "pending-results.jsonl");
2350
+ path7 = __toESM(require("path"), 1);
2351
+ DEFAULT_FALLBACK_PATH = path7.join(".plune", "pending-results.jsonl");
2345
2352
  }
2346
2353
  });
2347
2354
 
@@ -2468,6 +2475,10 @@ async function startRun(passed, declared) {
2468
2475
  ...externalKey !== null ? { externalKey } : {},
2469
2476
  ...cfg.meta !== void 0 ? { meta: cfg.meta } : {},
2470
2477
  ...configuration !== void 0 ? { configuration } : {},
2478
+ // Named even when nobody named it — the list has to read without opening a row. Only what
2479
+ // the caller DESCRIBED goes to `warnIfDropped` below: a title this side invented is not one
2480
+ // a person will miss, and a deployment older than the field must not be nagged on every run.
2481
+ title: defaultRunTitle(),
2471
2482
  ...described
2472
2483
  });
2473
2484
  if (start.ok) {
@@ -2689,7 +2700,9 @@ async function handleRunStart(options = {}) {
2689
2700
  const out = await client.post("/v1/runs", {
2690
2701
  schemaVersion: 2,
2691
2702
  kind: options.kind ?? "automated",
2692
- externalKey
2703
+ externalKey,
2704
+ // The same name the reporter would give it; `PLUNE_RUN_TITLE` replaces it, as everywhere.
2705
+ title: env.config.title ?? defaultRunTitle()
2693
2706
  });
2694
2707
  if (!out.ok) throw new RunCommandError(`Could not start the run \u2014 ${out.detail || out.kind}.`);
2695
2708
  const result = {
@@ -3351,7 +3364,7 @@ function isRunResult(v) {
3351
3364
  }
3352
3365
  function handleReport(opts = {}) {
3353
3366
  const cwd = opts.cwd ?? process.cwd();
3354
- const file = path7.join(cwd, ".plune", "last-run.json");
3367
+ const file = path8.join(cwd, ".plune", "last-run.json");
3355
3368
  let raw;
3356
3369
  try {
3357
3370
  raw = fs9.readFileSync(file, "utf8");
@@ -3369,13 +3382,13 @@ function handleReport(opts = {}) {
3369
3382
  }
3370
3383
  return parsed;
3371
3384
  }
3372
- var fs9, path7, ReportNotFoundError;
3385
+ var fs9, path8, ReportNotFoundError;
3373
3386
  var init_report = __esm({
3374
3387
  "src/cli/commands/report.ts"() {
3375
3388
  "use strict";
3376
3389
  init_cjs_shims();
3377
3390
  fs9 = __toESM(require("fs"), 1);
3378
- path7 = __toESM(require("path"), 1);
3391
+ path8 = __toESM(require("path"), 1);
3379
3392
  ReportNotFoundError = class extends Error {
3380
3393
  code = "NO_SAVED_RUN";
3381
3394
  constructor(message) {
@@ -3599,7 +3612,7 @@ async function runInitWizard(cwd) {
3599
3612
  process.exit(1);
3600
3613
  return;
3601
3614
  }
3602
- const outputPath = path8.join(cwd, "plune.yaml");
3615
+ const outputPath = path9.join(cwd, "plune.yaml");
3603
3616
  if (fs11.existsSync(outputPath)) {
3604
3617
  const overwrite = await clack.confirm({
3605
3618
  message: "plune.yaml already exists. Overwrite?"
@@ -3643,14 +3656,14 @@ async function runInitWizard(cwd) {
3643
3656
  spin.stop("plune.yaml created!");
3644
3657
  clack.outro("plune.yaml is ready.");
3645
3658
  }
3646
- var clack, fs11, path8, import_yaml2;
3659
+ var clack, fs11, path9, import_yaml2;
3647
3660
  var init_wizard = __esm({
3648
3661
  "src/config/init/wizard.ts"() {
3649
3662
  "use strict";
3650
3663
  init_cjs_shims();
3651
3664
  clack = __toESM(require("@clack/prompts"), 1);
3652
3665
  fs11 = __toESM(require("fs"), 1);
3653
- path8 = __toESM(require("path"), 1);
3666
+ path9 = __toESM(require("path"), 1);
3654
3667
  import_yaml2 = require("yaml");
3655
3668
  init_errors();
3656
3669
  }
@@ -3658,21 +3671,21 @@ var init_wizard = __esm({
3658
3671
 
3659
3672
  // src/cli/templates/index.ts
3660
3673
  function writeTemplateFile(cwd, rel, content, force) {
3661
- const target = path9.join(cwd, rel);
3674
+ const target = path10.join(cwd, rel);
3662
3675
  if (!force && fs12.existsSync(target)) {
3663
3676
  return "skipped";
3664
3677
  }
3665
- fs12.mkdirSync(path9.dirname(target), { recursive: true });
3678
+ fs12.mkdirSync(path10.dirname(target), { recursive: true });
3666
3679
  fs12.writeFileSync(target, content, "utf8");
3667
3680
  return "written";
3668
3681
  }
3669
- var fs12, path9, ENV_EXAMPLE_TPL, EXAMPLE_JSONL_TPL, PLUNE_YAML_TPL;
3682
+ var fs12, path10, ENV_EXAMPLE_TPL, EXAMPLE_JSONL_TPL, PLUNE_YAML_TPL;
3670
3683
  var init_templates = __esm({
3671
3684
  "src/cli/templates/index.ts"() {
3672
3685
  "use strict";
3673
3686
  init_cjs_shims();
3674
3687
  fs12 = __toESM(require("fs"), 1);
3675
- path9 = __toESM(require("path"), 1);
3688
+ path10 = __toESM(require("path"), 1);
3676
3689
  ENV_EXAMPLE_TPL = `# Copy this file to .env and replace the placeholders with your real keys.
3677
3690
  # .env is gitignored; .env.example is safe to commit.
3678
3691
  # Plune reads whichever key matches your configured provider.
@@ -3928,7 +3941,7 @@ __export(sync_exports, {
3928
3941
  reportSyncFailure: () => reportSyncFailure
3929
3942
  });
3930
3943
  function readRun(cwd, file) {
3931
- const target = file !== void 0 ? path10.resolve(cwd, file) : path10.join(cwd, ".plune", "last-run.json");
3944
+ const target = file !== void 0 ? path11.resolve(cwd, file) : path11.join(cwd, ".plune", "last-run.json");
3932
3945
  let raw;
3933
3946
  try {
3934
3947
  raw = fs13.readFileSync(target, "utf8");
@@ -3995,13 +4008,13 @@ function reportSyncFailure(err, write) {
3995
4008
  }
3996
4009
  return null;
3997
4010
  }
3998
- var fs13, path10, NotLoggedInError, TokenRejectedError2, SyncFileError, SyncNetworkError, SyncHttpError;
4011
+ var fs13, path11, NotLoggedInError, TokenRejectedError2, SyncFileError, SyncNetworkError, SyncHttpError;
3999
4012
  var init_sync = __esm({
4000
4013
  "src/cli/commands/sync.ts"() {
4001
4014
  "use strict";
4002
4015
  init_cjs_shims();
4003
4016
  fs13 = __toESM(require("fs"), 1);
4004
- path10 = __toESM(require("path"), 1);
4017
+ path11 = __toESM(require("path"), 1);
4005
4018
  init_run_result_schema();
4006
4019
  init_api_url();
4007
4020
  init_credentials();
@@ -4051,9 +4064,9 @@ async function findLatestCairnRun(runsDir = "runs") {
4051
4064
  let newest;
4052
4065
  for (const entry of entries) {
4053
4066
  if (!entry.isDirectory()) continue;
4054
- const dir = path11.join(runsDir, entry.name);
4067
+ const dir = path12.join(runsDir, entry.name);
4055
4068
  try {
4056
- const report = await fs14.lstat(path11.join(dir, "report.json"));
4069
+ const report = await fs14.lstat(path12.join(dir, "report.json"));
4057
4070
  if (!report.isFile()) continue;
4058
4071
  if (newest === void 0 || report.mtimeMs > newest.at) {
4059
4072
  newest = { dir, at: report.mtimeMs };
@@ -4072,7 +4085,7 @@ async function readJson(file) {
4072
4085
  }
4073
4086
  }
4074
4087
  async function defaultReadEvidence(dir) {
4075
- const raw = await readJson(path11.join(dir, "api-evidence.json"));
4088
+ const raw = await readJson(path12.join(dir, "api-evidence.json"));
4076
4089
  if (!Array.isArray(raw)) return void 0;
4077
4090
  return raw.map((r) => {
4078
4091
  const rec = asRecord(r);
@@ -4114,7 +4127,7 @@ function casesFromApi(report, verdicts) {
4114
4127
  });
4115
4128
  }
4116
4129
  async function readCairnRun(dir, deps = {}) {
4117
- const raw = await readJson(path11.join(dir, "report.json"));
4130
+ const raw = await readJson(path12.join(dir, "report.json"));
4118
4131
  if (raw === void 0) throw new CairnRunNotFoundError(dir);
4119
4132
  const report = asRecord(raw);
4120
4133
  if (report["schemaVersion"] !== SUPPORTED_ARTIFACT_VERSION) {
@@ -4144,20 +4157,20 @@ async function readCairnRun(dir, deps = {}) {
4144
4157
  return {
4145
4158
  schemaVersion: INGEST_SCHEMA_VERSION,
4146
4159
  run: {
4147
- runId: str(report["runId"], path11.basename(dir)),
4160
+ runId: str(report["runId"], path12.basename(dir)),
4148
4161
  mode,
4149
4162
  target: str(report["url"], "unknown")
4150
4163
  },
4151
4164
  cases
4152
4165
  };
4153
4166
  }
4154
- var fs14, path11, SUPPORTED_ARTIFACT_VERSION, INGEST_SCHEMA_VERSION, NoCairnRunsError, CairnRunNotFoundError, CairnRunVersionError, CairnRunPartialError, CairnEvidenceAmbiguousError, asRecord, str;
4167
+ var fs14, path12, SUPPORTED_ARTIFACT_VERSION, INGEST_SCHEMA_VERSION, NoCairnRunsError, CairnRunNotFoundError, CairnRunVersionError, CairnRunPartialError, CairnEvidenceAmbiguousError, asRecord, str;
4155
4168
  var init_cairn_artifact = __esm({
4156
4169
  "src/cli/commands/cairn-artifact.ts"() {
4157
4170
  "use strict";
4158
4171
  init_cjs_shims();
4159
4172
  fs14 = __toESM(require("fs/promises"), 1);
4160
- path11 = __toESM(require("path"), 1);
4173
+ path12 = __toESM(require("path"), 1);
4161
4174
  SUPPORTED_ARTIFACT_VERSION = 1;
4162
4175
  INGEST_SCHEMA_VERSION = 1;
4163
4176
  NoCairnRunsError = class extends Error {
@@ -4613,10 +4626,10 @@ function createProgram() {
4613
4626
  const { handleLogin: handleLogin2, reportLoginFailure: reportLoginFailure2 } = await Promise.resolve().then(() => (init_login(), login_exports));
4614
4627
  try {
4615
4628
  const token = options.token ?? await readTokenFromStdin();
4616
- const { path: path12, verified } = await handleLogin2({ token, skipVerify: options.skipVerify });
4629
+ const { path: path13, verified } = await handleLogin2({ token, skipVerify: options.skipVerify });
4617
4630
  process.stdout.write(
4618
- verified ? `Logged in. Token checked against the API and saved to ${path12}
4619
- ` : `Token saved to ${path12} WITHOUT being checked (--skip-verify).
4631
+ verified ? `Logged in. Token checked against the API and saved to ${path13}
4632
+ ` : `Token saved to ${path13} WITHOUT being checked (--skip-verify).
4620
4633
  `
4621
4634
  );
4622
4635
  } catch (err) {
@@ -4633,9 +4646,9 @@ function createProgram() {
4633
4646
  const verbose = command.optsWithGlobals().verbose === true;
4634
4647
  const { handleLogout: handleLogout2 } = await Promise.resolve().then(() => (init_logout(), logout_exports));
4635
4648
  try {
4636
- const { removed, path: path12 } = handleLogout2();
4649
+ const { removed, path: path13 } = handleLogout2();
4637
4650
  process.stdout.write(
4638
- removed ? `Logged out. Removed ${path12}
4651
+ removed ? `Logged out. Removed ${path13}
4639
4652
  ` : "Not logged in \u2014 nothing to remove.\n"
4640
4653
  );
4641
4654
  } catch (err) {
package/dist/index.cjs CHANGED
@@ -480,7 +480,7 @@ function makeAnthropicProvider(config, env, pricing) {
480
480
  () => client.messages.create({
481
481
  model: req.model,
482
482
  max_tokens: req.max_tokens,
483
- temperature: req.temperature,
483
+ ...req.temperature !== void 0 ? { temperature: req.temperature } : {},
484
484
  messages: [{ role: "user", content: req.prompt_resolved }]
485
485
  }),
486
486
  { max_retries: maxRetries }
@@ -529,7 +529,7 @@ function makeOpenAiCompatibleProvider(opts) {
529
529
  const res = await withRetry(
530
530
  () => client.chat.completions.create({
531
531
  model: req.model,
532
- temperature: req.temperature,
532
+ ...req.temperature !== void 0 ? { temperature: req.temperature } : {},
533
533
  max_tokens: req.max_tokens,
534
534
  messages: [{ role: "user", content: req.prompt_resolved }],
535
535
  // OpenRouter extension: ask it to include the call's actual cost in `usage` (ADR-PRC02).
@@ -1216,7 +1216,7 @@ function buildJudge(provider, cfg, onUsage) {
1216
1216
  const res = await provider.complete({
1217
1217
  provider: cfg.type,
1218
1218
  model: cfg.model,
1219
- temperature: 0,
1219
+ ...cfg.temperature !== void 0 ? { temperature: cfg.temperature } : {},
1220
1220
  max_tokens: cfg.max_tokens ?? DEFAULT_MAX_TOKENS,
1221
1221
  prompt_resolved: prompt5
1222
1222
  });
@@ -1273,7 +1273,7 @@ function estimateUsage(prompt5, maxTokens) {
1273
1273
  async function runRow(p) {
1274
1274
  const t0 = p.now();
1275
1275
  const prompt5 = resolvePrompt(p.template, p.row.vars);
1276
- const temperature = p.providerConfig.temperature ?? 0;
1276
+ const temperature = p.providerConfig.temperature;
1277
1277
  const maxTokens = p.providerConfig.max_tokens ?? DEFAULT_MAX_TOKENS2;
1278
1278
  if (p.dryRun) {
1279
1279
  const est = estimateUsage(prompt5, maxTokens);
@@ -1283,7 +1283,7 @@ async function runRow(p) {
1283
1283
  const key = cacheKey({
1284
1284
  provider: p.providerConfig.type,
1285
1285
  model: p.providerConfig.model,
1286
- temperature,
1286
+ temperature: temperature ?? 0,
1287
1287
  max_tokens: maxTokens,
1288
1288
  prompt_resolved: prompt5
1289
1289
  });
@@ -1304,7 +1304,7 @@ async function runRow(p) {
1304
1304
  const res = await p.provider.complete({
1305
1305
  provider: p.providerConfig.type,
1306
1306
  model: p.providerConfig.model,
1307
- temperature,
1307
+ ...temperature !== void 0 ? { temperature } : {},
1308
1308
  max_tokens: maxTokens,
1309
1309
  prompt_resolved: prompt5
1310
1310
  });
package/dist/index.d.cts CHANGED
@@ -146,7 +146,12 @@ interface Config {
146
146
  interface CompletionRequest {
147
147
  provider: string;
148
148
  model: string;
149
- temperature: number;
149
+ /**
150
+ * Only when the config asked for one. Models released after Claude Opus 4.6 do not take a
151
+ * temperature at all — any value but 1.0 is a 400 — so a default here would be a default that
152
+ * breaks every eval on a current model. Absent means "the model's own".
153
+ */
154
+ temperature?: number;
150
155
  max_tokens: number;
151
156
  prompt_resolved: string;
152
157
  }
package/dist/index.d.ts CHANGED
@@ -146,7 +146,12 @@ interface Config {
146
146
  interface CompletionRequest {
147
147
  provider: string;
148
148
  model: string;
149
- temperature: number;
149
+ /**
150
+ * Only when the config asked for one. Models released after Claude Opus 4.6 do not take a
151
+ * temperature at all — any value but 1.0 is a 400 — so a default here would be a default that
152
+ * breaks every eval on a current model. Absent means "the model's own".
153
+ */
154
+ temperature?: number;
150
155
  max_tokens: number;
151
156
  prompt_resolved: string;
152
157
  }
package/dist/index.js CHANGED
@@ -427,7 +427,7 @@ function makeAnthropicProvider(config, env, pricing) {
427
427
  () => client.messages.create({
428
428
  model: req.model,
429
429
  max_tokens: req.max_tokens,
430
- temperature: req.temperature,
430
+ ...req.temperature !== void 0 ? { temperature: req.temperature } : {},
431
431
  messages: [{ role: "user", content: req.prompt_resolved }]
432
432
  }),
433
433
  { max_retries: maxRetries }
@@ -476,7 +476,7 @@ function makeOpenAiCompatibleProvider(opts) {
476
476
  const res = await withRetry(
477
477
  () => client.chat.completions.create({
478
478
  model: req.model,
479
- temperature: req.temperature,
479
+ ...req.temperature !== void 0 ? { temperature: req.temperature } : {},
480
480
  max_tokens: req.max_tokens,
481
481
  messages: [{ role: "user", content: req.prompt_resolved }],
482
482
  // OpenRouter extension: ask it to include the call's actual cost in `usage` (ADR-PRC02).
@@ -1163,7 +1163,7 @@ function buildJudge(provider, cfg, onUsage) {
1163
1163
  const res = await provider.complete({
1164
1164
  provider: cfg.type,
1165
1165
  model: cfg.model,
1166
- temperature: 0,
1166
+ ...cfg.temperature !== void 0 ? { temperature: cfg.temperature } : {},
1167
1167
  max_tokens: cfg.max_tokens ?? DEFAULT_MAX_TOKENS,
1168
1168
  prompt_resolved: prompt5
1169
1169
  });
@@ -1220,7 +1220,7 @@ function estimateUsage(prompt5, maxTokens) {
1220
1220
  async function runRow(p) {
1221
1221
  const t0 = p.now();
1222
1222
  const prompt5 = resolvePrompt(p.template, p.row.vars);
1223
- const temperature = p.providerConfig.temperature ?? 0;
1223
+ const temperature = p.providerConfig.temperature;
1224
1224
  const maxTokens = p.providerConfig.max_tokens ?? DEFAULT_MAX_TOKENS2;
1225
1225
  if (p.dryRun) {
1226
1226
  const est = estimateUsage(prompt5, maxTokens);
@@ -1230,7 +1230,7 @@ async function runRow(p) {
1230
1230
  const key = cacheKey({
1231
1231
  provider: p.providerConfig.type,
1232
1232
  model: p.providerConfig.model,
1233
- temperature,
1233
+ temperature: temperature ?? 0,
1234
1234
  max_tokens: maxTokens,
1235
1235
  prompt_resolved: prompt5
1236
1236
  });
@@ -1251,7 +1251,7 @@ async function runRow(p) {
1251
1251
  const res = await p.provider.complete({
1252
1252
  provider: p.providerConfig.type,
1253
1253
  model: p.providerConfig.model,
1254
- temperature,
1254
+ ...temperature !== void 0 ? { temperature } : {},
1255
1255
  max_tokens: maxTokens,
1256
1256
  prompt_resolved: prompt5
1257
1257
  });
@@ -97,7 +97,7 @@ function createClient(opts) {
97
97
  const wait = opts.wait ?? sleep;
98
98
  const base = opts.apiUrl.replace(/\/+$/, "");
99
99
  const scrub = (text) => opts.token === "" ? text : text.split(opts.token).join("***");
100
- async function send(method, path2, body) {
100
+ async function send(method, path3, body) {
101
101
  let last = {
102
102
  kind: "unavailable",
103
103
  status: null,
@@ -106,7 +106,7 @@ function createClient(opts) {
106
106
  for (let attempt = 0; attempt <= RETRY_DELAYS_MS.length; attempt += 1) {
107
107
  let res;
108
108
  try {
109
- res = await doFetch(`${base}${path2}`, {
109
+ res = await doFetch(`${base}${path3}`, {
110
110
  method,
111
111
  headers: { "content-type": "application/json", authorization: `Bearer ${opts.token}` },
112
112
  // A DELETE carries none. Sending `"undefined"` as a body is what a naive
@@ -134,12 +134,13 @@ function createClient(opts) {
134
134
  return { ok: false, ...last };
135
135
  }
136
136
  return {
137
- post: (path2, body) => send("POST", path2, body),
138
- del: (path2) => send("DELETE", path2)
137
+ post: (path3, body) => send("POST", path3, body),
138
+ del: (path3) => send("DELETE", path3)
139
139
  };
140
140
  }
141
141
 
142
142
  // src/reporter-core/env.ts
143
+ var path = __toESM(require("path"), 1);
143
144
  var UNSUPPORTED_VARS = ["PLUNE_GROUP"];
144
145
  function value(env, name) {
145
146
  const raw = env[name]?.trim();
@@ -192,13 +193,19 @@ function readEnv(env = process.env) {
192
193
  ignored: UNSUPPORTED_VARS.filter((name) => value(env, name) !== void 0)
193
194
  };
194
195
  }
196
+ function defaultRunTitle(env = process.env, now = /* @__PURE__ */ new Date(), cwd = process.cwd()) {
197
+ const two = (n) => String(n).padStart(2, "0");
198
+ const stamp = `${now.getFullYear()}-${two(now.getMonth() + 1)}-${two(now.getDate())} ${two(now.getHours())}:${two(now.getMinutes())}`;
199
+ const where = flag(env, "CI") || flag(env, "GITHUB_ACTIONS") ? "ci" : "local";
200
+ return `${path.basename(cwd)} \xB7 ${stamp} \xB7 ${where}`;
201
+ }
195
202
 
196
203
  // src/reporter-core/fallback.ts
197
204
  var fs = __toESM(require("fs"), 1);
198
- var path = __toESM(require("path"), 1);
199
- var DEFAULT_FALLBACK_PATH = path.join(".plune", "pending-results.jsonl");
205
+ var path2 = __toESM(require("path"), 1);
206
+ var DEFAULT_FALLBACK_PATH = path2.join(".plune", "pending-results.jsonl");
200
207
  function appendBatch(file, batch) {
201
- fs.mkdirSync(path.dirname(file), { recursive: true });
208
+ fs.mkdirSync(path2.dirname(file), { recursive: true });
202
209
  const line = {
203
210
  ts: (/* @__PURE__ */ new Date()).toISOString(),
204
211
  runId: batch.runId,
@@ -339,6 +346,10 @@ async function startRun(passed, declared) {
339
346
  ...externalKey !== null ? { externalKey } : {},
340
347
  ...cfg.meta !== void 0 ? { meta: cfg.meta } : {},
341
348
  ...configuration !== void 0 ? { configuration } : {},
349
+ // Named even when nobody named it — the list has to read without opening a row. Only what
350
+ // the caller DESCRIBED goes to `warnIfDropped` below: a title this side invented is not one
351
+ // a person will miss, and a deployment older than the field must not be nagged on every run.
352
+ title: defaultRunTitle(),
342
353
  ...described
343
354
  });
344
355
  if (start.ok) {
@@ -54,7 +54,7 @@ function createClient(opts) {
54
54
  const wait = opts.wait ?? sleep;
55
55
  const base = opts.apiUrl.replace(/\/+$/, "");
56
56
  const scrub = (text) => opts.token === "" ? text : text.split(opts.token).join("***");
57
- async function send(method, path2, body) {
57
+ async function send(method, path3, body) {
58
58
  let last = {
59
59
  kind: "unavailable",
60
60
  status: null,
@@ -63,7 +63,7 @@ function createClient(opts) {
63
63
  for (let attempt = 0; attempt <= RETRY_DELAYS_MS.length; attempt += 1) {
64
64
  let res;
65
65
  try {
66
- res = await doFetch(`${base}${path2}`, {
66
+ res = await doFetch(`${base}${path3}`, {
67
67
  method,
68
68
  headers: { "content-type": "application/json", authorization: `Bearer ${opts.token}` },
69
69
  // A DELETE carries none. Sending `"undefined"` as a body is what a naive
@@ -91,12 +91,13 @@ function createClient(opts) {
91
91
  return { ok: false, ...last };
92
92
  }
93
93
  return {
94
- post: (path2, body) => send("POST", path2, body),
95
- del: (path2) => send("DELETE", path2)
94
+ post: (path3, body) => send("POST", path3, body),
95
+ del: (path3) => send("DELETE", path3)
96
96
  };
97
97
  }
98
98
 
99
99
  // src/reporter-core/env.ts
100
+ import * as path from "path";
100
101
  var UNSUPPORTED_VARS = ["PLUNE_GROUP"];
101
102
  function value(env, name) {
102
103
  const raw = env[name]?.trim();
@@ -149,13 +150,19 @@ function readEnv(env = process.env) {
149
150
  ignored: UNSUPPORTED_VARS.filter((name) => value(env, name) !== void 0)
150
151
  };
151
152
  }
153
+ function defaultRunTitle(env = process.env, now = /* @__PURE__ */ new Date(), cwd = process.cwd()) {
154
+ const two = (n) => String(n).padStart(2, "0");
155
+ const stamp = `${now.getFullYear()}-${two(now.getMonth() + 1)}-${two(now.getDate())} ${two(now.getHours())}:${two(now.getMinutes())}`;
156
+ const where = flag(env, "CI") || flag(env, "GITHUB_ACTIONS") ? "ci" : "local";
157
+ return `${path.basename(cwd)} \xB7 ${stamp} \xB7 ${where}`;
158
+ }
152
159
 
153
160
  // src/reporter-core/fallback.ts
154
161
  import * as fs from "fs";
155
- import * as path from "path";
156
- var DEFAULT_FALLBACK_PATH = path.join(".plune", "pending-results.jsonl");
162
+ import * as path2 from "path";
163
+ var DEFAULT_FALLBACK_PATH = path2.join(".plune", "pending-results.jsonl");
157
164
  function appendBatch(file, batch) {
158
- fs.mkdirSync(path.dirname(file), { recursive: true });
165
+ fs.mkdirSync(path2.dirname(file), { recursive: true });
159
166
  const line = {
160
167
  ts: (/* @__PURE__ */ new Date()).toISOString(),
161
168
  runId: batch.runId,
@@ -296,6 +303,10 @@ async function startRun(passed, declared) {
296
303
  ...externalKey !== null ? { externalKey } : {},
297
304
  ...cfg.meta !== void 0 ? { meta: cfg.meta } : {},
298
305
  ...configuration !== void 0 ? { configuration } : {},
306
+ // Named even when nobody named it — the list has to read without opening a row. Only what
307
+ // the caller DESCRIBED goes to `warnIfDropped` below: a title this side invented is not one
308
+ // a person will miss, and a deployment older than the field must not be nagged on every run.
309
+ title: defaultRunTitle(),
299
310
  ...described
300
311
  });
301
312
  if (start.ok) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plune-ai/cli",
3
- "version": "0.10.0",
3
+ "version": "0.11.1",
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",