@inerrata-corporation/errata 2.0.14-dev.3125 → 2.0.14-dev.3128

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 +266 -173
  2. package/package.json +1 -1
package/errata.mjs CHANGED
@@ -81027,7 +81027,7 @@ function createWatchBreaker(deps) {
81027
81027
  }
81028
81028
 
81029
81029
  // src/engine.ts
81030
- var DAEMON_VERSION = true ? "2.0.14-dev.3125" : "2.0.0-alpha.0";
81030
+ var DAEMON_VERSION = true ? "2.0.14-dev.3128" : "2.0.0-alpha.0";
81031
81031
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
81032
81032
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
81033
81033
  var GIT_OP_MUTE_MS = 4e3;
@@ -83521,6 +83521,151 @@ async function listenServer(fetchFn, port) {
83521
83521
  init_registry();
83522
83522
  init_identity2();
83523
83523
 
83524
+ // src/cli-identity.ts
83525
+ init_cloud_auth();
83526
+ init_config();
83527
+ init_consent();
83528
+ init_identity2();
83529
+ init_profile2();
83530
+
83531
+ // src/project-link.ts
83532
+ init_profile2();
83533
+ import { execFile as execFile3 } from "node:child_process";
83534
+ import { promisify } from "node:util";
83535
+ var execFileAsync = promisify(execFile3);
83536
+ function isClaimedElsewhere(err2) {
83537
+ if (!err2 || typeof err2 !== "object") return false;
83538
+ const e = err2;
83539
+ return e.status === 409 && e.code === "claimed_elsewhere";
83540
+ }
83541
+ function claimHoldStands(onDisk) {
83542
+ return Boolean(onDisk && !onDisk.projectId && onDisk.projectLinkHold);
83543
+ }
83544
+ var LINK_FIELDS = [
83545
+ "projectId",
83546
+ "projectLocator",
83547
+ "projectLinkHold",
83548
+ "projectLinkDisabled",
83549
+ "rootCommitReportedFor"
83550
+ ];
83551
+ function adoptLinkFields(into, from) {
83552
+ for (const k of LINK_FIELDS) {
83553
+ if (from[k] === void 0) delete into[k];
83554
+ else into[k] = from[k];
83555
+ }
83556
+ }
83557
+ async function readRootCommit(root) {
83558
+ try {
83559
+ const { stdout } = await execFileAsync("git", ["rev-list", "--max-parents=0", "HEAD"], {
83560
+ cwd: root,
83561
+ encoding: "utf8",
83562
+ timeout: 15e3,
83563
+ windowsHide: true
83564
+ });
83565
+ const sha2 = stdout.split(/\s+/).filter(Boolean).pop();
83566
+ return sha2 && /^[0-9a-f]{40,64}$/i.test(sha2) ? sha2.toLowerCase() : null;
83567
+ } catch {
83568
+ return null;
83569
+ }
83570
+ }
83571
+ async function ensureProjectLink(root, profile, client, rootCommit = readRootCommit) {
83572
+ if (profile.projectLinkDisabled) return { linked: false, reason: "disabled" };
83573
+ const locator = profile.repoLocator;
83574
+ if (!locator) return { linked: false, reason: "no-locator" };
83575
+ if (profile.projectId && profile.projectLocator === locator) {
83576
+ if (profile.rootCommitReportedFor !== profile.projectId && client.recordProjectRootCommit) {
83577
+ const sha2 = await rootCommit(root);
83578
+ if (sha2) {
83579
+ try {
83580
+ await client.recordProjectRootCommit(profile.projectId, sha2);
83581
+ profile.rootCommitReportedFor = profile.projectId;
83582
+ saveProfile(root, profile);
83583
+ } catch {
83584
+ }
83585
+ }
83586
+ }
83587
+ return { linked: false, reason: "already-linked" };
83588
+ }
83589
+ try {
83590
+ const sha2 = await rootCommit(root);
83591
+ const res = await client.resolveProject(locator, profile.name, sha2 ?? void 0);
83592
+ profile.projectId = res.project.id;
83593
+ profile.projectLocator = locator;
83594
+ if (sha2) profile.rootCommitReportedFor = res.project.id;
83595
+ delete profile.projectLinkHold;
83596
+ saveProfile(root, profile);
83597
+ return { linked: true, projectId: res.project.id, created: res.created };
83598
+ } catch (err2) {
83599
+ if (isClaimedElsewhere(err2)) {
83600
+ if (profile.projectLinkHold?.locator !== locator) {
83601
+ profile.projectLinkHold = { reason: "claimed-elsewhere", locator, since: Date.now() };
83602
+ saveProfile(root, profile);
83603
+ }
83604
+ return {
83605
+ linked: false,
83606
+ reason: "held",
83607
+ detail: "this repo is already on errata in another workspace \u2014 run `errata link` to join it or create your own project"
83608
+ };
83609
+ }
83610
+ return {
83611
+ linked: false,
83612
+ reason: "error",
83613
+ detail: err2 instanceof Error ? err2.message : String(err2)
83614
+ };
83615
+ }
83616
+ }
83617
+
83618
+ // src/cli-identity.ts
83619
+ init_registry();
83620
+ function workspaceCloud(cfg, entry, env2 = process.env) {
83621
+ if (registryCorrupt() && !env2["ERRATA_CONFIG_PATH"]) throw new IdentityBlockedError("workspace registry is unreadable");
83622
+ const identity = resolveWorkspaceIdentity(cfg, entry, env2);
83623
+ if (identity.invalid) throw new IdentityBlockedError(identity.reason);
83624
+ const name2 = identity.profileName ?? cfg.activeInstallationProfile;
83625
+ const profile = name2 ? cfg.installationProfiles[name2] : void 0;
83626
+ if (name2 && !profile) throw new IdentityBlockedError(`installation profile not found: ${name2}`);
83627
+ const config2 = profile ? { ...cfg, ...profile, activeInstallationProfile: name2 } : cfg;
83628
+ return {
83629
+ identity,
83630
+ config: config2,
83631
+ profile,
83632
+ hasCredential: hasCloudCredential(config2),
83633
+ client: name2 ? authedCloudClientForProfile(cfg, name2) : authedCloudClient(cfg),
83634
+ profileName: name2
83635
+ };
83636
+ }
83637
+ function configWithDeviceLogin(cfg, input) {
83638
+ const name2 = input.profileName.trim();
83639
+ if (!name2 || name2.length > 80) throw new Error("profile name must be 1\u201380 characters");
83640
+ const selected = input.makeDefault || cfg.activeInstallationProfile === name2;
83641
+ return {
83642
+ ...cfg,
83643
+ ...selected ? { ...input.profile, activeInstallationProfile: name2 } : {},
83644
+ installationProfiles: { ...cfg.installationProfiles, [name2]: input.profile }
83645
+ };
83646
+ }
83647
+ async function saveVerifiedInstallationProfile(name2, installationId, cfg) {
83648
+ const result = await authedCloudClient(cfg).context();
83649
+ if (result.context.installation_id?.toLowerCase() !== installationId.toLowerCase()) {
83650
+ throw new Error("credential belongs to a different installation (or is unbound); use login --device for the requested installation");
83651
+ }
83652
+ return saveInstallationProfile(name2, installationId, cfg);
83653
+ }
83654
+ async function linkRegisteredWorkspaces(cfg, loggedInProfile = cfg.activeInstallationProfile, log = console.log, env2 = process.env) {
83655
+ for (const entry of listWorkspaces()) {
83656
+ try {
83657
+ const cloud = workspaceCloud(cfg, entry, env2);
83658
+ if (cloud.profileName !== loggedInProfile || !cloud.hasCredential) continue;
83659
+ const profile = loadProfile(entry.path);
83660
+ if (!profile || !effectiveConsent(cloud.config, profile).sync) continue;
83661
+ refreshRepoLocator(entry.path, profile);
83662
+ const out2 = await ensureProjectLink(entry.path, profile, cloud.client);
83663
+ if (out2.linked) log(` linked ${profile.name} \u2192 project ${out2.projectId}${out2.created ? " (created)" : ""} \xB7 profile ${cloud.profileName ?? "default"}`);
83664
+ } catch {
83665
+ }
83666
+ }
83667
+ }
83668
+
83524
83669
  // src/instance-receipts.ts
83525
83670
  import { createHash as createHash18 } from "node:crypto";
83526
83671
  var FIELDS = ["contributedAtSeq", "cloudNodeId", "anchorsContributedDigest", "projectAnchorsContributedDigest", "semanticEdgesContributedDigest", "shipRefusedSinceSeq", "shipRefusedCount", "shipRefusedReason", "shipRefusedFingerprint"];
@@ -84825,12 +84970,12 @@ function startUpdatePoller(opts) {
84825
84970
  const check2 = opts.check ?? (() => checkForUpdate(opts.channel));
84826
84971
  const base = opts.intervalMs ?? POLL_INTERVAL_MS[opts.channel];
84827
84972
  let pending = null;
84828
- let inFlight = false;
84973
+ let inFlight2 = false;
84829
84974
  let timer = null;
84830
84975
  let stopped = false;
84831
84976
  const runOnce = async () => {
84832
- if (inFlight) return;
84833
- inFlight = true;
84977
+ if (inFlight2) return;
84978
+ inFlight2 = true;
84834
84979
  try {
84835
84980
  const status = await check2();
84836
84981
  if (status.latest === null) return;
@@ -84840,7 +84985,7 @@ function startUpdatePoller(opts) {
84840
84985
  if (changed) opts.onChange?.(pending);
84841
84986
  } catch {
84842
84987
  } finally {
84843
- inFlight = false;
84988
+ inFlight2 = false;
84844
84989
  }
84845
84990
  };
84846
84991
  const schedule = () => {
@@ -84912,93 +85057,6 @@ function liveCloudClient(src) {
84912
85057
  // src/multi.ts
84913
85058
  init_identity2();
84914
85059
 
84915
- // src/project-link.ts
84916
- init_profile2();
84917
- import { execFile as execFile3 } from "node:child_process";
84918
- import { promisify } from "node:util";
84919
- var execFileAsync = promisify(execFile3);
84920
- function isClaimedElsewhere(err2) {
84921
- if (!err2 || typeof err2 !== "object") return false;
84922
- const e = err2;
84923
- return e.status === 409 && e.code === "claimed_elsewhere";
84924
- }
84925
- function claimHoldStands(onDisk) {
84926
- return Boolean(onDisk && !onDisk.projectId && onDisk.projectLinkHold);
84927
- }
84928
- var LINK_FIELDS = [
84929
- "projectId",
84930
- "projectLocator",
84931
- "projectLinkHold",
84932
- "projectLinkDisabled",
84933
- "rootCommitReportedFor"
84934
- ];
84935
- function adoptLinkFields(into, from) {
84936
- for (const k of LINK_FIELDS) {
84937
- if (from[k] === void 0) delete into[k];
84938
- else into[k] = from[k];
84939
- }
84940
- }
84941
- async function readRootCommit(root) {
84942
- try {
84943
- const { stdout } = await execFileAsync("git", ["rev-list", "--max-parents=0", "HEAD"], {
84944
- cwd: root,
84945
- encoding: "utf8",
84946
- timeout: 15e3,
84947
- windowsHide: true
84948
- });
84949
- const sha2 = stdout.split(/\s+/).filter(Boolean).pop();
84950
- return sha2 && /^[0-9a-f]{40,64}$/i.test(sha2) ? sha2.toLowerCase() : null;
84951
- } catch {
84952
- return null;
84953
- }
84954
- }
84955
- async function ensureProjectLink(root, profile, client, rootCommit = readRootCommit) {
84956
- if (profile.projectLinkDisabled) return { linked: false, reason: "disabled" };
84957
- const locator = profile.repoLocator;
84958
- if (!locator) return { linked: false, reason: "no-locator" };
84959
- if (profile.projectId && profile.projectLocator === locator) {
84960
- if (profile.rootCommitReportedFor !== profile.projectId && client.recordProjectRootCommit) {
84961
- const sha2 = await rootCommit(root);
84962
- if (sha2) {
84963
- try {
84964
- await client.recordProjectRootCommit(profile.projectId, sha2);
84965
- profile.rootCommitReportedFor = profile.projectId;
84966
- saveProfile(root, profile);
84967
- } catch {
84968
- }
84969
- }
84970
- }
84971
- return { linked: false, reason: "already-linked" };
84972
- }
84973
- try {
84974
- const sha2 = await rootCommit(root);
84975
- const res = await client.resolveProject(locator, profile.name, sha2 ?? void 0);
84976
- profile.projectId = res.project.id;
84977
- profile.projectLocator = locator;
84978
- if (sha2) profile.rootCommitReportedFor = res.project.id;
84979
- delete profile.projectLinkHold;
84980
- saveProfile(root, profile);
84981
- return { linked: true, projectId: res.project.id, created: res.created };
84982
- } catch (err2) {
84983
- if (isClaimedElsewhere(err2)) {
84984
- if (profile.projectLinkHold?.locator !== locator) {
84985
- profile.projectLinkHold = { reason: "claimed-elsewhere", locator, since: Date.now() };
84986
- saveProfile(root, profile);
84987
- }
84988
- return {
84989
- linked: false,
84990
- reason: "held",
84991
- detail: "this repo is already on errata in another workspace \u2014 run `errata link` to join it or create your own project"
84992
- };
84993
- }
84994
- return {
84995
- linked: false,
84996
- reason: "error",
84997
- detail: err2 instanceof Error ? err2.message : String(err2)
84998
- };
84999
- }
85000
- }
85001
-
85002
85060
  // src/project-hydrate.ts
85003
85061
  init_src11();
85004
85062
  init_profile2();
@@ -85409,7 +85467,31 @@ import { extname as extname3 } from "node:path";
85409
85467
  init_profile2();
85410
85468
  var exec = promisify2(execFile4);
85411
85469
  var KINDS = { function: "Function", class: "Class", method: "Method", interface: "Interface", const: "Const", enum: "Enum", typealias: "TypeAlias", namespace: "Namespace", property: "Property" };
85470
+ var inFlight = /* @__PURE__ */ new Map();
85412
85471
  async function syncHistoryRevision(opts) {
85472
+ const current = inFlight.get(opts.root);
85473
+ if (current) return current;
85474
+ const work = (async () => {
85475
+ const started2 = Date.now();
85476
+ let changed = 0, remaining = false;
85477
+ const pages = Math.min(20, Math.max(1, opts.maxPages ?? 10));
85478
+ for (let i2 = 0; i2 < pages; i2++) {
85479
+ const result = await syncHistoryRevisionPage(opts);
85480
+ changed += result.changed;
85481
+ remaining = result.remaining;
85482
+ if (!remaining || Date.now() - started2 >= (opts.budgetMs ?? 45e3)) break;
85483
+ await new Promise((resolve7) => setImmediate(resolve7));
85484
+ }
85485
+ return { changed, remaining };
85486
+ })();
85487
+ inFlight.set(opts.root, work);
85488
+ try {
85489
+ return await work;
85490
+ } finally {
85491
+ if (inFlight.get(opts.root) === work) inFlight.delete(opts.root);
85492
+ }
85493
+ }
85494
+ async function syncHistoryRevisionPage(opts) {
85413
85495
  const { root, profile, store, cloud, salt } = opts;
85414
85496
  if (!profile.projectId) return { changed: 0, remaining: false };
85415
85497
  const projectId = profile.projectId;
@@ -85419,7 +85501,8 @@ async function syncHistoryRevision(opts) {
85419
85501
  const oldCursor = profile.historyRevisionCursor;
85420
85502
  const same = oldCursor?.atCommit === atCommit;
85421
85503
  if (same && oldCursor.complete && Date.now() - oldCursor.updatedAt < 60 * 6e4) return { changed: 0, remaining: false };
85422
- const after = same && !oldCursor.complete ? oldCursor.after : void 0;
85504
+ const after = oldCursor && !oldCursor.complete ? oldCursor.after : void 0;
85505
+ const sweepCommit = after ? oldCursor.atCommit : atCommit;
85423
85506
  const page = await cloud.getHistoryOccurrences(projectId, atCommit, after);
85424
85507
  if (!Array.isArray(page.occurrences) || page.occurrences.length > 50 || page.occurrences.some((o, i2) => o.occurrenceId <= (page.occurrences[i2 - 1]?.occurrenceId ?? after ?? "")) || page.cursor !== null && page.cursor !== page.occurrences.at(-1)?.occurrenceId) throw new Error("invalid history occurrence page");
85425
85508
  const parents = (await git3(["rev-list", "--parents", "--max-count=8192", atCommit])).trim().split("\n").filter(Boolean).map((row) => {
@@ -85475,7 +85558,7 @@ async function syncHistoryRevision(opts) {
85475
85558
  }
85476
85559
  const response = page.occurrences.length ? await cloud.reconcileHistory(projectId, { atCommit, parents, observations }) : { atCommit, receipts: [] };
85477
85560
  if (response.atCommit !== atCommit || response.receipts.length !== page.occurrences.length || new Set(response.receipts.map((r) => r.occurrenceId)).size !== response.receipts.length || response.receipts.some((r) => r.atCommit !== atCommit || !observations.some((o) => o.occurrenceId === r.occurrenceId))) throw new Error("incomplete history reconciliation response");
85478
- if ((await git3(["rev-parse", "--verify", "HEAD"])).trim() !== atCommit) return { changed: 0, remaining: true };
85561
+ const headChanged = (await git3(["rev-parse", "--verify", "HEAD"])).trim() !== atCommit;
85479
85562
  let changed = 0;
85480
85563
  for (const occurrence of page.occurrences) {
85481
85564
  const node2 = store.getNode(occurrence.canonicalId) ?? (occurrence.nodeId ? store.getNode(occurrence.nodeId) : null);
@@ -85491,9 +85574,9 @@ async function syncHistoryRevision(opts) {
85491
85574
  } } });
85492
85575
  changed++;
85493
85576
  }
85494
- profile.historyRevisionCursor = { atCommit, ...page.cursor ? { after: page.cursor } : {}, complete: page.cursor === null, updatedAt: Date.now() };
85577
+ profile.historyRevisionCursor = { atCommit: sweepCommit, ...page.cursor ? { after: page.cursor } : {}, complete: page.cursor === null, updatedAt: Date.now() };
85495
85578
  (opts.save ?? ((p) => saveProfile(root, p)))(profile);
85496
- return { changed, remaining: page.cursor !== null };
85579
+ return { changed, remaining: page.cursor !== null || headChanged || sweepCommit !== atCommit };
85497
85580
  }
85498
85581
 
85499
85582
  // src/project-history.ts
@@ -88054,6 +88137,8 @@ async function startMultiDaemon(opts = {}) {
88054
88137
  // eslint-disable-next-line no-console
88055
88138
  log: (line) => console.log(line)
88056
88139
  });
88140
+ const revisionBacklog = /* @__PURE__ */ new Set();
88141
+ const lastAnchorPull = /* @__PURE__ */ new Map();
88057
88142
  const pullProjectAnchors = async (r, client, salt) => {
88058
88143
  const donePass = markPass(`anchor-pull:${r.engine.profile.name}`);
88059
88144
  const beforeCorrections = r.engine.profile.projectHistoryCorrectionCursor ?? 0;
@@ -88070,6 +88155,8 @@ async function startMultiDaemon(opts = {}) {
88070
88155
  }
88071
88156
  try {
88072
88157
  const revision = await syncHistoryRevision({ root: r.root, profile: r.engine.profile, store: r.engine.store, cloud: client, salt });
88158
+ if (revision.remaining) revisionBacklog.add(r.root);
88159
+ else revisionBacklog.delete(r.root);
88073
88160
  if (revision.changed) r.engine.markContextDirty();
88074
88161
  } catch (err2) {
88075
88162
  console.warn(`[errata] history revision reconciliation failed for ${r.engine.profile.name}: ${err2 instanceof Error ? err2.message : String(err2)}`);
@@ -88086,30 +88173,39 @@ async function startMultiDaemon(opts = {}) {
88086
88173
  }
88087
88174
  };
88088
88175
  const ANCHOR_PULL_INTERVAL_MS = 10 * 60 * 1e3;
88176
+ let anchorPullRunning = false;
88089
88177
  const anchorPullAll = async () => {
88090
- const c = loadConfig();
88091
- if (registryCorrupt()) return;
88092
- for (const r of records) {
88093
- const projectId = r.engine.profile.projectId;
88094
- if (!projectId || !effectiveConsent(c, r.engine.profile).sync) continue;
88095
- const identity = identityFor(liveWorkspaceEntry(r.entry));
88096
- if (identity.invalid) {
88097
- noteIdentityBlock(r.id, r.entry.name, identity);
88098
- continue;
88099
- }
88100
- if (identity.rule !== "workspace-binding" && !hasCloudCredential(c)) continue;
88101
- const client = cloudForIdentity(identity);
88102
- try {
88103
- const { salt } = await resolveProjectSymbolSalt(client, projectId);
88104
- await pullProjectAnchors(r, client, salt);
88105
- } catch (err2) {
88106
- console.warn(`[errata] anchor pull skipped for ${r.engine.profile.name}: ${err2 instanceof Error ? err2.message : String(err2)}`);
88178
+ if (anchorPullRunning) return;
88179
+ anchorPullRunning = true;
88180
+ try {
88181
+ const c = loadConfig();
88182
+ if (registryCorrupt()) return;
88183
+ for (const r of records) {
88184
+ if (!revisionBacklog.has(r.root) && Date.now() - (lastAnchorPull.get(r.root) ?? 0) < ANCHOR_PULL_INTERVAL_MS) continue;
88185
+ const projectId = r.engine.profile.projectId;
88186
+ if (!projectId || !effectiveConsent(c, r.engine.profile).sync) continue;
88187
+ const identity = identityFor(liveWorkspaceEntry(r.entry));
88188
+ if (identity.invalid) {
88189
+ noteIdentityBlock(r.id, r.entry.name, identity);
88190
+ continue;
88191
+ }
88192
+ if (identity.rule !== "workspace-binding" && !hasCloudCredential(c)) continue;
88193
+ const client = cloudForIdentity(identity);
88194
+ try {
88195
+ const { salt } = await resolveProjectSymbolSalt(client, projectId);
88196
+ await pullProjectAnchors(r, client, salt);
88197
+ lastAnchorPull.set(r.root, Date.now());
88198
+ } catch (err2) {
88199
+ console.warn(`[errata] anchor pull skipped for ${r.engine.profile.name}: ${err2 instanceof Error ? err2.message : String(err2)}`);
88200
+ }
88107
88201
  }
88202
+ } finally {
88203
+ anchorPullRunning = false;
88108
88204
  }
88109
88205
  };
88110
88206
  const anchorPull = setInterval(() => {
88111
88207
  void anchorPullAll();
88112
- }, ANCHOR_PULL_INTERVAL_MS);
88208
+ }, 6e4);
88113
88209
  const ambientLinkAll = async () => {
88114
88210
  const c = loadConfig();
88115
88211
  if (registryCorrupt()) return;
@@ -89024,7 +89120,6 @@ async function startMultiDaemon(opts = {}) {
89024
89120
  // src/cli.ts
89025
89121
  init_src5();
89026
89122
  init_profile2();
89027
- init_consent();
89028
89123
  init_git_remote();
89029
89124
  init_config();
89030
89125
  init_src7();
@@ -90222,7 +90317,7 @@ Commands:
90222
90317
  login Sign in with the cloud. Default: short verification URL +
90223
90318
  typeable code (device-bridged OAuth). Flags: --browser
90224
90319
  (loopback code flow) \xB7 --token <key> \xB7
90225
- --device --installation <uuid> [--profile <name>]
90320
+ --device --installation <uuid> [--profile <name>] [--make-default]
90226
90321
  (headless, installation-bound console device authorization)
90227
90322
  logout Clear local cloud credentials
90228
90323
  doctor [--json] Verify profile, credential authority, renewal, endpoint,
@@ -90852,6 +90947,7 @@ function parseFlags(args2) {
90852
90947
  else if (a.startsWith("--token=")) out2.token = a.slice("--token=".length);
90853
90948
  else if (a === "--oauth") out2.oauth = true;
90854
90949
  else if (a === "--device") out2.device = true;
90950
+ else if (a === "--make-default") out2.makeDefault = true;
90855
90951
  else if (a === "--browser") out2.browser = true;
90856
90952
  else if (a === "--installation") out2.installation = args2[++i2];
90857
90953
  else if (a.startsWith("--installation=")) out2.installation = a.slice("--installation=".length);
@@ -90925,6 +91021,8 @@ async function applyToken(cfg, token) {
90925
91021
  cfg.oauthClientId = null;
90926
91022
  cfg.userId = me.agentId;
90927
91023
  cfg.email = me.handle;
91024
+ cfg.activeInstallationProfile = null;
91025
+ cfg.activeAgent = null;
90928
91026
  saveConfig(cfg);
90929
91027
  console.log(`logged in as ${me.handle} (${me.tier}) \u2014 key stored`);
90930
91028
  return true;
@@ -90975,7 +91073,9 @@ async function cmdLoginOAuth(cfg, useBrowserLoopback = false) {
90975
91073
  refreshToken: result.tokens.refreshToken ?? null,
90976
91074
  tokenEndpoint: result.tokenEndpoint,
90977
91075
  oauthClientId: result.clientId,
90978
- apiKey: null
91076
+ apiKey: null,
91077
+ activeInstallationProfile: null,
91078
+ activeAgent: null
90979
91079
  };
90980
91080
  try {
90981
91081
  const me = await authedCloudClient(nextCfg).me();
@@ -91016,58 +91116,38 @@ async function cmdLoginOAuthDevice(cfg, input) {
91016
91116
  console.log(`code: ${userCode} \xB7 expires in ${Math.round(expiresInSec / 60)} minutes`);
91017
91117
  }
91018
91118
  });
91019
- const nextCfg = {
91020
- ...cfg,
91021
- apiKey: null,
91022
- accessToken: result.tokens.accessToken,
91023
- refreshToken: result.tokens.refreshToken ?? null,
91024
- tokenEndpoint: result.tokenEndpoint,
91025
- oauthClientId: result.clientId,
91026
- activeInstallationProfile: profileName,
91027
- installationProfiles: {
91028
- ...cfg.installationProfiles,
91029
- [profileName]: {
91030
- installationId: result.installationId,
91031
- cloudUrl: cfg.cloudUrl,
91032
- apiKey: null,
91033
- accessToken: result.tokens.accessToken,
91034
- refreshToken: result.tokens.refreshToken ?? null,
91035
- tokenEndpoint: result.tokenEndpoint,
91036
- oauthClientId: result.clientId,
91037
- activeAgent: cfg.activeAgent
91038
- }
91119
+ const nextCfg = configWithDeviceLogin(loadConfig(), {
91120
+ profileName,
91121
+ makeDefault: input.makeDefault ?? !input.profileName,
91122
+ profile: {
91123
+ installationId: result.installationId,
91124
+ cloudUrl: cfg.cloudUrl,
91125
+ apiKey: null,
91126
+ accessToken: result.tokens.accessToken,
91127
+ refreshToken: result.tokens.refreshToken ?? null,
91128
+ tokenEndpoint: result.tokenEndpoint,
91129
+ oauthClientId: result.clientId,
91130
+ activeAgent: null
91039
91131
  }
91040
- };
91041
- const me = await authedCloudClient(nextCfg).me();
91042
- nextCfg.userId = me.agentId;
91043
- nextCfg.email = me.handle;
91132
+ });
91133
+ const me = await authedCloudClientForProfile(nextCfg, profileName).me();
91134
+ if (nextCfg.activeInstallationProfile === profileName) {
91135
+ nextCfg.userId = me.agentId;
91136
+ nextCfg.email = me.handle;
91137
+ }
91044
91138
  saveConfig(nextCfg);
91045
91139
  console.log(
91046
91140
  `logged in as ${me.handle} (${me.tier}) \xB7 profile ${profileName} \xB7 installation ${result.installationId}`
91047
91141
  );
91048
- await linkRegisteredWorkspaces(nextCfg);
91142
+ await linkRegisteredWorkspaces(nextCfg, profileName);
91143
+ console.log(`machine default: ${nextCfg.activeInstallationProfile ?? "top-level credential"}; workspaces using this profile: ${listWorkspaces().filter((w) => w.installationProfile === profileName || !w.installationProfile && nextCfg.activeInstallationProfile === profileName).map((w) => w.name).join(", ") || "none (bind a workspace to use it)"}`);
91049
91144
  } catch (err2) {
91050
91145
  console.error(`device login failed: ${err2 instanceof Error ? err2.message : err2}`);
91051
91146
  console.error(" no credential was stored; retry from Console \u2192 Connect");
91052
91147
  process.exitCode = 1;
91053
91148
  }
91054
91149
  }
91055
- async function linkRegisteredWorkspaces(cfg) {
91056
- const client = authedCloudClient(cfg);
91057
- for (const w of listWorkspaces()) {
91058
- try {
91059
- const profile = loadProfile(w.path);
91060
- if (!profile || !effectiveConsent(cfg, profile).sync) continue;
91061
- refreshRepoLocator(w.path, profile);
91062
- const out2 = await ensureProjectLink(w.path, profile, client);
91063
- if (out2.linked) {
91064
- console.log(` linked ${profile.name} \u2192 project ${out2.projectId}${out2.created ? " (created)" : ""}`);
91065
- }
91066
- } catch {
91067
- }
91068
- }
91069
- }
91070
- async function bindWorkspaceToLinkInstallation(cfg, out2, orgId) {
91150
+ async function bindWorkspaceToLinkInstallation(cfg, out2, orgId, cloudUrl) {
91071
91151
  const installationId = out2.installationId;
91072
91152
  if (!out2.deviceSession) {
91073
91153
  console.warn(
@@ -91091,7 +91171,7 @@ async function bindWorkspaceToLinkInstallation(cfg, out2, orgId) {
91091
91171
  ...cfg.installationProfiles,
91092
91172
  [profileName]: {
91093
91173
  installationId,
91094
- cloudUrl: cfg.cloudUrl,
91174
+ cloudUrl,
91095
91175
  apiKey: null,
91096
91176
  accessToken: minted.tokens.accessToken,
91097
91177
  refreshToken: minted.tokens.refreshToken ?? null,
@@ -91123,8 +91203,9 @@ async function cmdLink(args2) {
91123
91203
  return;
91124
91204
  }
91125
91205
  const cfg = loadConfig();
91206
+ const cloud = workspaceCloud(cfg, workspaceEntryForPath(ROOT));
91126
91207
  const wantsLocal = args2.includes("--local") || Boolean(flag("--project")) || Boolean(flag("--name"));
91127
- if (wantsLocal && !hasCloudCredential(cfg)) {
91208
+ if (wantsLocal && !cloud.hasCredential) {
91128
91209
  console.error("not logged in \u2014 run `errata login` first (linking needs your cloud identity)");
91129
91210
  process.exitCode = 1;
91130
91211
  return;
@@ -91149,7 +91230,7 @@ async function cmdLink(args2) {
91149
91230
  return;
91150
91231
  }
91151
91232
  saveProfile(ROOT, profile);
91152
- const activeProfile = cfg.activeInstallationProfile ? cfg.installationProfiles[cfg.activeInstallationProfile] : void 0;
91233
+ const activeProfile = cloud.profile;
91153
91234
  const facts = {
91154
91235
  locator: profile.repoLocator,
91155
91236
  name: profile.name,
@@ -91158,7 +91239,7 @@ async function cmdLink(args2) {
91158
91239
  };
91159
91240
  try {
91160
91241
  const out3 = await linkThroughConsole({
91161
- cloudUrl: cfg.cloudUrl,
91242
+ cloudUrl: cloud.config.cloudUrl,
91162
91243
  facts,
91163
91244
  timeoutMs: 15 * 6e4,
91164
91245
  onVerification: ({ url: url2, userCode, expiresInSec }) => {
@@ -91191,24 +91272,28 @@ async function cmdLink(args2) {
91191
91272
  );
91192
91273
  await refreshLinkedCodexHooks(args2);
91193
91274
  if (applied.wroteIgnore) console.log("wrote .errataignore (starter) \u2014 edit it; it is your hard control");
91194
- let reportClient = authedCloudClient(applied.config);
91275
+ let reportCloud = workspaceCloud(applied.config, workspaceEntryForPath(ROOT));
91276
+ let reportClient = reportCloud.client;
91195
91277
  if (out3.installationId && out3.installationId !== activeProfile?.installationId) {
91196
- const bound = await bindWorkspaceToLinkInstallation(applied.config, out3, d.orgId);
91278
+ const bound = await bindWorkspaceToLinkInstallation(applied.config, out3, d.orgId, cloud.config.cloudUrl);
91197
91279
  if (bound) {
91198
- reportClient = authedCloudClientForProfile(bound.config, bound.profileName);
91280
+ reportCloud = workspaceCloud(bound.config, workspaceEntryForPath(ROOT));
91281
+ reportClient = reportCloud.client;
91199
91282
  console.log(
91200
91283
  `this workspace now uses installation ${out3.installationId.slice(0, 8)} (profile ${bound.profileName}) in org ${d.orgId.slice(0, 8)}; your machine default is unchanged`
91201
91284
  );
91285
+ } else {
91286
+ return;
91202
91287
  }
91203
91288
  }
91204
- if (!hasCloudCredential(cfg)) {
91289
+ if (!reportCloud.hasCredential) {
91205
91290
  console.log(" note: run `errata login` so this daemon can sync under the link and report the history stage.");
91206
91291
  return;
91207
91292
  }
91208
91293
  const consents = historyConsents(applied.profile);
91209
91294
  const reported = await reportHistoryStage(reportClient, applied.profile, (line) => console.warn(line));
91210
91295
  if (reported?.consents) {
91211
- const synced = applyConsents(applied.profile, applied.config, reported.consents, (/* @__PURE__ */ new Date()).toISOString());
91296
+ const synced = applyConsents(applied.profile, loadConfig(), reported.consents, (/* @__PURE__ */ new Date()).toISOString());
91212
91297
  if (synced.changed) {
91213
91298
  saveProfile(ROOT, synced.profile);
91214
91299
  saveConfig(synced.config);
@@ -91220,7 +91305,7 @@ async function cmdLink(args2) {
91220
91305
  return;
91221
91306
  } catch (err2) {
91222
91307
  const message2 = err2 instanceof Error ? err2.message : String(err2);
91223
- if (!/does not offer the link flow/.test(message2) || !hasCloudCredential(cfg)) {
91308
+ if (!/does not offer the link flow/.test(message2) || !cloud.hasCredential) {
91224
91309
  console.error(`link failed: ${message2}`);
91225
91310
  process.exitCode = 1;
91226
91311
  return;
@@ -91228,7 +91313,7 @@ async function cmdLink(args2) {
91228
91313
  console.log(`${message2}; falling back to the ambient resolve`);
91229
91314
  }
91230
91315
  }
91231
- const client = authedCloudClient(cfg);
91316
+ const client = cloud.client;
91232
91317
  const adoptId = flag("--project");
91233
91318
  if (adoptId) {
91234
91319
  if (!profile.repoLocator) {
@@ -91285,7 +91370,12 @@ async function cmdUnlink() {
91285
91370
  async function cmdLogin() {
91286
91371
  const cfg = loadConfig();
91287
91372
  const flags2 = parseFlags(rest);
91288
- if (flags2.cloud) applyCloudUrl(cfg, flags2.cloud);
91373
+ if (flags2.profile && (!flags2.device || flags2.token)) {
91374
+ console.error("--profile requires --device --installation <uuid>; the machine default was not changed");
91375
+ process.exitCode = 1;
91376
+ return;
91377
+ }
91378
+ if (flags2.cloud) cfg.cloudUrl = normalizeCloudUrl(flags2.cloud);
91289
91379
  if (flags2.token) {
91290
91380
  await applyToken(cfg, flags2.token);
91291
91381
  printBoundWorkspaceNotice();
@@ -91299,7 +91389,8 @@ async function cmdLogin() {
91299
91389
  }
91300
91390
  await cmdLoginOAuthDevice(cfg, {
91301
91391
  installationId: flags2.installation,
91302
- ...flags2.profile ? { profileName: flags2.profile } : {}
91392
+ ...flags2.profile ? { profileName: flags2.profile } : {},
91393
+ ...flags2.makeDefault ? { makeDefault: true } : {}
91303
91394
  });
91304
91395
  printBoundWorkspaceNotice();
91305
91396
  return;
@@ -91361,6 +91452,8 @@ async function cmdLogin() {
91361
91452
  cfg.oauthClientId = null;
91362
91453
  cfg.userId = poll.agentId ?? null;
91363
91454
  cfg.email = poll.handle ?? handle2;
91455
+ cfg.activeInstallationProfile = null;
91456
+ cfg.activeAgent = null;
91364
91457
  saveConfig(cfg);
91365
91458
  try {
91366
91459
  const me = await authedCloudClient(cfg).me();
@@ -91421,7 +91514,7 @@ function printBoundWorkspaceNotice() {
91421
91514
  } catch {
91422
91515
  }
91423
91516
  }
91424
- function cmdInstallationProfile(args2) {
91517
+ async function cmdInstallationProfile(args2) {
91425
91518
  const cfg = loadConfig();
91426
91519
  const action = args2[0] ?? "list";
91427
91520
  if (action === "list") {
@@ -91458,7 +91551,7 @@ function cmdInstallationProfile(args2) {
91458
91551
  if (!hasCloudCredential(cfg)) {
91459
91552
  throw new Error("not logged in \u2014 install the Console credential before saving a profile");
91460
91553
  }
91461
- saveInstallationProfile(name2, installationId, cfg);
91554
+ await saveVerifiedInstallationProfile(name2, installationId, cfg);
91462
91555
  console.log(`profile saved: ${name2} \xB7 installation ${installationId}`);
91463
91556
  return;
91464
91557
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.14-dev.3125",
3
+ "version": "2.0.14-dev.3128",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {