@inerrata-corporation/errata 2.0.18-dev.3596 → 2.0.18-dev.3612

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.
Files changed (2) hide show
  1. package/errata.mjs +197 -66
  2. package/package.json +1 -1
package/errata.mjs CHANGED
@@ -33461,6 +33461,13 @@ function foreignDaemonNote(lock, selfEntry) {
33461
33461
  ];
33462
33462
  return runningInstalled ? [...head2, "this CLI is a different bundle from the daemon it is talking to \u2014 its view may not match what the daemon runs."] : [...head2, "`errata update` does not replace it. To move onto the installed package: errata stop, then errata dash."];
33463
33463
  }
33464
+ function daemonVersionNote(lock, selfVersion) {
33465
+ if (!lock.version || lock.version === selfVersion) return null;
33466
+ return [
33467
+ `the running daemon is ${lock.version}, but this CLI is ${selfVersion} \u2014 it was started before the update.`,
33468
+ `restart it onto ${selfVersion}: errata stop, then errata start`
33469
+ ];
33470
+ }
33464
33471
  function isProcessAlive(pid) {
33465
33472
  if (!Number.isSafeInteger(pid) || pid <= 0) return false;
33466
33473
  try {
@@ -33500,6 +33507,20 @@ function isDaemonAlive(lockPath) {
33500
33507
  function daemonLockBlocksStartup(lockPath) {
33501
33508
  return daemonLockState(readDaemonLock(lockPath), true) !== "stale";
33502
33509
  }
33510
+ async function waitForDaemonExit(lock, timeoutMs, pollMs = 200) {
33511
+ const deadline = Date.now() + timeoutMs;
33512
+ for (; ; ) {
33513
+ if (daemonLockState(lock, true) === "stale") return true;
33514
+ if (Date.now() >= deadline) return false;
33515
+ await new Promise((resolve10) => setTimeout(resolve10, pollMs));
33516
+ }
33517
+ }
33518
+ function releaseOwnLock(lockPath) {
33519
+ try {
33520
+ if (parseDaemonLock(readFileSync12(lockPath, "utf8"))?.pid === process.pid) unlinkSync2(lockPath);
33521
+ } catch {
33522
+ }
33523
+ }
33503
33524
  function stopLockedDaemon(lockPath) {
33504
33525
  let original;
33505
33526
  try {
@@ -73598,7 +73619,7 @@ var init_report_render = __esm({
73598
73619
 
73599
73620
  // src/cli.ts
73600
73621
  init_src6();
73601
- import { closeSync as closeSync2, existsSync as existsSync39, openSync as openSync2, readFileSync as readFileSync42, renameSync as renameSync6, statSync as statSync12 } from "node:fs";
73622
+ import { closeSync as closeSync2, existsSync as existsSync39, openSync as openSync2, readFileSync as readFileSync41, renameSync as renameSync6, statSync as statSync12 } from "node:fs";
73602
73623
  import { join as join48, resolve as pathResolve2 } from "node:path";
73603
73624
  import { spawn as spawn5 } from "node:child_process";
73604
73625
 
@@ -82685,7 +82706,7 @@ function createWatchBreaker(deps) {
82685
82706
  }
82686
82707
 
82687
82708
  // src/engine.ts
82688
- var DAEMON_VERSION = true ? "2.0.18-dev.3596" : "2.0.0-alpha.0";
82709
+ var DAEMON_VERSION = true ? "2.0.18-dev.3612" : "2.0.0-alpha.0";
82689
82710
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
82690
82711
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
82691
82712
  var GIT_OP_MUTE_MS = 4e3;
@@ -85407,10 +85428,20 @@ async function startDaemon(opts) {
85407
85428
  };
85408
85429
  }
85409
85430
  async function listenServer(fetchFn, port) {
85410
- return new Promise((resolve10) => {
85431
+ return new Promise((resolve10, reject) => {
85411
85432
  const server = serve({ fetch: fetchFn, hostname: "127.0.0.1", port }, (info2) => {
85412
85433
  resolve10({ server, port: info2.port });
85413
85434
  });
85435
+ server.once(
85436
+ "error",
85437
+ (err2) => {
85438
+ reject(
85439
+ err2.code === "EADDRINUSE" ? new Error(
85440
+ `port ${port} is still held by another process \u2014 usually a previous errata daemon that has not finished shutting down. Wait for it to exit (errata stop waits for it), then start again.`
85441
+ ) : err2
85442
+ );
85443
+ }
85444
+ );
85414
85445
  });
85415
85446
  }
85416
85447
 
@@ -85531,6 +85562,17 @@ function workspaceCloud(cfg, entry, env2 = process.env) {
85531
85562
  profileName: name2
85532
85563
  };
85533
85564
  }
85565
+ function reportedIdentity(cfg, entry, env2 = process.env) {
85566
+ try {
85567
+ const ws = workspaceCloud(cfg, entry, env2);
85568
+ const machineDefault = cfg.activeInstallationProfile;
85569
+ const note = entry && ws.profileName !== machineDefault ? `this workspace pushes as profile "${ws.profileName ?? "top-level credential"}" (${ws.identity.reason}); machine default is "${machineDefault ?? "top-level credential"}"` : null;
85570
+ return { ok: true, config: ws.config, client: ws.client, profileName: ws.profileName, note };
85571
+ } catch (err2) {
85572
+ if (err2 instanceof IdentityBlockedError) return { ok: false, reason: err2.message };
85573
+ throw err2;
85574
+ }
85575
+ }
85534
85576
  function configWithDeviceLogin(cfg, input) {
85535
85577
  const name2 = input.profileName.trim();
85536
85578
  if (!name2 || name2.length > 80) throw new Error("profile name must be 1\u201380 characters");
@@ -85704,7 +85746,7 @@ function invalidateInstanceReceipt(current, destination, failedId, mirrorLegacy)
85704
85746
  // src/multi.ts
85705
85747
  init_dist();
85706
85748
  init_src5();
85707
- import { existsSync as existsSync34, readFileSync as readFileSync36, unlinkSync as unlinkSync5, writeFileSync as writeFileSync26 } from "node:fs";
85749
+ import { existsSync as existsSync34, writeFileSync as writeFileSync26 } from "node:fs";
85708
85750
  import { resolve as pathResolve } from "node:path";
85709
85751
 
85710
85752
  // src/principle-sync.ts
@@ -89646,6 +89688,7 @@ var SessionAffinity = class {
89646
89688
  // src/multi.ts
89647
89689
  init_checkout_root();
89648
89690
  var PROCESS_STARTED_AT = Date.now();
89691
+ var SHUTDOWN_CONNECTION_GRACE_MS = 2e3;
89649
89692
  var SELF_ENTRY = process.argv[1] ? pathResolve(process.argv[1]) : "";
89650
89693
  var LOCAL_BUILD_ENTRY = SELF_ENTRY && !isInstalledEntry(SELF_ENTRY) ? SELF_ENTRY : void 0;
89651
89694
  function withLaunchOrigin(pending) {
@@ -91468,21 +91511,23 @@ async function startMultiDaemon(opts = {}) {
91468
91511
  clearInterval(historyRetry);
91469
91512
  clearTimeout(codexWakeBoot);
91470
91513
  try {
91471
- const cur = readFileSync36(lockPath, "utf8");
91472
- if (JSON.parse(cur).pid === process.pid) unlinkSync5(lockPath);
91473
- } catch {
91474
- }
91475
- await new Promise(
91476
- (res) => server.close(res)
91477
- );
91478
- updatePoller?.stop();
91479
- healthSentinel.stop();
91480
- await Promise.allSettled(attaching.values());
91481
- for (const r of records) await r.engine.stop();
91482
- if (consolidateWorker) await consolidateWorker.stop();
91483
- try {
91484
- sharedStore.close();
91485
- } catch {
91514
+ const http = server;
91515
+ const closed = new Promise((res) => http.close(res));
91516
+ http.closeIdleConnections?.();
91517
+ const force = setTimeout(() => http.closeAllConnections?.(), SHUTDOWN_CONNECTION_GRACE_MS);
91518
+ await closed;
91519
+ clearTimeout(force);
91520
+ updatePoller?.stop();
91521
+ healthSentinel.stop();
91522
+ await Promise.allSettled(attaching.values());
91523
+ for (const r of records) await r.engine.stop();
91524
+ if (consolidateWorker) await consolidateWorker.stop();
91525
+ try {
91526
+ sharedStore.close();
91527
+ } catch {
91528
+ }
91529
+ } finally {
91530
+ releaseOwnLock(lockPath);
91486
91531
  }
91487
91532
  }
91488
91533
  };
@@ -91932,8 +91977,31 @@ function installedHarnessVersion(bin) {
91932
91977
  return null;
91933
91978
  }
91934
91979
  }
91980
+ function daemonDoctorCheck(input) {
91981
+ const { lock, state, selfVersion, lockPath } = input;
91982
+ if (!lock || state === "stale") {
91983
+ return { id: "daemon", status: "warn", detail: "Daemon is not running \u2014 start it: errata start" };
91984
+ }
91985
+ if (state === "unknown") {
91986
+ return {
91987
+ id: "daemon",
91988
+ status: "fail",
91989
+ detail: `${lockPath} names pid ${lock.pid}, but that process's identity cannot be verified. If \`ps -p ${lock.pid}\` shows no errata process, delete the lock; otherwise run errata stop.`
91990
+ };
91991
+ }
91992
+ const where = `pid ${lock.pid}${lock.webUiUrl ? ` \xB7 ${lock.webUiUrl}` : ""}`;
91993
+ if (lock.version && lock.version !== selfVersion) {
91994
+ return {
91995
+ id: "daemon",
91996
+ status: "warn",
91997
+ detail: `Running ${lock.version} (${where}), but this CLI is ${selfVersion} \u2014 restart it: errata stop, then errata start`
91998
+ };
91999
+ }
92000
+ return { id: "daemon", status: "pass", detail: `Running${lock.version ? ` ${lock.version}` : ""} \xB7 ${where}` };
92001
+ }
91935
92002
  async function diagnoseInstallation(cfg, dependencies) {
91936
92003
  const checks = [];
92004
+ if (dependencies.daemon) checks.push(dependencies.daemon());
91937
92005
  if (dependencies.codexHooks) checks.push(...await dependencies.codexHooks());
91938
92006
  const profileName = cfg.activeInstallationProfile;
91939
92007
  const profile = profileName ? cfg.installationProfiles[profileName] : void 0;
@@ -92030,12 +92098,12 @@ async function diagnoseInstallation(cfg, dependencies) {
92030
92098
 
92031
92099
  // src/codex-doctor.ts
92032
92100
  import { spawn as spawn4 } from "node:child_process";
92033
- import { readFileSync as readFileSync38, statSync as statSync10 } from "node:fs";
92101
+ import { readFileSync as readFileSync37, statSync as statSync10 } from "node:fs";
92034
92102
  import { join as join44 } from "node:path";
92035
92103
  import { createInterface } from "node:readline";
92036
92104
 
92037
92105
  // src/codex-hooks.ts
92038
- import { existsSync as existsSync35, mkdirSync as mkdirSync9, readFileSync as readFileSync37, renameSync as renameSync5, unlinkSync as unlinkSync6, writeFileSync as writeFileSync27 } from "node:fs";
92106
+ import { existsSync as existsSync35, mkdirSync as mkdirSync9, readFileSync as readFileSync36, renameSync as renameSync5, unlinkSync as unlinkSync5, writeFileSync as writeFileSync27 } from "node:fs";
92039
92107
  import { homedir as homedir7 } from "node:os";
92040
92108
  import { join as join43 } from "node:path";
92041
92109
 
@@ -92206,23 +92274,23 @@ function buildCodexRelaySource(workspaceRoot) {
92206
92274
  init_checkout_root();
92207
92275
  var CODEX_HOOK_REVIEW_HELP = "Codex desktop: Settings > Hooks, select this project, expand each event and Trust the Errata hook. CLI users can use /hooks.";
92208
92276
  function writeRelay(file2, source) {
92209
- if (existsSync35(file2) && readFileSync37(file2, "utf8") === source) return;
92277
+ if (existsSync35(file2) && readFileSync36(file2, "utf8") === source) return;
92210
92278
  const temp = `${file2}.${process.pid}.tmp`;
92211
92279
  try {
92212
92280
  writeFileSync27(temp, source, { encoding: "utf8", mode: 384 });
92213
92281
  renameSync5(temp, file2);
92214
92282
  } finally {
92215
- if (existsSync35(temp)) unlinkSync6(temp);
92283
+ if (existsSync35(temp)) unlinkSync5(temp);
92216
92284
  }
92217
92285
  }
92218
92286
  function refreshCodexRelay(root) {
92219
92287
  const config2 = join43(root, ".codex", "config.toml");
92220
92288
  if (!existsSync35(config2)) return "absent";
92221
- const existing = readFileSync37(config2, "utf8");
92289
+ const existing = readFileSync36(config2, "utf8");
92222
92290
  if (!existing.includes(CODEX_HOOKS_BEGIN) && !existing.includes(CODEX_HOOKS_END)) return "absent";
92223
92291
  replaceCodexHooks(existing, "");
92224
92292
  const relay = join43(root, ".codex", "hooks", "errata-relay.mjs");
92225
- const prior = existsSync35(relay) ? readFileSync37(relay, "utf8") : "";
92293
+ const prior = existsSync35(relay) ? readFileSync36(relay, "utf8") : "";
92226
92294
  if (prior && !prior.startsWith("// errata-managed: regenerated by errata install-hooks codex")) {
92227
92295
  throw new Error("Codex relay is not Errata-managed; preserved it. Run errata install-hooks codex to explicitly replace it.");
92228
92296
  }
@@ -92253,10 +92321,10 @@ function writeCodexHooks(root, port, nodePath = process.execPath) {
92253
92321
  const file2 = join43(dir, "config.toml");
92254
92322
  const relay = join43(dir, "hooks", "errata-relay.mjs");
92255
92323
  const block = buildCodexHooksToml(port, { nodePath, relayPath: relay });
92256
- const final = replaceCodexHooks(existsSync35(file2) ? readFileSync37(file2, "utf8") : "", block);
92324
+ const final = replaceCodexHooks(existsSync35(file2) ? readFileSync36(file2, "utf8") : "", block);
92257
92325
  mkdirSync9(join43(dir, "hooks"), { recursive: true });
92258
92326
  writeRelay(relay, buildCodexRelaySource(checkoutRootOf(root)));
92259
- if (!existsSync35(file2) || readFileSync37(file2, "utf8") !== final) writeFileSync27(file2, final, "utf8");
92327
+ if (!existsSync35(file2) || readFileSync36(file2, "utf8") !== final) writeFileSync27(file2, final, "utf8");
92260
92328
  return file2;
92261
92329
  }
92262
92330
 
@@ -92265,7 +92333,7 @@ init_checkout_root();
92265
92333
  function inspectCodexRelay(cwd) {
92266
92334
  const root = nearestGitRoot(cwd) ?? cwd;
92267
92335
  try {
92268
- const source = readFileSync38(join44(root, ".codex", "hooks", "errata-relay.mjs"), "utf8");
92336
+ const source = readFileSync37(join44(root, ".codex", "hooks", "errata-relay.mjs"), "utf8");
92269
92337
  if (source === buildCodexRelaySource(checkoutRootOf(root))) {
92270
92338
  return { id: "codex_relay", status: "pass", detail: "Installed Codex relay matches this Errata version" };
92271
92339
  }
@@ -92277,7 +92345,7 @@ function inspectCodexContext(cwd) {
92277
92345
  const root = checkoutRootOf(nearestGitRoot(cwd) ?? cwd);
92278
92346
  try {
92279
92347
  const file2 = join44(root, ".errata", "context.md");
92280
- if (statSync10(file2).size <= 128 * 1024 && readFileSync38(file2, "utf8").trim()) {
92348
+ if (statSync10(file2).size <= 128 * 1024 && readFileSync37(file2, "utf8").trim()) {
92281
92349
  return { id: "codex_context", status: "pass", detail: "Saved workspace context is readable by the relay; this does not prove live daemon freshness" };
92282
92350
  }
92283
92351
  } catch {
@@ -92453,7 +92521,7 @@ function consolidationGapMs(lastPassMs, policy) {
92453
92521
 
92454
92522
  // src/codex-hook-refresh.ts
92455
92523
  init_checkout_root();
92456
- import { existsSync as existsSync36, readdirSync as readdirSync13, readFileSync as readFileSync39 } from "node:fs";
92524
+ import { existsSync as existsSync36, readdirSync as readdirSync13, readFileSync as readFileSync38 } from "node:fs";
92457
92525
  import { dirname as dirname13, join as join45, resolve as resolve8 } from "node:path";
92458
92526
  init_registry();
92459
92527
  function codexHookRoots(roots) {
@@ -92471,7 +92539,7 @@ function codexHookRoots(roots) {
92471
92539
  }
92472
92540
  for (const name2 of names) {
92473
92541
  try {
92474
- const checkout = dirname13(readFileSync39(join45(dir, name2, "gitdir"), "utf8").trim());
92542
+ const checkout = dirname13(readFileSync38(join45(dir, name2, "gitdir"), "utf8").trim());
92475
92543
  if (mainCheckoutOfWorktree(checkout) === main2) found.add(checkout);
92476
92544
  } catch {
92477
92545
  }
@@ -92493,7 +92561,7 @@ function refreshInstalledCodexRelays(roots = listWorkspaceEntries().map((entry)
92493
92561
  }
92494
92562
 
92495
92563
  // src/claude-statusline.ts
92496
- import { existsSync as existsSync37, mkdirSync as mkdirSync10, readFileSync as readFileSync40, writeFileSync as writeFileSync28 } from "node:fs";
92564
+ import { existsSync as existsSync37, mkdirSync as mkdirSync10, readFileSync as readFileSync39, writeFileSync as writeFileSync28 } from "node:fs";
92497
92565
  import { homedir as homedir8 } from "node:os";
92498
92566
  import { dirname as dirname14, join as join46 } from "node:path";
92499
92567
  var STATUSLINE_RELAY_SOURCE = String.raw`// errata-managed: regenerated by errata init / install-hooks claude
@@ -92554,7 +92622,7 @@ function ensureUserStatusLine(opts) {
92554
92622
  let settings = {};
92555
92623
  if (existsSync37(settingsPath)) {
92556
92624
  try {
92557
- const parsed = JSON.parse(readFileSync40(settingsPath, "utf8"));
92625
+ const parsed = JSON.parse(readFileSync39(settingsPath, "utf8"));
92558
92626
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return "unreadable";
92559
92627
  settings = parsed;
92560
92628
  } catch {
@@ -92567,7 +92635,7 @@ function ensureUserStatusLine(opts) {
92567
92635
  const ownRows = keep(settings["subagentStatusLine"]);
92568
92636
  if (ownMain && ownRows) return "kept-own";
92569
92637
  mkdirSync10(opts.relayDir, { recursive: true });
92570
- if (!existsSync37(relay) || readFileSync40(relay, "utf8") !== STATUSLINE_RELAY_SOURCE) {
92638
+ if (!existsSync37(relay) || readFileSync39(relay, "utf8") !== STATUSLINE_RELAY_SOURCE) {
92571
92639
  writeFileSync28(relay, STATUSLINE_RELAY_SOURCE, "utf8");
92572
92640
  }
92573
92641
  const nodePath = opts.nodePath ?? process.execPath;
@@ -92600,7 +92668,7 @@ function describeStatusLineOutcome(outcome) {
92600
92668
 
92601
92669
  // src/claude-mods.ts
92602
92670
  import { createHash as createHash20 } from "node:crypto";
92603
- import { cpSync as cpSync2, existsSync as existsSync38, mkdirSync as mkdirSync11, readFileSync as readFileSync41, readdirSync as readdirSync14, rmSync as rmSync4, statSync as statSync11, writeFileSync as writeFileSync29 } from "node:fs";
92671
+ import { cpSync as cpSync2, existsSync as existsSync38, mkdirSync as mkdirSync11, readFileSync as readFileSync40, readdirSync as readdirSync14, rmSync as rmSync4, statSync as statSync11, writeFileSync as writeFileSync29 } from "node:fs";
92604
92672
  import { delimiter as delimiter2, dirname as dirname15, join as join47, relative as relative7, resolve as resolve9, sep as sep5 } from "node:path";
92605
92673
  import { fileURLToPath as fileURLToPath2 } from "node:url";
92606
92674
  var CLAUDE_PLUGIN_DIRS_ENV = "CLAUDE_CODE_PLUGIN_DIRS";
@@ -92628,7 +92696,7 @@ function modHash(dir) {
92628
92696
  for (const abs of listFiles(dir).sort()) {
92629
92697
  h.update(relative7(dir, abs).split(sep5).join("/"));
92630
92698
  h.update("\0");
92631
- h.update(readFileSync41(abs));
92699
+ h.update(readFileSync40(abs));
92632
92700
  h.update("\0");
92633
92701
  }
92634
92702
  return h.digest("hex");
@@ -92652,7 +92720,7 @@ function off(env2) {
92652
92720
  function readSettings(path2) {
92653
92721
  if (!existsSync38(path2)) return {};
92654
92722
  try {
92655
- const parsed = JSON.parse(readFileSync41(path2, "utf8"));
92723
+ const parsed = JSON.parse(readFileSync40(path2, "utf8"));
92656
92724
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
92657
92725
  return parsed;
92658
92726
  } catch {
@@ -92703,7 +92771,7 @@ function ensureClaudeMods(opts) {
92703
92771
  const dest = join47(modsRoot, name2);
92704
92772
  const hash2 = modHash(src);
92705
92773
  const hashFile = join47(dest, HASH_FILE);
92706
- if (!existsSync38(hashFile) || readFileSync41(hashFile, "utf8") !== hash2) {
92774
+ if (!existsSync38(hashFile) || readFileSync40(hashFile, "utf8") !== hash2) {
92707
92775
  rmSync4(dest, { recursive: true, force: true });
92708
92776
  cpSync2(src, dest, { recursive: true, filter: (p) => !isEngineOwned(relative7(src, p)) });
92709
92777
  writeFileSync29(hashFile, hash2, "utf8");
@@ -92875,6 +92943,8 @@ export const InerrataPlugin = async (input) => {
92875
92943
  }
92876
92944
 
92877
92945
  // src/cli.ts
92946
+ var STOP_WAIT_MS = 3e4;
92947
+ var SHUTDOWN_DEADLINE_MS = 2e4;
92878
92948
  var exitCleanOnEpipe = (err2) => {
92879
92949
  if (err2.code === "EPIPE") process.exit(0);
92880
92950
  throw err2;
@@ -92916,6 +92986,10 @@ async function main() {
92916
92986
  return cmdLogout();
92917
92987
  case "link":
92918
92988
  return cmdLink(rest);
92989
+ // Moving a workspace to another org/team IS the console link flow (the
92990
+ // page offers every org and team the person is in); `switch` names it.
92991
+ case "switch":
92992
+ return cmdLink(rest.filter((a) => a !== "--local"));
92919
92993
  case "unlink":
92920
92994
  return cmdUnlink();
92921
92995
  case "use":
@@ -93074,6 +93148,10 @@ Commands:
93074
93148
  --remote <name> derive the locator from a non-origin remote;
93075
93149
  --name <name> mint a locator-less project; no flags = re-resolve now
93076
93150
  --skip-hooks leave existing harness configuration unchanged
93151
+ switch Move THIS workspace to another of your orgs or teams (e.g. a
93152
+ company org \u2192 your personal org). Opens the Console to pick;
93153
+ only this workspace changes \u2014 your machine default and other
93154
+ workspaces stay put. Check with \`errata whoami\` from inside it.
93077
93155
  unlink Detach from the cloud project + disable ambient re-linking
93078
93156
  use [<handle>] Set the sticky session active agent the daemon acts as.
93079
93157
  No arg lists the agents you can act as + the current one;
@@ -93119,7 +93197,7 @@ function resolveHookPort(explicit) {
93119
93197
  }
93120
93198
  const settingsPath = join48(ROOT, ".claude", "settings.json");
93121
93199
  try {
93122
- if (existsSync39(settingsPath)) return installedHookPort(readFileSync42(settingsPath, "utf8")) ?? 7891;
93200
+ if (existsSync39(settingsPath)) return installedHookPort(readFileSync41(settingsPath, "utf8")) ?? 7891;
93123
93201
  } catch {
93124
93202
  }
93125
93203
  return 7891;
@@ -93284,6 +93362,8 @@ async function cmdStart() {
93284
93362
  console.log(`errata: workspace registered; attached to the running daemon`);
93285
93363
  console.log(` workspace: ${where}`);
93286
93364
  console.log(` daemon: ${running.webUiUrl ?? "(running)"} \xB7 pid ${running.pid}`);
93365
+ const versionNote = daemonVersionNote(running, DAEMON_VERSION);
93366
+ if (versionNote) for (const l of versionNote) console.log(` \u26A0 ${l}`);
93287
93367
  let watching = "on the daemon's next restart";
93288
93368
  if (running.webUiUrl) {
93289
93369
  try {
@@ -93307,7 +93387,7 @@ async function cmdStart() {
93307
93387
  if (up) {
93308
93388
  console.log(` daemon: running \xB7 pid ${up.pid} \xB7 ${up.webUiUrl ?? "(running)"}`);
93309
93389
  console.log(` logs: ${daemonLogPath()}`);
93310
- console.log(` watch it: errata dash \xB7 check it: errata status`);
93390
+ console.log(` follow it: ${followLogHint()} \xB7 check it: errata status`);
93311
93391
  } else {
93312
93392
  console.log(` \u26A0 daemon did not come up within 12s.`);
93313
93393
  console.log(` run \`errata dash\` in the foreground to see the error.`);
@@ -93419,8 +93499,10 @@ async function cmdStatus() {
93419
93499
  const lockPath = globalDaemonLock();
93420
93500
  const running = isDaemonAlive(lockPath) ? readDaemonLock(lockPath) : null;
93421
93501
  console.log(
93422
- ` daemon: ${running ? `running \xB7 pid ${running.pid} \xB7 ${running.webUiUrl ?? "(running)"}` : "not running (run: errata start)"}`
93502
+ ` daemon: ${running ? `running \xB7 pid ${running.pid}${running.version ? ` \xB7 ${running.version}` : ""} \xB7 ${running.webUiUrl ?? "(running)"}` : "not running (run: errata start)"}`
93423
93503
  );
93504
+ const versionNote = running ? daemonVersionNote(running, DAEMON_VERSION) : null;
93505
+ if (versionNote) for (const l of versionNote) console.log(` \u26A0 ${l}`);
93424
93506
  if (running && process.argv[1]) {
93425
93507
  const note = foreignDaemonNote(running, pathResolve2(process.argv[1]));
93426
93508
  if (note) for (const l of note) console.log(` \u26A0 ${l}`);
@@ -93479,15 +93561,20 @@ async function cmdStatus() {
93479
93561
  const shellCloud = describeCloudTarget(cfg, {}, process.env, globalConfigPath());
93480
93562
  for (const line of cloudTargetDrift(daemonCloud, shellCloud)) console.log(` \u26A0 ${line}`);
93481
93563
  }
93482
- console.log(` profile: ${cfg.activeInstallationProfile ?? "(unbound legacy credential)"}`);
93483
- if (!hasCloudCredential(cfg)) {
93564
+ const shown = reportedIdentity(cfg, workspaceEntryForPath(ROOT));
93565
+ const ecfg = shown.ok ? shown.config : cfg;
93566
+ console.log(` profile: ${ecfg.activeInstallationProfile ?? "(unbound legacy credential)"}`);
93567
+ if (!shown.ok) console.log(` \u26A0 ${shown.reason} \u2014 this workspace's capture holds locally until fixed`);
93568
+ else if (shown.note) console.log(` \u21B3 ${shown.note}`);
93569
+ const who = shown.ok && shown.note ? "" : ` (${ecfg.email})`;
93570
+ if (!hasCloudCredential(ecfg)) {
93484
93571
  console.log(` logged in: no`);
93485
- } else if (cfg.accessToken) {
93486
- console.log(` logged in: yes (${cfg.email}) \u2014 attested daemon, can contribute`);
93572
+ } else if (ecfg.accessToken) {
93573
+ console.log(` logged in: yes${who} \u2014 attested daemon, can contribute`);
93487
93574
  } else {
93488
- console.log(` logged in: yes (${cfg.email}) \u2014 key capability enforced by Console installation/scopes`);
93575
+ console.log(` logged in: yes${who} \u2014 key capability enforced by Console installation/scopes`);
93489
93576
  }
93490
- const active3 = getActiveAgent(cfg);
93577
+ const active3 = getActiveAgent(ecfg);
93491
93578
  console.log(
93492
93579
  ` active agent: ${active3 ? `${active3.handle} (acting-as; \`errata use --none\` to clear)` : "default identity"}`
93493
93580
  );
@@ -93499,7 +93586,7 @@ async function cmdStatus() {
93499
93586
  console.log(` policy: blocked - ${decision.reason}`);
93500
93587
  if (decision.guidance) console.log(` ${decision.guidance}`);
93501
93588
  }
93502
- if (hasCloudCredential(cfg)) {
93589
+ if (hasCloudCredential(ecfg)) {
93503
93590
  if (!decision.allowed) {
93504
93591
  console.log(
93505
93592
  ` reachable: ${inspection.reachable ? `yes (${inspection.probe})` : `no (${inspection.error ?? "unreachable"})`}`
@@ -93508,7 +93595,7 @@ async function cmdStatus() {
93508
93595
  return;
93509
93596
  }
93510
93597
  try {
93511
- const c = authedCloudClient(cfg);
93598
+ const c = shown.ok ? shown.client : authedCloudClient(cfg);
93512
93599
  const me = await c.me();
93513
93600
  console.log(` reachable: yes \xB7 authenticated as ${me.handle} (${me.tier})`);
93514
93601
  } catch (err2) {
@@ -93603,19 +93690,26 @@ async function cmdWhoami(args2) {
93603
93690
  if (identity.invalid) process.exitCode = 1;
93604
93691
  return;
93605
93692
  }
93606
- if (!hasCloudCredential(cfg)) {
93693
+ const shown = reportedIdentity(cfg, workspaceEntryForPath(ROOT));
93694
+ if (!shown.ok) {
93695
+ console.error(`${shown.reason} \u2014 this workspace's capture holds locally (see \`errata whoami --workspace\`)`);
93696
+ process.exitCode = 1;
93697
+ return;
93698
+ }
93699
+ if (!hasCloudCredential(shown.config)) {
93607
93700
  console.error("not logged in \u2014 run `errata login` or select an installation profile");
93608
93701
  process.exitCode = 1;
93609
93702
  return;
93610
93703
  }
93611
93704
  try {
93612
- const active3 = getActiveAgent(cfg);
93613
- const response = await resolveWhoami(authedCloudClient(cfg), active3?.handle ?? null);
93705
+ const active3 = getActiveAgent(shown.config);
93706
+ const response = await resolveWhoami(shown.client, active3?.handle ?? null);
93614
93707
  if (args2.includes("--json")) {
93615
93708
  console.log(JSON.stringify(response, null, 2));
93616
93709
  return;
93617
93710
  }
93618
- for (const line of formatWhoami(response, cfg.activeInstallationProfile)) console.log(line);
93711
+ if (shown.note) console.log(`(${shown.note})`);
93712
+ for (const line of formatWhoami(response, shown.profileName ?? null)) console.log(line);
93619
93713
  } catch (err2) {
93620
93714
  console.error(`could not resolve context: ${err2 instanceof Error ? err2.message : err2}`);
93621
93715
  process.exitCode = 1;
@@ -93625,6 +93719,11 @@ async function cmdDoctor(args2) {
93625
93719
  const cfg = loadConfig();
93626
93720
  const report = await diagnoseInstallation(cfg, {
93627
93721
  ...hasCodexConfig(process.cwd()) ? { codexHooks: () => diagnoseCodexHooks(process.cwd()) } : {},
93722
+ daemon: () => {
93723
+ const lockPath = globalDaemonLock();
93724
+ const lock = readDaemonLock(lockPath);
93725
+ return daemonDoctorCheck({ lock, state: daemonLockState(lock, true), selfVersion: DAEMON_VERSION, lockPath });
93726
+ },
93628
93727
  inspect: () => inspectCloudEndpoint(cfg.cloudUrl),
93629
93728
  authenticate: async () => {
93630
93729
  const me = await authedCloudClient(cfg).me();
@@ -93641,7 +93740,7 @@ async function cmdDoctor(args2) {
93641
93740
  const marker = check2.status === "pass" ? "\u2713" : check2.status === "warn" ? "!" : "\u2717";
93642
93741
  console.log(` ${marker} ${check2.id}: ${check2.detail}`);
93643
93742
  }
93644
- if (!report.ok) {
93743
+ if (report.checks.some((check2) => check2.status === "fail" && check2.id !== "daemon")) {
93645
93744
  console.log(
93646
93745
  " recovery: `errata profile list`, then `errata profile use <name>`; re-login if authentication still fails."
93647
93746
  );
@@ -93707,7 +93806,8 @@ async function cmdUpdate(args2) {
93707
93806
  }
93708
93807
  console.log(`
93709
93808
  [OK] updated to ${status.latest}. Restart the daemon to run it:`);
93710
- console.log(` errata stop (autostart relaunches it at next logon, or run 'errata dash')`);
93809
+ console.log(` errata stop (waits for the old daemon to exit)`);
93810
+ console.log(` errata start (starts the updated one; autostart also relaunches it at next logon)`);
93711
93811
  } else {
93712
93812
  console.log(`
93713
93813
  [X] npm install failed (exit ${code}). Try manually:`);
@@ -94344,7 +94444,7 @@ async function cmdReview() {
94344
94444
  console.log("(review queue empty)");
94345
94445
  return;
94346
94446
  }
94347
- const queue = JSON.parse(readFileSync42(paths.reviewQueue, "utf8"));
94447
+ const queue = JSON.parse(readFileSync41(paths.reviewQueue, "utf8"));
94348
94448
  if (queue.length === 0) {
94349
94449
  console.log("(review queue empty)");
94350
94450
  return;
@@ -95082,8 +95182,20 @@ async function cmdStop() {
95082
95182
  return;
95083
95183
  }
95084
95184
  try {
95185
+ const lock = readDaemonLock(lockPath);
95085
95186
  const result = stopLockedDaemon(lockPath);
95086
- if (result === "signaled") console.log(`sent SIGTERM to the verified daemon process`);
95187
+ if (result === "signaled" && lock) {
95188
+ console.log(`sent SIGTERM to the daemon (pid ${lock.pid}); waiting for it to exit\u2026`);
95189
+ if (await waitForDaemonExit(lock, STOP_WAIT_MS)) {
95190
+ stopLockedDaemon(lockPath);
95191
+ console.log(`daemon stopped \u2014 safe to start it again`);
95192
+ } else {
95193
+ console.warn(
95194
+ `daemon (pid ${lock.pid}) has not exited after ${STOP_WAIT_MS / 1e3}s; it may still hold port 7891 and its stores. Check it with \`ps -p ${lock.pid}\` (Windows: \`Get-Process -Id ${lock.pid}\`) before starting again.`
95195
+ );
95196
+ process.exitCode = 1;
95197
+ }
95198
+ } else if (result === "signaled") console.log(`sent SIGTERM to the verified daemon process`);
95087
95199
  else if (result === "stale") console.log(`removed stale daemon lock; no process was signaled`);
95088
95200
  else if (result === "missing") console.log(`no daemon lock found \u2014 nothing to stop`);
95089
95201
  else throw new Error(`daemon lock ownership is ${result}; no process was signaled and the lock was preserved`);
@@ -95181,13 +95293,13 @@ async function cmdInstallHooks(args2) {
95181
95293
  }
95182
95294
  }
95183
95295
  async function installOpencodePlugin(port) {
95184
- const { mkdirSync: mkdirSync12, existsSync: existsSync40, readFileSync: readFileSync43, writeFileSync: writeFileSync30 } = await import("node:fs");
95296
+ const { mkdirSync: mkdirSync12, existsSync: existsSync40, readFileSync: readFileSync42, writeFileSync: writeFileSync30 } = await import("node:fs");
95185
95297
  const { join: join49 } = await import("node:path");
95186
95298
  const dir = join49(ROOT, ".opencode", "plugin");
95187
95299
  if (!existsSync40(dir)) mkdirSync12(dir, { recursive: true });
95188
95300
  const file2 = join49(dir, "inerrata.js");
95189
95301
  if (existsSync40(file2)) {
95190
- const head2 = readFileSync43(file2, "utf8").split("\n")[0] ?? "";
95302
+ const head2 = readFileSync42(file2, "utf8").split("\n")[0] ?? "";
95191
95303
  if (head2.trim() !== OPENCODE_PLUGIN_MARKER) {
95192
95304
  console.error(`refusing to overwrite ${file2}: not an errata-managed plugin (line 1 lacks the marker)`);
95193
95305
  process.exit(2);
@@ -95198,7 +95310,7 @@ async function installOpencodePlugin(port) {
95198
95310
  let cfg = { $schema: "https://opencode.ai/config.json" };
95199
95311
  if (existsSync40(cfgFile)) {
95200
95312
  try {
95201
- cfg = JSON.parse(readFileSync43(cfgFile, "utf8"));
95313
+ cfg = JSON.parse(readFileSync42(cfgFile, "utf8"));
95202
95314
  } catch {
95203
95315
  console.error(`refusing to touch invalid JSON at ${cfgFile} \u2014 fix it and re-run`);
95204
95316
  process.exit(2);
@@ -95244,7 +95356,7 @@ function installClaudeMods() {
95244
95356
  }
95245
95357
  }
95246
95358
  async function installClaudeHooks(port) {
95247
- const { mkdirSync: mkdirSync12, existsSync: existsSync40, readFileSync: readFileSync43, writeFileSync: writeFileSync30 } = await import("node:fs");
95359
+ const { mkdirSync: mkdirSync12, existsSync: existsSync40, readFileSync: readFileSync42, writeFileSync: writeFileSync30 } = await import("node:fs");
95248
95360
  const { join: join49 } = await import("node:path");
95249
95361
  const dir = join49(ROOT, ".claude");
95250
95362
  if (!existsSync40(dir)) mkdirSync12(dir, { recursive: true });
@@ -95252,7 +95364,7 @@ async function installClaudeHooks(port) {
95252
95364
  let settings = {};
95253
95365
  if (existsSync40(file2)) {
95254
95366
  try {
95255
- settings = JSON.parse(readFileSync43(file2, "utf8"));
95367
+ settings = JSON.parse(readFileSync42(file2, "utf8"));
95256
95368
  } catch {
95257
95369
  console.error(`refusing to overwrite invalid JSON at ${file2}`);
95258
95370
  process.exit(2);
@@ -95318,7 +95430,7 @@ async function installClaudeHooks(port) {
95318
95430
  console.log(` endpoint: http://127.0.0.1:${port}/api/hook`);
95319
95431
  }
95320
95432
  async function installClaudeMcpConfig() {
95321
- const { mkdirSync: mkdirSync12, existsSync: existsSync40, readFileSync: readFileSync43, writeFileSync: writeFileSync30 } = await import("node:fs");
95433
+ const { mkdirSync: mkdirSync12, existsSync: existsSync40, readFileSync: readFileSync42, writeFileSync: writeFileSync30 } = await import("node:fs");
95322
95434
  const { join: join49, dirname: dirname16 } = await import("node:path");
95323
95435
  const file2 = join49(ROOT, ".mcp.json");
95324
95436
  const dir = dirname16(file2);
@@ -95326,7 +95438,7 @@ async function installClaudeMcpConfig() {
95326
95438
  let cfg = {};
95327
95439
  if (existsSync40(file2)) {
95328
95440
  try {
95329
- cfg = JSON.parse(readFileSync43(file2, "utf8"));
95441
+ cfg = JSON.parse(readFileSync42(file2, "utf8"));
95330
95442
  } catch {
95331
95443
  console.error(`refusing to overwrite invalid JSON at ${file2}`);
95332
95444
  process.exit(2);
@@ -95340,7 +95452,7 @@ async function installClaudeMcpConfig() {
95340
95452
  console.log(` Tools: errata.search / locate / neighbors / callers_of / what_uses / show / similar`);
95341
95453
  }
95342
95454
  async function installCursorMcpConfig() {
95343
- const { mkdirSync: mkdirSync12, existsSync: existsSync40, readFileSync: readFileSync43, writeFileSync: writeFileSync30 } = await import("node:fs");
95455
+ const { mkdirSync: mkdirSync12, existsSync: existsSync40, readFileSync: readFileSync42, writeFileSync: writeFileSync30 } = await import("node:fs");
95344
95456
  const { join: join49 } = await import("node:path");
95345
95457
  const dir = join49(ROOT, ".cursor");
95346
95458
  if (!existsSync40(dir)) mkdirSync12(dir, { recursive: true });
@@ -95348,7 +95460,7 @@ async function installCursorMcpConfig() {
95348
95460
  let cfg = {};
95349
95461
  if (existsSync40(file2)) {
95350
95462
  try {
95351
- cfg = JSON.parse(readFileSync43(file2, "utf8"));
95463
+ cfg = JSON.parse(readFileSync42(file2, "utf8"));
95352
95464
  } catch {
95353
95465
  console.error(`refusing to overwrite invalid JSON at ${file2}`);
95354
95466
  process.exit(2);
@@ -95551,7 +95663,21 @@ function installLogTimestamps() {
95551
95663
  };
95552
95664
  }
95553
95665
  }
95666
+ function followLogHint() {
95667
+ const log = daemonLogPath();
95668
+ return process.platform === "win32" ? `Get-Content -Wait "${log}"` : `tail -f "${log}"`;
95669
+ }
95554
95670
  async function cmdDash(args2) {
95671
+ const running = readDaemonLock(globalDaemonLock());
95672
+ if (running && daemonLockState(running, true) === "running") {
95673
+ console.log(
95674
+ `errata daemon already running \xB7 pid ${running.pid}${running.version ? ` \xB7 ${running.version}` : ""}${running.webUiUrl ? ` \xB7 ${running.webUiUrl}` : ""}`
95675
+ );
95676
+ console.log(` follow it: ${followLogHint()}`);
95677
+ console.log(` check it: errata status`);
95678
+ console.log(` restart it: errata stop, then errata start`);
95679
+ return;
95680
+ }
95555
95681
  process.title = "errata-daemon";
95556
95682
  installLogTimestamps();
95557
95683
  refreshExistingCodexHooks();
@@ -95921,6 +96047,11 @@ async function cmdDash(args2) {
95921
96047
  const shutdown = async (signal) => {
95922
96048
  console.log(`
95923
96049
  shutting down\u2026 (${signal})`);
96050
+ setTimeout(() => {
96051
+ console.warn(`[errata] shutdown exceeded ${SHUTDOWN_DEADLINE_MS / 1e3}s \u2014 forcing exit`);
96052
+ releaseOwnLock(globalDaemonLock());
96053
+ process.exit(1);
96054
+ }, SHUTDOWN_DEADLINE_MS).unref();
95924
96055
  clearInterval(interval);
95925
96056
  clearInterval(nightlyInterval);
95926
96057
  if (quiesceTimer) clearTimeout(quiesceTimer);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.18-dev.3596",
3
+ "version": "2.0.18-dev.3612",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {