@critiquedotsh/harness 0.1.4 → 0.1.5

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.js CHANGED
@@ -12884,6 +12884,16 @@ var init_critique_code_runtime_config = __esm({
12884
12884
  });
12885
12885
 
12886
12886
  // lib/finish/critique-code-model-defaults.ts
12887
+ function modelFromInferenceSelection(id4) {
12888
+ const raw = id4?.trim();
12889
+ if (!raw || raw === "auto") return DEFAULT_INFERENCE_MODEL;
12890
+ try {
12891
+ const parsed = parseCritiqueCodeModels(raw.startsWith("critique/") ? raw : `critique/${raw}`)[0];
12892
+ if (parsed?.provider === "critique") return parsed;
12893
+ } catch {
12894
+ }
12895
+ return DEFAULT_INFERENCE_MODEL;
12896
+ }
12887
12897
  function defaultModelForRoute(route) {
12888
12898
  if (route === "critique-inference") return DEFAULT_INFERENCE_MODEL;
12889
12899
  if (route === "anthropic") return DEFAULT_ANTHROPIC_MODEL;
@@ -20290,16 +20300,17 @@ async function beginCritiqueInferenceLogin(input) {
20290
20300
  if (token?.status === "denied") throw new Error("Website approval was denied.");
20291
20301
  const apiKey = token?.api_key?.trim() || token?.apiKey?.trim();
20292
20302
  if (token?.status === "authorized" && apiKey) {
20303
+ const author = modelFromInferenceSelection(token.model);
20293
20304
  await saveCritiqueCodeCredential({ home: input.home, kind: "critique", key: apiKey });
20294
20305
  await saveCritiqueCodeRuntimeSettings({
20295
20306
  home: input.home,
20296
20307
  settings: settingsFromSelection({
20297
20308
  route: "critique-inference",
20298
- author: DEFAULT_INFERENCE_MODEL,
20299
- review: [DEFAULT_INFERENCE_MODEL]
20309
+ author,
20310
+ review: [author]
20300
20311
  })
20301
20312
  });
20302
- input.io.note("Critique Inference is connected. Default model is critique/auto.");
20313
+ input.io.note(`Critique Inference is connected. Default model is ${piModelId(author)}.`);
20303
20314
  return { apiKey, verificationUri: verifyUrl };
20304
20315
  }
20305
20316
  }
@@ -20769,14 +20780,16 @@ async function runCritiqueCodeSettingsSession(input) {
20769
20780
  }
20770
20781
  });
20771
20782
  config = await loadCritiqueCodeRuntimeConfig(env);
20772
- route = "critique-inference";
20773
- author = DEFAULT_INFERENCE_MODEL;
20774
- review = [author];
20783
+ route = config.settings?.route ?? "critique-inference";
20784
+ author = config.settings?.author_model ?? DEFAULT_INFERENCE_MODEL;
20785
+ review = config.settings?.review_models ?? [author];
20786
+ screen = "models";
20787
+ modelRole = "author";
20775
20788
  } catch (error) {
20776
20789
  input.ui.note(error instanceof Error ? error.message : String(error));
20790
+ if (exitNested()) break;
20791
+ screen = "home";
20777
20792
  }
20778
- if (input.start === "login") break;
20779
- screen = "home";
20780
20793
  continue;
20781
20794
  }
20782
20795
  if (screen === "keys") {
@@ -20852,8 +20865,12 @@ async function runCritiqueCodeSettingsSession(input) {
20852
20865
  continue;
20853
20866
  }
20854
20867
  if (screen === "models") {
20868
+ const current = piModelId(modelRole === "author" ? author : review[0] ?? author);
20855
20869
  const items = [
20856
- ...modelsForRoute(route),
20870
+ ...modelsForRoute(route).map((item) => ({
20871
+ ...item,
20872
+ description: item.value === current || item.value === current.replace(/^critique\/critique\//, "critique/") ? "current" : item.description
20873
+ })),
20857
20874
  { value: "custom", label: "Enter a model id" },
20858
20875
  { value: "back", label: "Back" }
20859
20876
  ];
@@ -20874,7 +20891,7 @@ async function runCritiqueCodeSettingsSession(input) {
20874
20891
  await persistSettings();
20875
20892
  input.ui.note(`Using ${piModelId(parsed)}.`);
20876
20893
  }
20877
- if (input.start === "models") break;
20894
+ if (exitNested()) break;
20878
20895
  screen = "home";
20879
20896
  continue;
20880
20897
  }
@@ -21131,7 +21148,9 @@ function parseAuthorCommand(line) {
21131
21148
  if (lower === "login" || lower === "/login") return { kind: "settings", start: "login" };
21132
21149
  if (lower === "settings" || lower === "/settings") return { kind: "settings", start: "home" };
21133
21150
  if (lower === "keys" || lower === "/keys") return { kind: "settings", start: "keys" };
21134
- if (lower === "models" || lower === "/models") return { kind: "settings", start: "models" };
21151
+ if (lower === "models" || lower === "/models" || lower === "model" || lower === "/model") {
21152
+ return { kind: "settings", start: "models" };
21153
+ }
21135
21154
  if (lower === "skills" || lower === "/skills") return { kind: "skills" };
21136
21155
  if (lower === "review" || lower === "/review" || lower === "review since" || lower === "/review since") {
21137
21156
  return { kind: "review", mode: "since" };
@@ -22694,6 +22713,7 @@ var CritiqueCodeLineHub = class {
22694
22713
  };
22695
22714
 
22696
22715
  // lib/finish/critique-code-web-server.ts
22716
+ init_critique_code_kernel();
22697
22717
  init_critique_code_model_defaults();
22698
22718
  init_critique_code_runtime_config();
22699
22719
 
@@ -22716,7 +22736,13 @@ function critiqueCodeWebPage(input) {
22716
22736
  const branch = escapeHtml(input.branch);
22717
22737
  const model = escapeHtml(input.model);
22718
22738
  const listen = escapeHtml(input.listen);
22719
- const modelShort = escapeHtml(input.model.replace(/^openrouter\//, "").replace(/^critique\//, "critique/"));
22739
+ const modelShort = escapeHtml(input.model.replace(/^openrouter\//, "").replace(/^critique\/critique\//, "critique/").replace(/^critique\//, "critique/"));
22740
+ const models = input.models && input.models.length > 0 ? input.models : [{ id: input.model, label: modelShort }];
22741
+ const currentId = input.model.replace(/^critique\/critique\//, "critique/");
22742
+ const options = models.map((entry) => {
22743
+ const selected = entry.id === input.model || entry.id === currentId ? " selected" : "";
22744
+ return `<option value="${escapeHtml(entry.id)}"${selected}>${escapeHtml(entry.label)}</option>`;
22745
+ }).join("");
22720
22746
  return `<!DOCTYPE html>
22721
22747
  <html lang="en">
22722
22748
  <head>
@@ -22789,6 +22815,8 @@ textarea::placeholder { color: #6a6a6a; }
22789
22815
  .cmds button:hover { background: rgba(255,255,255,.05); color: var(--fg); }
22790
22816
  .bar-right { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
22791
22817
  .model-chip { color: var(--muted); font-size: 12px; max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
22818
+ #model { max-width: 220px; background: transparent; border: 0; color: var(--muted); font-size: 12px; padding: 4px 0; outline: none; }
22819
+ #model:hover, #model:focus { color: var(--fg); }
22792
22820
  #send { width: 34px; height: 34px; border: 0; border-radius: 10px; background: #ececec; color: #111; display: grid; place-items: center; transition: transform .18s var(--ease), opacity .18s var(--ease); }
22793
22821
  #send:hover { transform: translateY(-1px); }
22794
22822
  #send:disabled, textarea:disabled { opacity: .4; cursor: not-allowed; }
@@ -22827,7 +22855,7 @@ textarea::placeholder { color: #6a6a6a; }
22827
22855
  <div class="badges">
22828
22856
  <span class="badge"><span class="dot"></span>${listen}</span>
22829
22857
  <span class="badge" id="files-badge" hidden></span>
22830
- <span class="badge model" title="${model}">${modelShort}</span>
22858
+ <span class="badge model" id="model-badge" title="${model}">${modelShort}</span>
22831
22859
  </div>
22832
22860
  </header>
22833
22861
  <main>
@@ -22864,7 +22892,7 @@ textarea::placeholder { color: #6a6a6a; }
22864
22892
  <button type="button" data-cmd="/exit">Exit</button>
22865
22893
  </div>
22866
22894
  <div class="bar-right">
22867
- <span class="model-chip">${modelShort}</span>
22895
+ <select id="model" aria-label="Author model">${options}</select>
22868
22896
  <button type="button" id="send" aria-label="Send">
22869
22897
  <svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 11V3M3.5 6.5 7 3l3.5 3.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
22870
22898
  </button>
@@ -22892,6 +22920,8 @@ window.CRITIQUE_CODE_TOKEN = "__CRITIQUE_CODE_TOKEN__";
22892
22920
  var emptyEl = document.getElementById("empty");
22893
22921
  var feedEl = document.getElementById("feed");
22894
22922
  var filesBadge = document.getElementById("files-badge");
22923
+ var modelEl = document.getElementById("model");
22924
+ var modelBadge = document.getElementById("model-badge");
22895
22925
  var inputEl = document.getElementById("input");
22896
22926
  var sendEl = document.getElementById("send");
22897
22927
  var modalEl = document.getElementById("modal");
@@ -22921,10 +22951,32 @@ window.CRITIQUE_CODE_TOKEN = "__CRITIQUE_CODE_TOKEN__";
22921
22951
  feedEl.className = "on";
22922
22952
  }
22923
22953
 
22954
+ function setModel(id) {
22955
+ if (!id) return;
22956
+ if (modelEl) {
22957
+ modelEl.value = id;
22958
+ if (modelEl.value !== id) {
22959
+ for (var i = 0; i < modelEl.options.length; i++) {
22960
+ if (modelEl.options[i].value === id || "critique/" + modelEl.options[i].value === id) {
22961
+ modelEl.selectedIndex = i;
22962
+ break;
22963
+ }
22964
+ }
22965
+ }
22966
+ }
22967
+ if (modelBadge) {
22968
+ var option = modelEl && modelEl.options[modelEl.selectedIndex];
22969
+ var label = option && option.text ? option.text : String(id).replace(/^openrouter//, "").replace(/^critique/critique//, "critique/");
22970
+ modelBadge.textContent = label;
22971
+ modelBadge.title = id;
22972
+ }
22973
+ }
22974
+
22924
22975
  function setBusy(next) {
22925
22976
  busy = !!next;
22926
22977
  sendEl.disabled = busy;
22927
22978
  inputEl.disabled = busy;
22979
+ if (modelEl) modelEl.disabled = busy;
22928
22980
  }
22929
22981
 
22930
22982
  function scrollMain() {
@@ -23053,6 +23105,9 @@ window.CRITIQUE_CODE_TOKEN = "__CRITIQUE_CODE_TOKEN__";
23053
23105
  case "review":
23054
23106
  setReview((ev.text || "") + (ev.conclusion ? "\\n" + ev.conclusion : ""));
23055
23107
  break;
23108
+ case "model":
23109
+ setModel(ev.text);
23110
+ break;
23056
23111
  case "busy":
23057
23112
  setBusy(ev.busy);
23058
23113
  break;
@@ -23068,6 +23123,7 @@ window.CRITIQUE_CODE_TOKEN = "__CRITIQUE_CODE_TOKEN__";
23068
23123
 
23069
23124
  function applySnapshot(s) {
23070
23125
  if (!s) return;
23126
+ if (s.model) setModel(s.model);
23071
23127
  setBusy(s.busy);
23072
23128
  setPaths(s.changed_paths);
23073
23129
  if (s.last_review_text) setReview(s.last_review_text);
@@ -23149,6 +23205,20 @@ window.CRITIQUE_CODE_TOKEN = "__CRITIQUE_CODE_TOKEN__";
23149
23205
  });
23150
23206
  document.getElementById("approve").addEventListener("click", function () { decide("approve"); });
23151
23207
  document.getElementById("deny").addEventListener("click", function () { decide("deny"); });
23208
+ if (modelEl) {
23209
+ modelEl.addEventListener("change", function () {
23210
+ var id = modelEl.value;
23211
+ if (!id || busy) return;
23212
+ api("POST", "/api/model", { id: id }).then(function (res) {
23213
+ return res.json().then(function (body) {
23214
+ if (!res.ok) addBlock("alert", body.error || "Could not change model.");
23215
+ else setModel(body.model || id);
23216
+ });
23217
+ }).catch(function () {
23218
+ addBlock("alert", "Could not change model.");
23219
+ });
23220
+ });
23221
+ }
23152
23222
 
23153
23223
  api("GET", "/api/snapshot").then(function (res) { return res.json(); }).then(applySnapshot).catch(function () {});
23154
23224
  connect();
@@ -23179,7 +23249,9 @@ async function startCritiqueCodeWebServer(input) {
23179
23249
  }
23180
23250
  const bindHost = "127.0.0.1";
23181
23251
  const env = input.env ?? process.env;
23252
+ let pendingModelChoice;
23182
23253
  let model = "critique/auto";
23254
+ let modelChoices = [];
23183
23255
  try {
23184
23256
  const config = await loadCritiqueCodeRuntimeConfig(env);
23185
23257
  const authorModels = input.author_models && input.author_models.length > 0 ? [...input.author_models] : await resolveHarnessModels({ role: "author", env, config });
@@ -23188,6 +23260,8 @@ async function startCritiqueCodeWebServer(input) {
23188
23260
  return { kind: "kernel_required", limitation: KERNEL_REQUIRED3 };
23189
23261
  }
23190
23262
  model = piModelId(authorModels[0]);
23263
+ const route = config.settings?.route ?? "critique-inference";
23264
+ modelChoices = modelsForRoute(route).map((item) => ({ id: item.value, label: item.label }));
23191
23265
  } catch (error) {
23192
23266
  return { kind: "kernel_required", limitation: error instanceof Error ? error.message : String(error) };
23193
23267
  }
@@ -23242,6 +23316,7 @@ async function startCritiqueCodeWebServer(input) {
23242
23316
  const snapshot = () => ({
23243
23317
  repository_root: repositoryRoot,
23244
23318
  model,
23319
+ models: modelChoices,
23245
23320
  busy,
23246
23321
  changed_paths: [...changedPaths],
23247
23322
  last_review_text: lastReviewText,
@@ -23288,6 +23363,7 @@ async function startCritiqueCodeWebServer(input) {
23288
23363
  repository_root: repositoryRoot,
23289
23364
  branch: gitBranch(repositoryRoot),
23290
23365
  model,
23366
+ models: modelChoices,
23291
23367
  listen: `127.0.0.1:${port}`
23292
23368
  }).replaceAll("__CRITIQUE_CODE_TOKEN__", token);
23293
23369
  send(res, 200, html, "text/html; charset=utf-8");
@@ -23367,7 +23443,7 @@ async function startCritiqueCodeWebServer(input) {
23367
23443
  });
23368
23444
  send(res, 202, { ok: true, verification_uri: started.verificationUri });
23369
23445
  void started.finished.then(() => {
23370
- emit({ type: "note", text: "Inference is connected. Restart critique-code web if you want this session on critique/auto." });
23446
+ emit({ type: "note", text: "Inference is connected. The author model is the one you picked on the website. Restart critique-code web if this session still shows the previous model." });
23371
23447
  }).catch((error) => {
23372
23448
  const text = error instanceof Error ? error.message : String(error);
23373
23449
  emit({ type: "error", text: humanizeCritiqueCodeWebError(text) });
@@ -23379,6 +23455,38 @@ async function startCritiqueCodeWebServer(input) {
23379
23455
  }
23380
23456
  return;
23381
23457
  }
23458
+ if (req.method === "GET" && path2 === "/api/models") {
23459
+ send(res, 200, { model, models: modelChoices });
23460
+ return;
23461
+ }
23462
+ if (req.method === "POST" && path2 === "/api/model") {
23463
+ const body = await readJson(req);
23464
+ const id4 = String(body.id ?? body.model ?? "").trim();
23465
+ const parsed = parseCritiqueCodeModels(id4)[0];
23466
+ if (!parsed) {
23467
+ send(res, 400, { error: "Unknown model." });
23468
+ return;
23469
+ }
23470
+ const config = await loadCritiqueCodeRuntimeConfig(env);
23471
+ const route = config.settings?.route ?? "critique-inference";
23472
+ await saveCritiqueCodeRuntimeSettings({
23473
+ home: critiqueCodeHome(env),
23474
+ settings: settingsFromSelection({
23475
+ route,
23476
+ author: parsed,
23477
+ review: [parsed],
23478
+ ...config.settings?.custom_openai ? { custom_openai: config.settings.custom_openai } : {}
23479
+ })
23480
+ });
23481
+ model = piModelId(parsed);
23482
+ emit({ type: "model", text: model });
23483
+ if (!busy && !ended) {
23484
+ pendingModelChoice = id4;
23485
+ hub.push("/models");
23486
+ }
23487
+ send(res, 200, { ok: true, model });
23488
+ return;
23489
+ }
23382
23490
  send(res, 404, { error: "not found" });
23383
23491
  });
23384
23492
  let portValue = 0;
@@ -23427,7 +23535,12 @@ Loopback only. The author session stays in this process.
23427
23535
  approve_exec: approveExec,
23428
23536
  on_kernel_event: mapKernel,
23429
23537
  settings_ui: {
23430
- async select() {
23538
+ async select(_title, items) {
23539
+ if (pendingModelChoice) {
23540
+ const choice = pendingModelChoice;
23541
+ pendingModelChoice = void 0;
23542
+ if (items.some((item) => item.value === choice)) return choice;
23543
+ }
23431
23544
  return "back";
23432
23545
  },
23433
23546
  async prompt() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@critiquedotsh/harness",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "CritiqueCode author agent — implement, then forced review and verified repair. Same org as @critiquedotsh/cli; not the sidecar.",
5
5
  "homepage": "https://critique.sh/docs/platform/critique-code",
6
6
  "repository": {
@@ -27,10 +27,12 @@
27
27
  },
28
28
  "files": [
29
29
  "dist",
30
- "README.md"
30
+ "README.md",
31
+ "scripts/postinstall.mjs"
31
32
  ],
32
33
  "scripts": {
33
34
  "build": "node scripts/build-cli.mjs",
35
+ "postinstall": "node scripts/postinstall.mjs",
34
36
  "start": "node --import ../../scripts/register-test-path-alias.mjs --experimental-strip-types src/cli.ts",
35
37
  "install:pi": "npm install --prefix .vendor",
36
38
  "prepublishOnly": "npm run build",
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+
3
+ const silent =
4
+ process.env.CI === 'true'
5
+ || process.env.npm_config_loglevel === 'silent'
6
+ || process.env.npm_config_loglevel === 'error'
7
+
8
+ if (silent) process.exit(0)
9
+
10
+ process.stderr.write(`
11
+ CritiqueCode installed. Binary: critique-code
12
+
13
+ cd into a git repo, then:
14
+
15
+ critique-code login connect Critique Inference in the browser (no key paste)
16
+ critique-code terminal author session
17
+ critique-code web local browser UI on 127.0.0.1
18
+ critique-code help commands, /review /repair /ship, voice
19
+
20
+ Voice needs the ffmpeg-static install script:
21
+ npm install -g @critiquedotsh/harness --allow-scripts=ffmpeg-static
22
+
23
+ Docs: https://critique.sh/docs/platform/critique-code
24
+
25
+ `)