@inerrata-corporation/errata 2.0.0-dev.96 → 2.0.0-dev.98

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 +872 -69
  2. package/package.json +1 -1
package/errata.mjs CHANGED
@@ -21786,11 +21786,24 @@ var init_client = __esm({
21786
21786
  async actableAgents() {
21787
21787
  return this.json("GET", "/api/gate/actable-agents");
21788
21788
  }
21789
+ /** Resolve the exact installation and credential context used by this client. */
21790
+ async context() {
21791
+ return this.json("GET", "/api/gate/context");
21792
+ }
21793
+ /** Resolve context after an audited, sessionless act-as re-mint. Useful for
21794
+ * CLI diagnostics that must report the same identity a selected agent uses. */
21795
+ async contextAs(handle2, teamId) {
21796
+ const acting = await this.actAs(handle2, teamId);
21797
+ return this.json("GET", "/api/gate/context", void 0, { bearerToken: acting.token });
21798
+ }
21789
21799
  /** Mint a fresh gateway JWT stamped with `handle` (authz-checked + audited
21790
21800
  * server-side). The returned `token` bears the acting-agent identity for
21791
21801
  * subsequent cloud calls. Authenticated with the caller's base JWT. */
21792
- async actAs(handle2) {
21793
- return this.json("POST", "/api/gate/act-as", { handle: handle2 });
21802
+ async actAs(handle2, teamId) {
21803
+ return this.json("POST", "/api/gate/act-as", {
21804
+ handle: handle2,
21805
+ ...teamId !== void 0 ? { teamId } : {}
21806
+ });
21794
21807
  }
21795
21808
  /** Resolve-or-create the org's project for a repo locator (ambient link,
21796
21809
  * PJ-entity). Requires user identity on the JWT — login-gated like actAs. */
@@ -22163,7 +22176,7 @@ var init_client = __esm({
22163
22176
  ...provenanceHeaders(this.provenance)
22164
22177
  };
22165
22178
  if (!opts?.skipAuth) {
22166
- const token = await this.authToken();
22179
+ const token = opts?.bearerToken ?? await this.authToken();
22167
22180
  if (token) headers["authorization"] = `Bearer ${token}`;
22168
22181
  }
22169
22182
  const ac = new AbortController();
@@ -22377,7 +22390,9 @@ function defaultConfig() {
22377
22390
  onboardedAt: null,
22378
22391
  machineId: null,
22379
22392
  updateChannel: "dev",
22380
- activeAgent: null
22393
+ activeAgent: null,
22394
+ installationProfiles: {},
22395
+ activeInstallationProfile: null
22381
22396
  };
22382
22397
  }
22383
22398
  function loadConfig() {
@@ -22402,14 +22417,89 @@ function loadConfig() {
22402
22417
  cloudUrl: envCloudUrl ?? (migrateLegacyDefault ? DEFAULT_CLOUD_URL : parsed.cloudUrl ?? base.cloudUrl),
22403
22418
  // Deep-merge consent so an old/partial config keeps the opt-in defaults
22404
22419
  // for any channel it doesn't mention.
22405
- consent: { ...base.consent, ...parsed.consent ?? {} }
22420
+ consent: { ...base.consent, ...parsed.consent ?? {} },
22421
+ installationProfiles: { ...parsed.installationProfiles ?? {} }
22406
22422
  };
22407
22423
  if (migrateLegacyDefault && !envCloudUrl) saveConfig(resolved);
22408
22424
  return resolved;
22409
22425
  }
22410
22426
  function saveConfig(cfg) {
22411
22427
  ensureDir(globalDir());
22412
- writeFileSync5(globalConfigPath(), JSON.stringify(cfg, null, 2), { encoding: "utf8", mode: 384 });
22428
+ const normalized = snapshotActiveInstallationProfile(cfg);
22429
+ writeFileSync5(globalConfigPath(), JSON.stringify(normalized, null, 2), { encoding: "utf8", mode: 384 });
22430
+ }
22431
+ function profileFromCurrent(cfg, installationId) {
22432
+ return {
22433
+ installationId,
22434
+ cloudUrl: cfg.cloudUrl,
22435
+ apiKey: cfg.apiKey,
22436
+ accessToken: cfg.accessToken,
22437
+ refreshToken: cfg.refreshToken,
22438
+ tokenEndpoint: cfg.tokenEndpoint,
22439
+ oauthClientId: cfg.oauthClientId,
22440
+ activeAgent: cfg.activeAgent
22441
+ };
22442
+ }
22443
+ function snapshotActiveInstallationProfile(cfg) {
22444
+ const name2 = cfg.activeInstallationProfile;
22445
+ if (!name2) return cfg;
22446
+ const existing = cfg.installationProfiles[name2];
22447
+ if (!existing) return { ...cfg, activeInstallationProfile: null };
22448
+ return {
22449
+ ...cfg,
22450
+ installationProfiles: {
22451
+ ...cfg.installationProfiles,
22452
+ [name2]: profileFromCurrent(cfg, existing.installationId)
22453
+ }
22454
+ };
22455
+ }
22456
+ function saveInstallationProfile(name2, installationId, cfg = loadConfig()) {
22457
+ const normalizedName = name2.trim();
22458
+ if (!normalizedName || normalizedName.length > 80) {
22459
+ throw new Error("profile name must be 1\u201380 characters");
22460
+ }
22461
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(installationId)) {
22462
+ throw new Error("installation id must be a UUID");
22463
+ }
22464
+ const next = {
22465
+ ...cfg,
22466
+ installationProfiles: {
22467
+ ...cfg.installationProfiles,
22468
+ [normalizedName]: profileFromCurrent(cfg, installationId)
22469
+ },
22470
+ activeInstallationProfile: normalizedName
22471
+ };
22472
+ saveConfig(next);
22473
+ return next;
22474
+ }
22475
+ function useInstallationProfile(name2, cfg = loadConfig()) {
22476
+ const profile = cfg.installationProfiles[name2];
22477
+ if (!profile) throw new Error(`installation profile not found: ${name2}`);
22478
+ const next = {
22479
+ ...cfg,
22480
+ cloudUrl: profile.cloudUrl,
22481
+ apiKey: profile.apiKey,
22482
+ accessToken: profile.accessToken,
22483
+ refreshToken: profile.refreshToken,
22484
+ tokenEndpoint: profile.tokenEndpoint,
22485
+ oauthClientId: profile.oauthClientId,
22486
+ activeAgent: profile.activeAgent,
22487
+ activeInstallationProfile: name2
22488
+ };
22489
+ saveConfig(next);
22490
+ return next;
22491
+ }
22492
+ function removeInstallationProfile(name2, cfg = loadConfig()) {
22493
+ if (!cfg.installationProfiles[name2]) return cfg;
22494
+ const installationProfiles = { ...cfg.installationProfiles };
22495
+ delete installationProfiles[name2];
22496
+ const next = {
22497
+ ...cfg,
22498
+ installationProfiles,
22499
+ activeInstallationProfile: cfg.activeInstallationProfile === name2 ? null : cfg.activeInstallationProfile
22500
+ };
22501
+ saveConfig(next);
22502
+ return next;
22413
22503
  }
22414
22504
  function machineId() {
22415
22505
  const fromEnv = process.env["ERRATA_MACHINE_ID"];
@@ -22444,12 +22534,30 @@ var init_config = __esm({
22444
22534
  });
22445
22535
 
22446
22536
  // src/cloud-endpoint-policy.ts
22537
+ function warnDirectV1OverrideOnce() {
22538
+ if (warnedDirectV1Override) return;
22539
+ warnedDirectV1Override = true;
22540
+ console.error(
22541
+ [
22542
+ "",
22543
+ "!!! DIRECT V1 OVERRIDE ACTIVE !!!",
22544
+ `${DIRECT_V1_OVERRIDE_ENV} + ${DIRECT_V1_ACK_ENV} are set: this daemon is talking`,
22545
+ "straight to the legacy v1 data plane, BYPASSING the Console Gateway \u2014",
22546
+ "no metering, no signed context enforcement, no write-target binding.",
22547
+ "Staff testing only. This escape hatch is removed at v1 EOL.",
22548
+ ""
22549
+ ].join("\n")
22550
+ );
22551
+ }
22552
+ function isDirectV1OverrideRequested() {
22553
+ const raw2 = process.env[DIRECT_V1_OVERRIDE_ENV]?.trim().toLowerCase();
22554
+ return raw2 === "1" || raw2 === "true" || raw2 === "yes" || raw2 === "on";
22555
+ }
22447
22556
  function normalizeCloudUrl(url2) {
22448
22557
  return url2.replace(/\/+$/, "");
22449
22558
  }
22450
22559
  function isDirectV1OverrideEnabled() {
22451
- const raw2 = process.env[DIRECT_V1_OVERRIDE_ENV]?.trim().toLowerCase();
22452
- return raw2 === "1" || raw2 === "true" || raw2 === "yes" || raw2 === "on";
22560
+ return isDirectV1OverrideRequested() && process.env[DIRECT_V1_ACK_ENV]?.trim() === DIRECT_V1_ACK_VALUE;
22453
22561
  }
22454
22562
  function isLocalCloudUrl(url2) {
22455
22563
  try {
@@ -22481,18 +22589,20 @@ function evaluateCloudEndpoint(url2, inspection) {
22481
22589
  return { allowed: true, label: "gateway-configured" };
22482
22590
  }
22483
22591
  if (isDirectV1OverrideEnabled()) {
22592
+ warnDirectV1OverrideOnce();
22484
22593
  return {
22485
22594
  allowed: true,
22486
22595
  label: "legacy-direct (override)",
22487
- reason: `${DIRECT_V1_OVERRIDE_ENV} is enabled`
22596
+ reason: `${DIRECT_V1_OVERRIDE_ENV} acknowledged via ${DIRECT_V1_ACK_ENV}`
22488
22597
  };
22489
22598
  }
22490
22599
  const service = inspection?.service ? ` (${inspection.service})` : "";
22600
+ const unacked = isDirectV1OverrideRequested() ? ` ${DIRECT_V1_OVERRIDE_ENV} is set but unacknowledged \u2014 staff testing additionally requires ${DIRECT_V1_ACK_ENV}=${DIRECT_V1_ACK_VALUE}.` : "";
22491
22601
  return {
22492
22602
  allowed: false,
22493
22603
  label: "legacy-direct blocked",
22494
22604
  reason: `non-local cloud endpoint is not the Console Gateway${service}`,
22495
- guidance: `Point ERRATA_CLOUD_URL at the Console Gateway, or set ${DIRECT_V1_OVERRIDE_ENV}=1 only for accepted legacy/direct-v1 testing.`
22605
+ guidance: `Point ERRATA_CLOUD_URL at the Console Gateway.${unacked}`
22496
22606
  };
22497
22607
  }
22498
22608
  function formatCloudEndpointBlock(url2, decision) {
@@ -22634,13 +22744,16 @@ async function fetchHealthJson(fetchFn, url2, timeoutMs) {
22634
22744
  clearTimeout(tid);
22635
22745
  }
22636
22746
  }
22637
- var DIRECT_V1_OVERRIDE_ENV, GATEWAY_SERVICE, PROBE_TIMEOUT_MS, CloudEndpointPolicyError, BLOCKED_RECHECK_MS;
22747
+ var DIRECT_V1_OVERRIDE_ENV, DIRECT_V1_ACK_ENV, DIRECT_V1_ACK_VALUE, GATEWAY_SERVICE, PROBE_TIMEOUT_MS, warnedDirectV1Override, CloudEndpointPolicyError, BLOCKED_RECHECK_MS;
22638
22748
  var init_cloud_endpoint_policy = __esm({
22639
22749
  "src/cloud-endpoint-policy.ts"() {
22640
22750
  "use strict";
22641
22751
  DIRECT_V1_OVERRIDE_ENV = "ERRATA_ALLOW_DIRECT_V1_CLOUD";
22752
+ DIRECT_V1_ACK_ENV = "ERRATA_DIRECT_V1_ACK";
22753
+ DIRECT_V1_ACK_VALUE = "I_ACCEPT_UNMETERED_DIRECT_V1";
22642
22754
  GATEWAY_SERVICE = "console-gateway";
22643
22755
  PROBE_TIMEOUT_MS = 2e3;
22756
+ warnedDirectV1Override = false;
22644
22757
  CloudEndpointPolicyError = class extends Error {
22645
22758
  decision;
22646
22759
  constructor(message, decision) {
@@ -22678,6 +22791,7 @@ function authedCloudClient(cfg, opts = {}) {
22678
22791
  const versionOpts = opts.daemonVersion ? { daemonVersion: opts.daemonVersion, daemonChannel: cfg.updateChannel } : {};
22679
22792
  if (cfg.accessToken) {
22680
22793
  const tokenEndpoint = cfg.tokenEndpoint;
22794
+ const installationId = cfg.activeInstallationProfile ? cfg.installationProfiles[cfg.activeInstallationProfile]?.installationId : void 0;
22681
22795
  return new CloudClient({
22682
22796
  baseUrl,
22683
22797
  accessToken: cfg.accessToken,
@@ -22687,7 +22801,11 @@ function authedCloudClient(cfg, opts = {}) {
22687
22801
  provenance: daemonProvenance(),
22688
22802
  ...opts.timeoutMs ? { timeoutMs: opts.timeoutMs } : {},
22689
22803
  ...tokenEndpoint && cfg.refreshToken ? {
22690
- refreshFn: (rt) => refreshAccessToken(opts.fetchFn ?? fetch, tokenEndpoint, {
22804
+ refreshFn: (rt) => tokenEndpoint.endsWith("/api/gate/session-token") && installationId ? refreshDeviceSessionToken(opts.fetchFn ?? fetch, tokenEndpoint, {
22805
+ sessionToken: rt,
22806
+ installationId,
22807
+ clientId: cfg.oauthClientId ?? oauthClientId()
22808
+ }) : refreshAccessToken(opts.fetchFn ?? fetch, tokenEndpoint, {
22691
22809
  refreshToken: rt,
22692
22810
  clientId: cfg.oauthClientId ?? oauthClientId()
22693
22811
  }),
@@ -22710,6 +22828,30 @@ function authedCloudClient(cfg, opts = {}) {
22710
22828
  ...opts.timeoutMs ? { timeoutMs: opts.timeoutMs } : {}
22711
22829
  });
22712
22830
  }
22831
+ async function refreshDeviceSessionToken(fetchFn, endpoint, input) {
22832
+ const response = await fetchFn(endpoint, {
22833
+ method: "POST",
22834
+ headers: {
22835
+ authorization: `Bearer ${input.sessionToken}`,
22836
+ "content-type": "application/json",
22837
+ accept: "application/json"
22838
+ },
22839
+ body: JSON.stringify({
22840
+ installationId: input.installationId,
22841
+ clientId: input.clientId,
22842
+ scopes: ["graph:read", "graph:write", "mcp:tools"]
22843
+ })
22844
+ });
22845
+ const body2 = await response.json().catch(() => null);
22846
+ if (!response.ok || typeof body2?.["access_token"] !== "string") {
22847
+ throw new Error(`device session refresh failed: HTTP ${response.status}`);
22848
+ }
22849
+ return {
22850
+ accessToken: body2["access_token"],
22851
+ refreshToken: input.sessionToken,
22852
+ ...typeof body2["expires_in"] === "number" ? { expiresInSec: body2["expires_in"] } : {}
22853
+ };
22854
+ }
22713
22855
  function hasCloudCredential(cfg) {
22714
22856
  return Boolean(cfg.accessToken || cfg.apiKey);
22715
22857
  }
@@ -36701,14 +36843,15 @@ function createMcpHandler(store, ctx = {}) {
36701
36843
  };
36702
36844
  }
36703
36845
  function buildToolContext() {
36846
+ const base = { switchInstallationProfile: useInstallationProfile };
36704
36847
  try {
36705
36848
  const cfg = loadConfig();
36706
36849
  if (cfg.consent.sync && hasCloudCredential(cfg) && cfg.cloudUrl) {
36707
- return { cloud: authedCloudClient(cfg) };
36850
+ return { ...base, cloud: authedCloudClient(cfg) };
36708
36851
  }
36709
36852
  } catch {
36710
36853
  }
36711
- return {};
36854
+ return base;
36712
36855
  }
36713
36856
  async function runMcpServer(workspaceRoot) {
36714
36857
  const paths = workspacePaths(workspaceRoot);
@@ -36817,6 +36960,41 @@ var init_mcp = __esm({
36817
36960
  AUDIT_NUDGE_COOLDOWN = 12;
36818
36961
  AUDIT_FLAG_NUDGE = "\u26A1 errata \u2014 reading/auditing? anything you notice that's off, flag it inline as `[!one line]` (`[?\u2026]` = TODO) \u2014 no tool call, we harvest it.";
36819
36962
  TOOLS = [
36963
+ {
36964
+ name: "errata.switch_context",
36965
+ description: "Switch this daemon to an already-installed Personal, organization, or Team profile. The profile's full credential set changes with the selection; this never widens the current token or carries consent between installations. Returns the selected installation descriptor so the caller can verify what changed.",
36966
+ inputSchema: {
36967
+ type: "object",
36968
+ properties: {
36969
+ profile: {
36970
+ type: "string",
36971
+ minLength: 1,
36972
+ maxLength: 80,
36973
+ description: "Exact name from `errata profile list`."
36974
+ }
36975
+ },
36976
+ required: ["profile"],
36977
+ additionalProperties: false
36978
+ },
36979
+ handler: (args2, _store, ctx) => {
36980
+ const profileName = typeof args2["profile"] === "string" ? args2["profile"].trim() : "";
36981
+ if (!profileName) throw new Error("profile is required");
36982
+ const next = (ctx.switchInstallationProfile ?? useInstallationProfile)(profileName);
36983
+ const profile = next.installationProfiles[profileName];
36984
+ if (!profile) throw new Error(`installation profile not found: ${profileName}`);
36985
+ return {
36986
+ switched: true,
36987
+ profile: profileName,
36988
+ installation_id: profile.installationId,
36989
+ cloud_url: profile.cloudUrl,
36990
+ credential_type: profile.accessToken ? "oauth" : profile.apiKey ? "api_key" : "none",
36991
+ actor: profile.activeAgent,
36992
+ authority_changed: true,
36993
+ standing_consent_carried: false,
36994
+ note: "Live graph visibility and write target are resolved server-side for each request."
36995
+ };
36996
+ }
36997
+ },
36820
36998
  {
36821
36999
  name: "errata.search",
36822
37000
  description: "Find a symbol, string, or comment across the live code graph \u2014 reaches matches by structure, not just text. Multi-word queries are tokenized and OR'd (more terms matched ranks higher, then pageRank), so word order and punctuation don't matter. Returns up to `limit` hits with id, label, description, and pageRank (higher pageRank = more central \u2014 start there). A comment that documents a symbol resolves to that SYMBOL (the comment rides along as `via` \u2014 it's the why, not the destination); test scaffolding and floating comments sort below production code. When the collective is reachable, results BLEND cross-project knowledge, each tagged `provenance` (local | collective | corroborated) with `usageCount`.",
@@ -49644,8 +49822,8 @@ function tagEdgeCorroborated(targetAnchorIds, sessionTouchedNodeIds, independent
49644
49822
  for (const a of targetAnchorIds) if (sessionTouchedNodeIds.has(a)) return true;
49645
49823
  return false;
49646
49824
  }
49647
- function typePriorEdge(sourceLabel, targetLabel, sentence = "") {
49648
- const direct = LABEL_PAIR[`${sourceLabel}>${targetLabel}`];
49825
+ function typePriorEdge(sourceLabel, targetLabel2, sentence = "") {
49826
+ const direct = LABEL_PAIR[`${sourceLabel}>${targetLabel2}`];
49649
49827
  if (direct) return direct;
49650
49828
  if (sentence && !NEG.test(sentence)) {
49651
49829
  const t = TIEBREAK.find((c) => c.re.test(sentence));
@@ -51471,7 +51649,7 @@ function startLoopLagMonitor(thresholdMs = 1e3) {
51471
51649
  }
51472
51650
 
51473
51651
  // src/engine.ts
51474
- var DAEMON_VERSION = true ? "2.0.0-dev.96" : "2.0.0-alpha.0";
51652
+ var DAEMON_VERSION = true ? "2.0.0-dev.98" : "2.0.0-alpha.0";
51475
51653
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
51476
51654
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
51477
51655
  var GIT_OP_MUTE_MS = 4e3;
@@ -54629,6 +54807,102 @@ init_cloud_auth();
54629
54807
  import { createServer } from "node:http";
54630
54808
  var DEFAULT_OAUTH_SCOPE = "openid profile graph:read graph:write mcp:tools";
54631
54809
  var strip = (u) => u.replace(/\/+$/, "");
54810
+ function deviceEndpoints(tokenEndpoint) {
54811
+ const token = new URL(tokenEndpoint);
54812
+ const authRoot = token.pathname.replace(/\/(?:oauth2|mcp)\/token\/?$/, "");
54813
+ if (authRoot === token.pathname) throw new Error("authorization server exposes no device endpoint");
54814
+ const prefix = authRoot.replace(/\/api\/auth\/?$/, "");
54815
+ const at = (path2) => new URL(`${path2}`, `${token.origin}${prefix || "/"}`).toString();
54816
+ return {
54817
+ code: at(`${prefix}/api/auth/device/code`),
54818
+ token: at(`${prefix}/api/auth/device/token`),
54819
+ resource: at(`${prefix}/api/gate/session-token`)
54820
+ };
54821
+ }
54822
+ async function loginOAuthDevice(opts) {
54823
+ const fetchFn = opts.fetchFn ?? fetch;
54824
+ const endpoints = await discoverEndpoints(opts.cloudUrl, fetchFn);
54825
+ const device = deviceEndpoints(endpoints.tokenEndpoint);
54826
+ const clientId = oauthClientId();
54827
+ const scope = opts.scope ?? DEFAULT_OAUTH_SCOPE;
54828
+ const codeResponse = await fetchFn(device.code, {
54829
+ method: "POST",
54830
+ headers: { "content-type": "application/json", accept: "application/json" },
54831
+ body: JSON.stringify({ client_id: clientId, scope })
54832
+ });
54833
+ if (!codeResponse.ok) {
54834
+ throw new Error(`device authorization failed: HTTP ${codeResponse.status} ${(await codeResponse.text()).slice(0, 200)}`);
54835
+ }
54836
+ const code = await codeResponse.json();
54837
+ if (!code.device_code || !code.user_code || !code.verification_uri || !code.expires_in) {
54838
+ throw new Error("device authorization returned an incomplete response");
54839
+ }
54840
+ opts.onVerification?.({
54841
+ url: code.verification_uri_complete ?? code.verification_uri,
54842
+ userCode: code.user_code,
54843
+ expiresInSec: code.expires_in
54844
+ });
54845
+ const deadline = Date.now() + Math.min(code.expires_in * 1e3, opts.timeoutMs ?? Number.POSITIVE_INFINITY);
54846
+ let intervalMs = Math.max(2, code.interval ?? 5) * 1e3;
54847
+ let deviceSession = null;
54848
+ let approvedInstallation = null;
54849
+ while (Date.now() < deadline) {
54850
+ await (opts.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms))))(intervalMs);
54851
+ const poll = await fetchFn(device.token, {
54852
+ method: "POST",
54853
+ headers: { "content-type": "application/json", accept: "application/json" },
54854
+ body: JSON.stringify({
54855
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
54856
+ device_code: code.device_code,
54857
+ client_id: clientId
54858
+ })
54859
+ });
54860
+ const payload = await poll.json().catch(() => null);
54861
+ if (!poll.ok) {
54862
+ const error48 = typeof payload?.["error"] === "string" ? payload["error"] : "device_poll_failed";
54863
+ if (error48 === "authorization_pending") continue;
54864
+ if (error48 === "slow_down") {
54865
+ intervalMs += 5e3;
54866
+ continue;
54867
+ }
54868
+ throw new Error(`device authorization failed: ${error48}`);
54869
+ }
54870
+ deviceSession = typeof payload?.["access_token"] === "string" ? payload["access_token"] : null;
54871
+ approvedInstallation = typeof payload?.["installation_id"] === "string" ? payload["installation_id"] : null;
54872
+ break;
54873
+ }
54874
+ if (!deviceSession || !approvedInstallation) throw new Error("device authorization expired");
54875
+ if (approvedInstallation !== opts.installationId) {
54876
+ throw new Error("approved installation does not match the terminal request");
54877
+ }
54878
+ const resourceResponse = await fetchFn(device.resource, {
54879
+ method: "POST",
54880
+ headers: {
54881
+ authorization: `Bearer ${deviceSession}`,
54882
+ "content-type": "application/json",
54883
+ accept: "application/json"
54884
+ },
54885
+ body: JSON.stringify({
54886
+ installationId: approvedInstallation,
54887
+ clientId,
54888
+ scopes: scope.split(/\s+/).filter((value) => !["openid", "profile", "email", "offline_access"].includes(value))
54889
+ })
54890
+ });
54891
+ const resource = await resourceResponse.json().catch(() => null);
54892
+ if (!resourceResponse.ok || typeof resource?.["access_token"] !== "string") {
54893
+ throw new Error(`device resource exchange failed: HTTP ${resourceResponse.status}`);
54894
+ }
54895
+ return {
54896
+ installationId: approvedInstallation,
54897
+ clientId,
54898
+ tokenEndpoint: device.resource,
54899
+ tokens: {
54900
+ accessToken: resource["access_token"],
54901
+ refreshToken: deviceSession,
54902
+ ...typeof resource["expires_in"] === "number" ? { expiresInSec: resource["expires_in"] } : {}
54903
+ }
54904
+ };
54905
+ }
54632
54906
  async function discoverEndpoints(cloudUrl, fetchFn = fetch) {
54633
54907
  let consoleUrl = cloudUrl;
54634
54908
  try {
@@ -54752,6 +55026,193 @@ async function loginOAuthLoopback(opts) {
54752
55026
  return { tokens, tokenEndpoint: endpoints.tokenEndpoint, clientId };
54753
55027
  }
54754
55028
 
55029
+ // src/device-login.ts
55030
+ init_src6();
55031
+ var DEFAULT_SCOPE = "openid profile graph:read graph:write mcp:tools";
55032
+ var BRIDGE_REDIRECT_URI = "http://127.0.0.1:1/device-bridge-callback";
55033
+ var strip2 = (u) => u.replace(/\/+$/, "");
55034
+ async function discoverDeviceEndpoint(cloudUrl, fetchFn = fetch) {
55035
+ try {
55036
+ const res = await fetchFn(`${strip2(cloudUrl)}/.well-known/oauth-authorization-server`);
55037
+ if (res.ok) {
55038
+ const j = await res.json();
55039
+ if (j.device_authorization_endpoint) return j.device_authorization_endpoint;
55040
+ }
55041
+ } catch {
55042
+ }
55043
+ return `${strip2(cloudUrl)}/device`;
55044
+ }
55045
+ async function requestDeviceAuthorization(fetchFn, deviceEndpoint, p) {
55046
+ const res = await fetchFn(deviceEndpoint, {
55047
+ method: "POST",
55048
+ headers: { "content-type": "application/json", accept: "application/json" },
55049
+ body: JSON.stringify({ client_id: p.clientId, scope: p.scope })
55050
+ });
55051
+ if (!res.ok) throw new Error(`device authorization failed: HTTP ${res.status}`);
55052
+ const j = await res.json();
55053
+ if (!j.device_code || !j.user_code || !j.verification_uri) {
55054
+ throw new Error("device authorization response missing device_code/user_code/verification_uri");
55055
+ }
55056
+ return {
55057
+ deviceCode: j.device_code,
55058
+ userCode: j.user_code,
55059
+ verificationUri: j.verification_uri,
55060
+ ...j.verification_uri_complete ? { verificationUriComplete: j.verification_uri_complete } : {},
55061
+ expiresInSeconds: j.expires_in ?? 1800,
55062
+ intervalSeconds: j.interval ?? 5
55063
+ };
55064
+ }
55065
+ async function pollDeviceApproval(fetchFn, deviceEndpoint, p, sleep = (ms) => new Promise((r) => setTimeout(r, ms))) {
55066
+ const deadline = Date.now() + p.timeoutMs;
55067
+ let intervalMs = Math.max(1, p.intervalSeconds) * 1e3;
55068
+ for (; ; ) {
55069
+ if (Date.now() >= deadline) throw new Error("device approval timed out \u2014 run `errata login` again");
55070
+ await sleep(intervalMs);
55071
+ const res = await fetchFn(`${strip2(deviceEndpoint)}/token`, {
55072
+ method: "POST",
55073
+ headers: { "content-type": "application/json", accept: "application/json" },
55074
+ body: JSON.stringify({
55075
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
55076
+ device_code: p.deviceCode,
55077
+ client_id: p.clientId
55078
+ })
55079
+ });
55080
+ const j = await res.json().catch(() => ({}));
55081
+ if (res.ok && j.access_token) return j.access_token;
55082
+ if (j.error === "authorization_pending") continue;
55083
+ if (j.error === "slow_down") {
55084
+ intervalMs += 5e3;
55085
+ continue;
55086
+ }
55087
+ throw new Error(`device approval failed: ${j.error ?? `HTTP ${res.status}`}`);
55088
+ }
55089
+ }
55090
+ async function authorizeWithSession(fetchFn, p) {
55091
+ const u = new URL(p.authorizationEndpoint);
55092
+ u.searchParams.set("response_type", "code");
55093
+ u.searchParams.set("client_id", p.clientId);
55094
+ u.searchParams.set("redirect_uri", BRIDGE_REDIRECT_URI);
55095
+ u.searchParams.set("scope", p.scope);
55096
+ u.searchParams.set("state", p.state);
55097
+ u.searchParams.set("code_challenge", p.challenge);
55098
+ u.searchParams.set("code_challenge_method", p.method);
55099
+ const res = await fetchFn(u.toString(), {
55100
+ method: "GET",
55101
+ headers: { authorization: `Bearer ${p.sessionToken}`, accept: "application/json" },
55102
+ redirect: "manual"
55103
+ });
55104
+ const location = res.headers.get("location");
55105
+ if (!location) throw new Error(`authorize did not redirect (HTTP ${res.status}) \u2014 is the session valid?`);
55106
+ const codeFromLocation = extractCode(location, p.state);
55107
+ if (codeFromLocation) return { code: codeFromLocation };
55108
+ const consentUrl = new URL(location);
55109
+ const consentCode = consentUrl.searchParams.get("consent_code");
55110
+ if (!consentCode) throw new Error(`authorize redirected without code or consent_code: ${consentUrl.pathname}`);
55111
+ const pagePath = "/oauth/consent";
55112
+ const prefix = consentUrl.pathname.endsWith(pagePath) ? consentUrl.pathname.slice(0, -pagePath.length) : "";
55113
+ const consentApi = `${consentUrl.origin}${prefix}/api/auth/oauth2/consent`;
55114
+ const consentRes = await fetchFn(consentApi, {
55115
+ method: "POST",
55116
+ headers: {
55117
+ "content-type": "application/json",
55118
+ accept: "application/json",
55119
+ authorization: `Bearer ${p.sessionToken}`
55120
+ },
55121
+ body: JSON.stringify({ accept: true, consent_code: consentCode })
55122
+ });
55123
+ if (!consentRes.ok) throw new Error(`consent approval failed: HTTP ${consentRes.status}`);
55124
+ const consentBody = await consentRes.json();
55125
+ const code = consentBody.redirectURI ? extractCode(consentBody.redirectURI, p.state) : null;
55126
+ if (!code) throw new Error("consent approved but no authorization code returned");
55127
+ return { code };
55128
+ }
55129
+ function extractCode(redirectUrl, expectedState) {
55130
+ try {
55131
+ const u = new URL(redirectUrl);
55132
+ const code = u.searchParams.get("code");
55133
+ const state = u.searchParams.get("state");
55134
+ if (!code) return null;
55135
+ if (state !== expectedState) throw new Error("state mismatch in authorization redirect");
55136
+ return code;
55137
+ } catch (err2) {
55138
+ if (err2 instanceof Error && err2.message.includes("state mismatch")) throw err2;
55139
+ return null;
55140
+ }
55141
+ }
55142
+ async function loginViaDeviceBridge(opts) {
55143
+ const fetchFn = opts.fetchFn ?? fetch;
55144
+ const log = opts.log ?? console.log;
55145
+ const scope = opts.scope ?? DEFAULT_SCOPE;
55146
+ const timeoutMs = opts.timeoutMs ?? 15 * 6e4;
55147
+ const endpoints = await discoverEndpoints(opts.cloudUrl, fetchFn);
55148
+ const deviceEndpoint = await discoverDeviceEndpoint(opts.cloudUrl, fetchFn);
55149
+ if (!endpoints.registrationEndpoint) {
55150
+ throw new Error("cloud does not advertise dynamic client registration \u2014 try `errata login --browser`");
55151
+ }
55152
+ const { clientId } = await registerOAuthClient(fetchFn, endpoints.registrationEndpoint, {
55153
+ redirectUri: BRIDGE_REDIRECT_URI,
55154
+ scope,
55155
+ clientName: "errata daemon"
55156
+ });
55157
+ const grant = await requestDeviceAuthorization(fetchFn, deviceEndpoint, { clientId, scope });
55158
+ log(`approve this login:`);
55159
+ log(` visit ${grant.verificationUri}`);
55160
+ log(` code ${grant.userCode}`);
55161
+ if (grant.verificationUriComplete && opts.open) opts.open(grant.verificationUriComplete);
55162
+ const sessionToken = await pollDeviceApproval(fetchFn, deviceEndpoint, {
55163
+ clientId,
55164
+ deviceCode: grant.deviceCode,
55165
+ intervalSeconds: grant.intervalSeconds,
55166
+ timeoutMs
55167
+ });
55168
+ const pkce = generatePkce();
55169
+ const state = randomState();
55170
+ const { code } = await authorizeWithSession(fetchFn, {
55171
+ authorizationEndpoint: endpoints.authorizationEndpoint,
55172
+ sessionToken,
55173
+ clientId,
55174
+ scope,
55175
+ state,
55176
+ challenge: pkce.challenge,
55177
+ method: pkce.method
55178
+ });
55179
+ const tokens = await exchangeAuthorizationCode(fetchFn, endpoints.tokenEndpoint, {
55180
+ code,
55181
+ codeVerifier: pkce.verifier,
55182
+ redirectUri: BRIDGE_REDIRECT_URI,
55183
+ clientId
55184
+ });
55185
+ try {
55186
+ const consoleAuthBase = deriveAuthBase(grant.verificationUri);
55187
+ if (consoleAuthBase) {
55188
+ await fetchFn(`${consoleAuthBase}/sign-out`, {
55189
+ method: "POST",
55190
+ headers: { authorization: `Bearer ${sessionToken}`, "content-type": "application/json" },
55191
+ body: "{}"
55192
+ });
55193
+ }
55194
+ } catch {
55195
+ }
55196
+ return { tokens, tokenEndpoint: endpoints.tokenEndpoint, clientId };
55197
+ }
55198
+ function deriveAuthBase(verificationUri) {
55199
+ try {
55200
+ const origin = new URL(verificationUri).origin;
55201
+ return `${origin}/newapp/api/auth`;
55202
+ } catch {
55203
+ return null;
55204
+ }
55205
+ }
55206
+ function openInBrowser(url2) {
55207
+ try {
55208
+ void import("node:child_process").then(({ spawn: spawn4 }) => {
55209
+ const [cmd2, args2] = process.platform === "win32" ? ["cmd", ["/c", "start", "", url2]] : process.platform === "darwin" ? ["open", [url2]] : ["xdg-open", [url2]];
55210
+ spawn4(cmd2, args2, { detached: true, stdio: "ignore" }).unref();
55211
+ });
55212
+ } catch {
55213
+ }
55214
+ }
55215
+
54755
55216
  // src/cli.ts
54756
55217
  init_paths();
54757
55218
 
@@ -54811,6 +55272,116 @@ function shouldUseOAuthLogin(flags2, env2 = process.env) {
54811
55272
  return Boolean(flags2.oauth || !flags2.device && isDaemonOAuthDefaultEnabled(env2));
54812
55273
  }
54813
55274
 
55275
+ // src/doctor.ts
55276
+ async function diagnoseInstallation(cfg, dependencies) {
55277
+ const checks = [];
55278
+ const profileName = cfg.activeInstallationProfile;
55279
+ const profile = profileName ? cfg.installationProfiles[profileName] : void 0;
55280
+ checks.push(
55281
+ profile ? { id: "installation_profile", status: "pass", detail: `${profileName} is selected` } : {
55282
+ id: "installation_profile",
55283
+ status: "fail",
55284
+ detail: "No installation profile is selected"
55285
+ }
55286
+ );
55287
+ const credentialType = cfg.accessToken ? "oauth" : cfg.apiKey ? "api_key" : "none";
55288
+ checks.push(
55289
+ credentialType === "none" ? { id: "credential", status: "fail", detail: "No credential is installed" } : { id: "credential", status: "pass", detail: `${credentialType} credential is present` }
55290
+ );
55291
+ const authorityMatches = !profile || profile.cloudUrl === cfg.cloudUrl && profile.apiKey === cfg.apiKey && profile.accessToken === cfg.accessToken && profile.refreshToken === cfg.refreshToken;
55292
+ checks.push(
55293
+ authorityMatches ? {
55294
+ id: "profile_authority",
55295
+ status: profile ? "pass" : "warn",
55296
+ detail: profile ? "Selected profile and active credential set match" : "Cannot compare authority without a selected profile"
55297
+ } : {
55298
+ id: "profile_authority",
55299
+ status: "fail",
55300
+ detail: "Selected profile and active credential set differ; reselect the profile"
55301
+ }
55302
+ );
55303
+ if (credentialType === "oauth") {
55304
+ checks.push(
55305
+ cfg.refreshToken && cfg.tokenEndpoint ? { id: "renewal", status: "pass", detail: "OAuth renewal is configured" } : {
55306
+ id: "renewal",
55307
+ status: "fail",
55308
+ detail: "OAuth access exists without a refresh credential or token endpoint"
55309
+ }
55310
+ );
55311
+ }
55312
+ try {
55313
+ const inspection = await dependencies.inspect();
55314
+ checks.push(
55315
+ inspection.reachable ? {
55316
+ id: "endpoint",
55317
+ status: "pass",
55318
+ detail: `Cloud is reachable via ${inspection.probe}`
55319
+ } : {
55320
+ id: "endpoint",
55321
+ status: "fail",
55322
+ detail: inspection.error ?? "Cloud endpoint is unreachable"
55323
+ }
55324
+ );
55325
+ } catch (cause) {
55326
+ checks.push({
55327
+ id: "endpoint",
55328
+ status: "fail",
55329
+ detail: cause instanceof Error ? cause.message : String(cause)
55330
+ });
55331
+ }
55332
+ if (credentialType !== "none") {
55333
+ try {
55334
+ const identity = await dependencies.authenticate();
55335
+ checks.push({
55336
+ id: "authentication",
55337
+ status: "pass",
55338
+ detail: `Authenticated as ${identity.handle} (${identity.tier})`
55339
+ });
55340
+ } catch (cause) {
55341
+ checks.push({
55342
+ id: "authentication",
55343
+ status: "fail",
55344
+ detail: cause instanceof Error ? cause.message : String(cause)
55345
+ });
55346
+ }
55347
+ }
55348
+ return {
55349
+ ok: checks.every((check2) => check2.status !== "fail"),
55350
+ profile: profileName,
55351
+ installationId: profile?.installationId ?? null,
55352
+ credentialType,
55353
+ checks
55354
+ };
55355
+ }
55356
+
55357
+ // src/whoami.ts
55358
+ async function resolveWhoami(client, actingHandle) {
55359
+ return actingHandle ? client.contextAs(actingHandle) : client.context();
55360
+ }
55361
+ function targetLabel(context) {
55362
+ const target = context.default_write_target;
55363
+ if (!target) return "read-only";
55364
+ if (target.visibility === "public") return "public graph";
55365
+ if (target.visibility === "org") return `organization graph (${target.org_id})`;
55366
+ return `private Team graph (${target.team_id})`;
55367
+ }
55368
+ function formatWhoami(response, localProfile) {
55369
+ const { context, identity } = response;
55370
+ return [
55371
+ `inErrata context \u2014 ${context.profile_name ?? localProfile ?? "unbound credential"}`,
55372
+ ` workspace: ${context.workspace_kind} \xB7 ${context.org_id}`,
55373
+ ` team: ${context.team_id ?? "(none)"}`,
55374
+ ` agent: ${context.agent_id ?? "(default identity)"}`,
55375
+ ` installation: ${context.installation_id ?? "(unbound)"}`,
55376
+ ` environment: ${context.environment} \xB7 ${context.client_kind}`,
55377
+ ` reads: ${context.read_visibilities.join(" + ")}`,
55378
+ ` contributes: ${targetLabel(context)}`,
55379
+ ` policy: ${context.policy_version ?? "(legacy)"}`,
55380
+ ` credential: ${identity.credential_type} \xB7 ${identity.credential_id}`,
55381
+ ` actor: ${identity.actor_type} \xB7 ${identity.actor_id}`
55382
+ ];
55383
+ }
55384
+
54814
55385
  // src/consolidation-trigger.ts
54815
55386
  var DEFAULT_CONSOLIDATION_POLICY = {
54816
55387
  baseFloorMs: 6e4,
@@ -54868,8 +55439,13 @@ async function main() {
54868
55439
  return cmdStop();
54869
55440
  case "status":
54870
55441
  return cmdStatus();
55442
+ case "doctor":
55443
+ case "verify":
55444
+ return cmdDoctor(rest);
54871
55445
  case "usage":
54872
55446
  return cmdUsage();
55447
+ case "whoami":
55448
+ return cmdWhoami(rest);
54873
55449
  case "login":
54874
55450
  return cmdLogin();
54875
55451
  case "logout":
@@ -54880,6 +55456,8 @@ async function main() {
54880
55456
  return cmdUnlink();
54881
55457
  case "use":
54882
55458
  return cmdUse(rest);
55459
+ case "profile":
55460
+ return cmdInstallationProfile(rest);
54883
55461
  case "review":
54884
55462
  return cmdReview();
54885
55463
  case "tick":
@@ -54958,6 +55536,8 @@ Commands:
54958
55536
  status Print workspace + cloud status (incl. version + pending update)
54959
55537
  usage Show current cloud plan, units remaining, estimated cost,
54960
55538
  and per-tool usage (reads the gateway's batched Hono ledger)
55539
+ whoami [--json] Show the Console-resolved Personal/org/Team, agent,
55540
+ read visibility, and exact default contribution target.
54961
55541
  update [--channel dev|latest] [--check]
54962
55542
  Pull the newest build on this machine's channel via npm.
54963
55543
  --check only reports; --channel switches + persists channel.
@@ -55004,8 +55584,14 @@ Commands:
55004
55584
  Flags: --port N (default 7891)
55005
55585
  mcp Run the MCP stdio server \u2014 the agent's full errata tool
55006
55586
  surface (navigation, problems, claims, burst, health)
55007
- login Sign in with the cloud (OAuth by default; --device for legacy device-code)
55587
+ login Sign in with the cloud. Default: short verification URL +
55588
+ typeable code (device-bridged OAuth). Flags: --browser
55589
+ (loopback code flow) \xB7 --token <key> \xB7
55590
+ --device --installation <uuid> [--profile <name>]
55591
+ (headless, installation-bound console device authorization)
55008
55592
  logout Clear local cloud credentials
55593
+ doctor [--json] Verify profile, credential authority, renewal, endpoint,
55594
+ and authentication without printing secrets (alias: verify)
55009
55595
  link Corrective project link (ambient linking covers the happy path).
55010
55596
  Flags: --project <id> adopt an existing project (fork\u2192upstream);
55011
55597
  --remote <name> derive the locator from a non-origin remote;
@@ -55014,6 +55600,12 @@ Commands:
55014
55600
  use [<handle>] Set the sticky session active agent the daemon acts as.
55015
55601
  No arg lists the agents you can act as + the current one;
55016
55602
  --none (or --clear) reverts to your default identity.
55603
+ profile List named installation profiles and the active one.
55604
+ profile save <name> --installation <uuid>
55605
+ Bind the current credential to a named Console installation.
55606
+ profile use <name> Switch credential + agent context to an installed profile.
55607
+ profile remove <name>
55608
+ Remove a local profile (does not revoke it in the Console).
55017
55609
  sync now Flush outbox to the cloud once
55018
55610
  privacy Show what is collected/scrubbed + your consent state
55019
55611
  consent <channel> <on|off>
@@ -55026,7 +55618,8 @@ Commands:
55026
55618
 
55027
55619
  Environment:
55028
55620
  ERRATA_CLOUD_URL Cloud base URL (default ${DEFAULT_CLOUD_URL})
55029
- ERRATA_ALLOW_DIRECT_V1_CLOUD=1 Allow non-local legacy v1 cloud testing
55621
+ ERRATA_ALLOW_DIRECT_V1_CLOUD=1 Staff-only: request direct legacy v1 (bypasses the gateway;
55622
+ also requires ERRATA_DIRECT_V1_ACK=I_ACCEPT_UNMETERED_DIRECT_V1)
55030
55623
  ${OAUTH_DEFAULT_ENV}=0 Test/dev escape hatch: plain \`errata login\` uses legacy device-code
55031
55624
  `);
55032
55625
  }
@@ -55048,9 +55641,7 @@ function resolveHookPort(explicit) {
55048
55641
  async function cmdInit() {
55049
55642
  const skipHooks = rest.includes("--skip-hooks");
55050
55643
  const portIdx = rest.indexOf("--port");
55051
- const port = resolveHookPort(
55052
- portIdx >= 0 && rest[portIdx + 1] ? Number(rest[portIdx + 1]) : void 0
55053
- );
55644
+ const port = resolveHookPort(portIdx >= 0 && rest[portIdx + 1] ? Number(rest[portIdx + 1]) : void 0);
55054
55645
  const existing = loadProfile(ROOT);
55055
55646
  if (existing) {
55056
55647
  console.log(`already initialized: ${existing.id} (${existing.name})`);
@@ -55167,9 +55758,7 @@ async function cmdStart() {
55167
55758
  if (claimed) {
55168
55759
  const adopted = autodetectProfile(ROOT);
55169
55760
  saveProfile(ROOT, adopted);
55170
- console.log(
55171
- `adopted: ${locator} is claimed by your org (project ${claimed.id}) \u2014 workspace initialized`
55172
- );
55761
+ console.log(`adopted: ${locator} is claimed by your org (project ${claimed.id}) \u2014 workspace initialized`);
55173
55762
  }
55174
55763
  } catch {
55175
55764
  }
@@ -55234,7 +55823,9 @@ async function cmdStatus() {
55234
55823
  console.log(` workspace: ${profile ? `${profile.name} (${profile.id})` : "(not initialized)"}`);
55235
55824
  if (profile) {
55236
55825
  const link = profile.projectId ? ` \u2192 project ${profile.projectId.slice(0, 8)}` : " (unlinked)";
55237
- console.log(` repo: ${profile.repoLocator ? `${profile.repoLocator}${link}` : "(no git remote \u2014 unlinked)"}`);
55826
+ console.log(
55827
+ ` repo: ${profile.repoLocator ? `${profile.repoLocator}${link}` : "(no git remote \u2014 unlinked)"}`
55828
+ );
55238
55829
  console.log(` stack: ${profile.stack.join(", ") || "(none)"}`);
55239
55830
  console.log(` languages: ${profile.languages.join(", ") || "(none)"}`);
55240
55831
  }
@@ -55256,12 +55847,13 @@ async function cmdStatus() {
55256
55847
  );
55257
55848
  console.log(` cloud:`);
55258
55849
  console.log(` url: ${cfg.cloudUrl}`);
55850
+ console.log(` profile: ${cfg.activeInstallationProfile ?? "(unbound legacy credential)"}`);
55259
55851
  if (!hasCloudCredential(cfg)) {
55260
55852
  console.log(` logged in: no`);
55261
55853
  } else if (cfg.accessToken) {
55262
55854
  console.log(` logged in: yes (${cfg.email}) \u2014 attested daemon, can contribute`);
55263
55855
  } else {
55264
- console.log(` logged in: yes (${cfg.email}) \u2014 READ-ONLY key; run \`errata login\` to attest for graph-write`);
55856
+ console.log(` logged in: yes (${cfg.email}) \u2014 key capability enforced by Console installation/scopes`);
55265
55857
  }
55266
55858
  const active = getActiveAgent(cfg);
55267
55859
  console.log(
@@ -55301,6 +55893,53 @@ async function cmdStatus() {
55301
55893
  \u26A1 update available: ${upd.current} \u2192 ${upd.latest} \u2014 run: errata update`);
55302
55894
  }
55303
55895
  }
55896
+ async function cmdWhoami(args2) {
55897
+ const cfg = loadConfig();
55898
+ if (!hasCloudCredential(cfg)) {
55899
+ console.error("not logged in \u2014 run `errata login` or select an installation profile");
55900
+ process.exitCode = 1;
55901
+ return;
55902
+ }
55903
+ try {
55904
+ const active = getActiveAgent(cfg);
55905
+ const response = await resolveWhoami(authedCloudClient(cfg), active?.handle ?? null);
55906
+ if (args2.includes("--json")) {
55907
+ console.log(JSON.stringify(response, null, 2));
55908
+ return;
55909
+ }
55910
+ for (const line of formatWhoami(response, cfg.activeInstallationProfile)) console.log(line);
55911
+ } catch (err2) {
55912
+ console.error(`could not resolve context: ${err2 instanceof Error ? err2.message : err2}`);
55913
+ process.exitCode = 1;
55914
+ }
55915
+ }
55916
+ async function cmdDoctor(args2) {
55917
+ const cfg = loadConfig();
55918
+ const report = await diagnoseInstallation(cfg, {
55919
+ inspect: () => inspectCloudEndpoint(cfg.cloudUrl),
55920
+ authenticate: async () => {
55921
+ const me = await authedCloudClient(cfg).me();
55922
+ return { handle: me.handle, tier: me.tier };
55923
+ }
55924
+ });
55925
+ if (args2.includes("--json")) {
55926
+ console.log(JSON.stringify(report, null, 2));
55927
+ } else {
55928
+ console.log(`inErrata doctor \u2014 ${report.ok ? "ready" : "needs attention"}`);
55929
+ console.log(` profile: ${report.profile ?? "(none)"}`);
55930
+ console.log(` installation: ${report.installationId ?? "(none)"}`);
55931
+ for (const check2 of report.checks) {
55932
+ const marker = check2.status === "pass" ? "\u2713" : check2.status === "warn" ? "!" : "\u2717";
55933
+ console.log(` ${marker} ${check2.id}: ${check2.detail}`);
55934
+ }
55935
+ if (!report.ok) {
55936
+ console.log(
55937
+ " recovery: `errata profile list`, then `errata profile use <name>`; re-login if authentication still fails."
55938
+ );
55939
+ }
55940
+ }
55941
+ if (!report.ok) process.exitCode = 1;
55942
+ }
55304
55943
  async function cmdUpdate(args2) {
55305
55944
  const cfg = loadConfig();
55306
55945
  let channel = cfg.updateChannel;
@@ -55360,6 +55999,11 @@ function parseFlags(args2) {
55360
55999
  else if (a.startsWith("--token=")) out2.token = a.slice("--token=".length);
55361
56000
  else if (a === "--oauth") out2.oauth = true;
55362
56001
  else if (a === "--device") out2.device = true;
56002
+ else if (a === "--browser") out2.browser = true;
56003
+ else if (a === "--installation") out2.installation = args2[++i2];
56004
+ else if (a.startsWith("--installation=")) out2.installation = a.slice("--installation=".length);
56005
+ else if (a === "--profile") out2.profile = args2[++i2];
56006
+ else if (a.startsWith("--profile=")) out2.profile = a.slice("--profile=".length);
55363
56007
  else if (!a.startsWith("--")) out2._.push(a);
55364
56008
  }
55365
56009
  return out2;
@@ -55390,14 +56034,20 @@ async function cmdUsage() {
55390
56034
  console.log(` held: ${status.quota.units_reserved.toLocaleString()} units reserved by in-flight calls`);
55391
56035
  }
55392
56036
  console.log(` cost: $${(status.quota.estimated_cents_current / 100).toFixed(2)} estimated`);
55393
- console.log(` rate: $${(status.pricing.cents_per_1000_units / 100).toFixed(2)} / 1,000 units${status.pricing.estimate_only ? " (estimate, not an invoice)" : ""}`);
56037
+ console.log(
56038
+ ` rate: $${(status.pricing.cents_per_1000_units / 100).toFixed(2)} / 1,000 units${status.pricing.estimate_only ? " (estimate, not an invoice)" : ""}`
56039
+ );
55394
56040
  if (status.by_tool.length > 0) {
55395
56041
  console.log(" tools:");
55396
56042
  for (const row of [...status.by_tool].sort((a, b) => b.units - a.units).slice(0, 10)) {
55397
- console.log(` ${row.tool.padEnd(24)} ${row.units.toLocaleString().padStart(8)} units ${row.event_count.toLocaleString().padStart(6)} calls`);
56043
+ console.log(
56044
+ ` ${row.tool.padEnd(24)} ${row.units.toLocaleString().padStart(8)} units ${row.event_count.toLocaleString().padStart(6)} calls`
56045
+ );
55398
56046
  }
55399
56047
  }
55400
- console.log(` as of: ${status.as_of} (ledger + live reservations; poll again after ${Math.ceil(status.poll_after_ms / 1e3)}s)`);
56048
+ console.log(
56049
+ ` as of: ${status.as_of} (ledger + live reservations; poll again after ${Math.ceil(status.poll_after_ms / 1e3)}s)`
56050
+ );
55401
56051
  } catch (err2) {
55402
56052
  console.error(`usage unavailable: ${err2 instanceof Error ? err2.message : err2}`);
55403
56053
  process.exitCode = 1;
@@ -55441,7 +56091,7 @@ async function warnIfApprovalUnreachable(url2) {
55441
56091
  } catch {
55442
56092
  }
55443
56093
  }
55444
- async function cmdLoginOAuth(cfg) {
56094
+ async function cmdLoginOAuth(cfg, useBrowserLoopback = false) {
55445
56095
  const inspection = await inspectCloudEndpoint(cfg.cloudUrl);
55446
56096
  try {
55447
56097
  assertCloudEndpointAllowed(cfg.cloudUrl, inspection);
@@ -55453,10 +56103,11 @@ async function cmdLoginOAuth(cfg) {
55453
56103
  console.log(`signing in via OAuth at ${cfg.cloudUrl} \u2026`);
55454
56104
  let result;
55455
56105
  try {
55456
- result = await loginOAuthLoopback({ cloudUrl: cfg.cloudUrl, timeoutMs: 5 * 6e4 });
56106
+ result = useBrowserLoopback ? await loginOAuthLoopback({ cloudUrl: cfg.cloudUrl, timeoutMs: 5 * 6e4 }) : await loginViaDeviceBridge({ cloudUrl: cfg.cloudUrl, timeoutMs: 15 * 6e4, open: openInBrowser });
55457
56107
  } catch (err2) {
55458
56108
  console.error(`oauth login failed: ${err2 instanceof Error ? err2.message : err2}`);
55459
- console.error(` fall back to device code: errata login --device`);
56109
+ if (!useBrowserLoopback) console.error(` try the browser flow: errata login --browser`);
56110
+ console.error(` use headless device login: errata login --device --installation <uuid>`);
55460
56111
  console.error(` or paste a key: errata login --token <key>`);
55461
56112
  process.exitCode = 1;
55462
56113
  return;
@@ -55482,6 +56133,68 @@ async function cmdLoginOAuth(cfg) {
55482
56133
  process.exitCode = 1;
55483
56134
  }
55484
56135
  }
56136
+ async function cmdLoginOAuthDevice(cfg, input) {
56137
+ const profileName = input.profileName?.trim() || `device-${input.installationId.slice(0, 8)}`;
56138
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(input.installationId)) {
56139
+ console.error("--installation must be a UUID from Console \u2192 Connect");
56140
+ process.exitCode = 1;
56141
+ return;
56142
+ }
56143
+ const inspection = await inspectCloudEndpoint(cfg.cloudUrl);
56144
+ try {
56145
+ assertCloudEndpointAllowed(cfg.cloudUrl, inspection);
56146
+ } catch (err2) {
56147
+ console.error(err2 instanceof Error ? err2.message : cloudPolicyMessage(cfg, inspection));
56148
+ process.exitCode = 1;
56149
+ return;
56150
+ }
56151
+ try {
56152
+ const result = await loginOAuthDevice({
56153
+ cloudUrl: cfg.cloudUrl,
56154
+ installationId: input.installationId,
56155
+ timeoutMs: 15 * 6e4,
56156
+ onVerification: ({ url: url2, userCode, expiresInSec }) => {
56157
+ console.log(`approve this device in the Console:
56158
+ ${url2}`);
56159
+ console.log(`code: ${userCode} \xB7 expires in ${Math.round(expiresInSec / 60)} minutes`);
56160
+ }
56161
+ });
56162
+ const nextCfg = {
56163
+ ...cfg,
56164
+ apiKey: null,
56165
+ accessToken: result.tokens.accessToken,
56166
+ refreshToken: result.tokens.refreshToken ?? null,
56167
+ tokenEndpoint: result.tokenEndpoint,
56168
+ oauthClientId: result.clientId,
56169
+ activeInstallationProfile: profileName,
56170
+ installationProfiles: {
56171
+ ...cfg.installationProfiles,
56172
+ [profileName]: {
56173
+ installationId: result.installationId,
56174
+ cloudUrl: cfg.cloudUrl,
56175
+ apiKey: null,
56176
+ accessToken: result.tokens.accessToken,
56177
+ refreshToken: result.tokens.refreshToken ?? null,
56178
+ tokenEndpoint: result.tokenEndpoint,
56179
+ oauthClientId: result.clientId,
56180
+ activeAgent: cfg.activeAgent
56181
+ }
56182
+ }
56183
+ };
56184
+ const me = await authedCloudClient(nextCfg).me();
56185
+ nextCfg.userId = me.agentId;
56186
+ nextCfg.email = me.handle;
56187
+ saveConfig(nextCfg);
56188
+ console.log(
56189
+ `logged in as ${me.handle} (${me.tier}) \xB7 profile ${profileName} \xB7 installation ${result.installationId}`
56190
+ );
56191
+ await linkRegisteredWorkspaces(nextCfg);
56192
+ } catch (err2) {
56193
+ console.error(`device login failed: ${err2 instanceof Error ? err2.message : err2}`);
56194
+ console.error(" no credential was stored; retry from Console \u2192 Connect");
56195
+ process.exitCode = 1;
56196
+ }
56197
+ }
55485
56198
  async function linkRegisteredWorkspaces(cfg) {
55486
56199
  if (!cfg.consent.sync) return;
55487
56200
  const client = authedCloudClient(cfg);
@@ -55492,9 +56205,7 @@ async function linkRegisteredWorkspaces(cfg) {
55492
56205
  refreshRepoLocator(w.path, profile);
55493
56206
  const out2 = await ensureProjectLink(w.path, profile, client);
55494
56207
  if (out2.linked) {
55495
- console.log(
55496
- ` linked ${profile.name} \u2192 project ${out2.projectId}${out2.created ? " (created)" : ""}`
55497
- );
56208
+ console.log(` linked ${profile.name} \u2192 project ${out2.projectId}${out2.created ? " (created)" : ""}`);
55498
56209
  }
55499
56210
  } catch {
55500
56211
  }
@@ -55576,7 +56287,9 @@ async function cmdUnlink() {
55576
56287
  delete profile.projectLocator;
55577
56288
  profile.projectLinkDisabled = true;
55578
56289
  saveProfile(ROOT, profile);
55579
- console.log(had ? `unlinked from project ${had} (ambient linking disabled \u2014 re-enable with \`errata link\`)` : "already unlinked (ambient linking disabled)");
56290
+ console.log(
56291
+ had ? `unlinked from project ${had} (ambient linking disabled \u2014 re-enable with \`errata link\`)` : "already unlinked (ambient linking disabled)"
56292
+ );
55580
56293
  }
55581
56294
  async function cmdLogin() {
55582
56295
  const cfg = loadConfig();
@@ -55586,8 +56299,20 @@ async function cmdLogin() {
55586
56299
  await applyToken(cfg, flags2.token);
55587
56300
  return;
55588
56301
  }
56302
+ if (flags2.device) {
56303
+ if (!flags2.installation) {
56304
+ console.error("device login requires --installation <uuid> from Console \u2192 Connect");
56305
+ process.exitCode = 1;
56306
+ return;
56307
+ }
56308
+ await cmdLoginOAuthDevice(cfg, {
56309
+ installationId: flags2.installation,
56310
+ ...flags2.profile ? { profileName: flags2.profile } : {}
56311
+ });
56312
+ return;
56313
+ }
55589
56314
  if (shouldUseOAuthLogin(flags2)) {
55590
- await cmdLoginOAuth(cfg);
56315
+ await cmdLoginOAuth(cfg, flags2.browser ?? false);
55591
56316
  return;
55592
56317
  }
55593
56318
  const c = new CloudClient({ baseUrl: cfg.cloudUrl });
@@ -55620,7 +56345,9 @@ async function cmdLogin() {
55620
56345
  await warnIfApprovalUnreachable(dc.verificationUrl);
55621
56346
  console.log(`approve this device in your browser:
55622
56347
  ${dc.verificationUrl}`);
55623
- console.log(`(code expires in ${Math.round(dc.expiresIn / 60)} minutes \u2014 or paste a key: errata login --token <key>)`);
56348
+ console.log(
56349
+ `(code expires in ${Math.round(dc.expiresIn / 60)} minutes \u2014 or paste a key: errata login --token <key>)`
56350
+ );
55624
56351
  const deadline = Date.now() + dc.expiresIn * 1e3;
55625
56352
  const intervalMs = Math.max(2, dc.interval) * 1e3;
55626
56353
  for (; ; ) {
@@ -55689,6 +56416,48 @@ async function cmdUse(args2) {
55689
56416
  const code = await runUse(action, ctx);
55690
56417
  if (code !== 0) process.exitCode = code;
55691
56418
  }
56419
+ function cmdInstallationProfile(args2) {
56420
+ const cfg = loadConfig();
56421
+ const action = args2[0] ?? "list";
56422
+ if (action === "list") {
56423
+ const entries = Object.entries(cfg.installationProfiles).sort(([a], [b]) => a.localeCompare(b));
56424
+ if (entries.length === 0) {
56425
+ console.log("(no installation profiles \u2014 create one in the Console, then run profile save)");
56426
+ return;
56427
+ }
56428
+ for (const [name3, profile] of entries) {
56429
+ const marker = cfg.activeInstallationProfile === name3 ? "*" : " ";
56430
+ console.log(`${marker} ${name3} \xB7 ${profile.installationId} \xB7 ${profile.cloudUrl}`);
56431
+ }
56432
+ return;
56433
+ }
56434
+ const name2 = args2[1]?.trim();
56435
+ if (!name2) throw new Error(`usage: errata profile ${action} <name>`);
56436
+ if (action === "use") {
56437
+ const selected = useInstallationProfile(name2, cfg);
56438
+ const profile = selected.installationProfiles[name2];
56439
+ console.log(`profile active: ${name2} \xB7 installation ${profile.installationId}`);
56440
+ return;
56441
+ }
56442
+ if (action === "remove") {
56443
+ removeInstallationProfile(name2, cfg);
56444
+ console.log(`profile removed locally: ${name2}`);
56445
+ return;
56446
+ }
56447
+ if (action === "save") {
56448
+ const direct = args2.find((arg) => arg.startsWith("--installation="))?.slice("--installation=".length);
56449
+ const flag = args2.indexOf("--installation");
56450
+ const installationId = direct ?? (flag >= 0 ? args2[flag + 1] : void 0);
56451
+ if (!installationId) throw new Error("usage: errata profile save <name> --installation <uuid>");
56452
+ if (!hasCloudCredential(cfg)) {
56453
+ throw new Error("not logged in \u2014 install the Console credential before saving a profile");
56454
+ }
56455
+ saveInstallationProfile(name2, installationId, cfg);
56456
+ console.log(`profile saved: ${name2} \xB7 installation ${installationId}`);
56457
+ return;
56458
+ }
56459
+ throw new Error(`unknown profile action: ${action}`);
56460
+ }
55692
56461
  async function cmdReview() {
55693
56462
  const paths = workspacePaths(ROOT);
55694
56463
  if (!existsSync24(paths.reviewQueue)) {
@@ -55769,9 +56538,7 @@ async function cmdLocate(relPath) {
55769
56538
  const file2 = findFileByPath2(store, relPath);
55770
56539
  if (!file2) {
55771
56540
  console.error(`not indexed: ${relPath}`);
55772
- console.error(
55773
- " \u2192 did you run `errata reindex --clean`? did you spell the relative path right?"
55774
- );
56541
+ console.error(" \u2192 did you run `errata reindex --clean`? did you spell the relative path right?");
55775
56542
  process.exit(2);
55776
56543
  }
55777
56544
  const symbols = store.outEdges(file2.id, ["DEFINES", "CONTAINS"]).map((e) => store.getNode(e.to)).filter((n) => n != null);
@@ -56019,9 +56786,7 @@ async function cmdSearch(args2) {
56019
56786
  for (const h of dual.results) {
56020
56787
  console.log(`${h.id}`);
56021
56788
  console.log(` [${h.label}] ${h.name.slice(0, 100)}`);
56022
- console.log(
56023
- ` score ${Number(h.score).toFixed(4)}${h.provenance ? ` \xB7 ${h.provenance}` : ""}`
56024
- );
56789
+ console.log(` score ${Number(h.score).toFixed(4)}${h.provenance ? ` \xB7 ${h.provenance}` : ""}`);
56025
56790
  }
56026
56791
  return;
56027
56792
  }
@@ -56131,9 +56896,7 @@ async function cmdSimilar(args2) {
56131
56896
  }
56132
56897
  console.log(`seed: ${r.seedId}`);
56133
56898
  for (const h of r.hits) {
56134
- console.log(
56135
- ` ${h.score.toFixed(4)} [${h.label}] ${h.name.slice(0, 80)} (${h.id})`
56136
- );
56899
+ console.log(` ${h.score.toFixed(4)} [${h.label}] ${h.name.slice(0, 80)} (${h.id})`);
56137
56900
  }
56138
56901
  });
56139
56902
  }
@@ -56165,7 +56928,10 @@ unresolved \u2014 no node matched "${seed}". Try \`errata search ${seed}\` for f
56165
56928
  return;
56166
56929
  }
56167
56930
  process.stdout.write(
56168
- formatBurstMd2(result, { limit, seedLabel: seed })
56931
+ formatBurstMd2(result, {
56932
+ limit,
56933
+ seedLabel: seed
56934
+ })
56169
56935
  );
56170
56936
  });
56171
56937
  }
@@ -56243,8 +57009,18 @@ async function gatherRepo(store, ws) {
56243
57009
  const sol = firstHop(store, p.id, ["SOLVED_BY", "FIXED_BY"]);
56244
57010
  const cause = firstHop(store, p.id, ["CAUSED_BY", "MANIFESTS_AS"]);
56245
57011
  const pi = addSpine(p, "Problem");
56246
- if (cause) spineEdges.push({ a: pi, b: addSpine(cause, spineType(cause.label, "RootCause")), kind: "causal" });
56247
- if (sol) spineEdges.push({ a: pi, b: addSpine(sol, spineType(sol.label, "Solution")), kind: "causal" });
57012
+ if (cause)
57013
+ spineEdges.push({
57014
+ a: pi,
57015
+ b: addSpine(cause, spineType(cause.label, "RootCause")),
57016
+ kind: "causal"
57017
+ });
57018
+ if (sol)
57019
+ spineEdges.push({
57020
+ a: pi,
57021
+ b: addSpine(sol, spineType(sol.label, "Solution")),
57022
+ kind: "causal"
57023
+ });
56248
57024
  if (pIdx >= LEARNED_MAX) continue;
56249
57025
  const srcCount = Array.isArray(p.attrs["sources"]) ? p.attrs["sources"].length : p.attrs["sources"] ? 1 : 0;
56250
57026
  const agents = Math.max(1, srcCount + Number(p.attrs["corroborations"] ?? 0));
@@ -56309,11 +57085,21 @@ async function gatherRepo(store, ws) {
56309
57085
  if (byFile.size > 0) {
56310
57086
  const ranked = [...byFile].sort((a, b) => b[1] - a[1]).slice(0, 6);
56311
57087
  const maxC = ranked[0]?.[1] ?? 1;
56312
- hotspots = ranked.map(([file2, c]) => ({ file: file2.split(/[\\/]/).pop() ?? file2, problemCount: c, weight: c / maxC, unit: "probs" }));
57088
+ hotspots = ranked.map(([file2, c]) => ({
57089
+ file: file2.split(/[\\/]/).pop() ?? file2,
57090
+ problemCount: c,
57091
+ weight: c / maxC,
57092
+ unit: "probs"
57093
+ }));
56313
57094
  } else {
56314
57095
  const fan = await runTool2("errata.hotspots", { kind: "fan-in", limit: 6 }, store);
56315
57096
  const maxF = fan.items[0]?.fanIn ?? 1;
56316
- hotspots = fan.items.map((h) => ({ file: h.name.split(/[\\/]/).pop() ?? h.name, problemCount: h.fanIn, weight: (h.fanIn ?? 0) / maxF, unit: "fan-in" }));
57097
+ hotspots = fan.items.map((h) => ({
57098
+ file: h.name.split(/[\\/]/).pop() ?? h.name,
57099
+ problemCount: h.fanIn,
57100
+ weight: (h.fanIn ?? 0) / maxF,
57101
+ unit: "fan-in"
57102
+ }));
56317
57103
  }
56318
57104
  const revisit = await runTool2("errata.needs_revisit", {}, store);
56319
57105
  const machineOnly = SEMANTIC_LABELS2.reduce((sum, l) => sum + store.findNodesByLabel(l).length, 0);
@@ -56404,14 +57190,14 @@ async function cmdReport(args2) {
56404
57190
  for (const f of files) writeFileSync18(join26(outDir, f.name), f.html, "utf8");
56405
57191
  const indexPath = join26(outDir, "report.html");
56406
57192
  console.log(`report \u2192 ${indexPath}`);
56407
- console.log(` ${data.repos.length} repo(s) \xB7 ${files.length} file(s) \xB7 ${data.rollup.nodes.toLocaleString("en-US")} nodes`);
57193
+ console.log(
57194
+ ` ${data.repos.length} repo(s) \xB7 ${files.length} file(s) \xB7 ${data.rollup.nodes.toLocaleString("en-US")} nodes`
57195
+ );
56408
57196
  console.log(` open: file://${indexPath.replace(/\\/g, "/")}`);
56409
57197
  }
56410
57198
  async function cmdStop() {
56411
57199
  const { unlinkSync: unlinkSync4 } = await import("node:fs");
56412
- const lockPath = [globalDaemonLock(), workspacePaths(ROOT).daemonLock].find(
56413
- (p) => readDaemonLock(p) !== null
56414
- );
57200
+ const lockPath = [globalDaemonLock(), workspacePaths(ROOT).daemonLock].find((p) => readDaemonLock(p) !== null);
56415
57201
  if (!lockPath) {
56416
57202
  console.log(`no daemon lock found \u2014 nothing to stop`);
56417
57203
  return;
@@ -56486,9 +57272,7 @@ function ensureSingletonRunning() {
56486
57272
  async function cmdInstallHooks(args2) {
56487
57273
  const harness = (args2[0] && !args2[0].startsWith("-") ? args2[0] : "claude").toLowerCase();
56488
57274
  const portIdx = args2.indexOf("--port");
56489
- const port = resolveHookPort(
56490
- portIdx >= 0 && args2[portIdx + 1] ? Number(args2[portIdx + 1]) : void 0
56491
- );
57275
+ const port = resolveHookPort(portIdx >= 0 && args2[portIdx + 1] ? Number(args2[portIdx + 1]) : void 0);
56492
57276
  switch (harness) {
56493
57277
  case "claude":
56494
57278
  await installClaudeHooks(port);
@@ -56794,7 +57578,13 @@ async function cmdDash(args2) {
56794
57578
  const reindexOnStart = !args2.includes("--no-reindex");
56795
57579
  const skipEmbed = !args2.includes("--embed");
56796
57580
  const skipWatchers = args2.includes("--no-watch");
56797
- const handle2 = await startMultiDaemon({ webPort: port, reindexOnStart, skipEmbed, skipWatchers, updateCheck: true });
57581
+ const handle2 = await startMultiDaemon({
57582
+ webPort: port,
57583
+ reindexOnStart,
57584
+ skipEmbed,
57585
+ skipWatchers,
57586
+ updateCheck: true
57587
+ });
56798
57588
  console.log(`errata multi-daemon running`);
56799
57589
  console.log(` endpoint: ${handle2.url} (JSON \u2014 the human view is \`errata report\`)`);
56800
57590
  console.log(` projects: ${handle2.records.length}`);
@@ -56851,7 +57641,10 @@ async function cmdDash(args2) {
56851
57641
  scheduleQuiescenceFlush();
56852
57642
  }
56853
57643
  if (notifyEnabled && (r.problemsResolved || r.reviewsTriggered)) {
56854
- notifyTick({ problemsResolved: r.problemsResolved, reviewsTriggered: r.reviewsTriggered });
57644
+ notifyTick({
57645
+ problemsResolved: r.problemsResolved,
57646
+ reviewsTriggered: r.reviewsTriggered
57647
+ });
56855
57648
  }
56856
57649
  }
56857
57650
  }).finally(() => {
@@ -56877,7 +57670,10 @@ async function cmdDash(args2) {
56877
57670
  }
56878
57671
  }
56879
57672
  if (skillsLearned > 0) {
56880
- notifyEvent("skill-learned", skillsLearned === 1 ? "Distilled a new reusable skill from your work." : `Distilled ${skillsLearned} new skills from your work.`);
57673
+ notifyEvent(
57674
+ "skill-learned",
57675
+ skillsLearned === 1 ? "Distilled a new reusable skill from your work." : `Distilled ${skillsLearned} new skills from your work.`
57676
+ );
56881
57677
  }
56882
57678
  const p = await handle2.percolateAll();
56883
57679
  const touched = /* @__PURE__ */ new Set();
@@ -56968,7 +57764,9 @@ async function cmdDash(args2) {
56968
57764
  const fanIn = wStore.inEdges(anchor.to, [...CODE_REACH_EDGES]).length;
56969
57765
  if (fanIn < HOTSPOT_FANIN) continue;
56970
57766
  const sym = wStore.getNode(anchor.to)?.description ?? "a symbol";
56971
- notifyEvent("hotspot-problem", `"${p2.description.slice(0, 60)}" touches ${sym} (${fanIn} dependents).`, { key: p2.id });
57767
+ notifyEvent("hotspot-problem", `"${p2.description.slice(0, 60)}" touches ${sym} (${fanIn} dependents).`, {
57768
+ key: p2.id
57769
+ });
56972
57770
  }
56973
57771
  }
56974
57772
  void handle2.syncPrinciplesPublic().then((r) => {
@@ -56998,7 +57796,9 @@ async function cmdDash(args2) {
56998
57796
  try {
56999
57797
  await materializeOverview2(r.root, r.engine.store);
57000
57798
  } catch (err2) {
57001
- console.warn(`[errata] overview refresh failed for ${r.entry.name}: ${err2 instanceof Error ? err2.message : err2}`);
57799
+ console.warn(
57800
+ `[errata] overview refresh failed for ${r.entry.name}: ${err2 instanceof Error ? err2.message : err2}`
57801
+ );
57002
57802
  }
57003
57803
  }
57004
57804
  maybeFlushDigests();
@@ -57013,7 +57813,12 @@ async function cmdDash(args2) {
57013
57813
  if (consolidating) return;
57014
57814
  if (process.uptime() < CONSOLIDATE_BOOT_GRACE_S) return;
57015
57815
  consolidationState.momentum = totalMutations() - consolidatedAtMutations;
57016
- if (!shouldConsolidate({ now: Date.now(), state: consolidationState, policy: consolidationPolicy, force })) {
57816
+ if (!shouldConsolidate({
57817
+ now: Date.now(),
57818
+ state: consolidationState,
57819
+ policy: consolidationPolicy,
57820
+ force
57821
+ })) {
57017
57822
  return;
57018
57823
  }
57019
57824
  consolidating = true;
@@ -57111,9 +57916,7 @@ async function cmdFeedback(args2) {
57111
57916
  }
57112
57917
  async function ensureProfile() {
57113
57918
  if (!loadProfile(ROOT)) {
57114
- console.error(
57115
- `no errata workspace in ${ROOT}. Run 'errata init' first.`
57116
- );
57919
+ console.error(`no errata workspace in ${ROOT}. Run 'errata init' first.`);
57117
57920
  process.exit(2);
57118
57921
  }
57119
57922
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.0-dev.96",
3
+ "version": "2.0.0-dev.98",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {