@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.
- package/errata.mjs +872 -69
- 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", {
|
|
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
|
-
|
|
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
|
-
|
|
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}
|
|
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
|
|
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) =>
|
|
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,
|
|
49648
|
-
const direct = LABEL_PAIR[`${sourceLabel}>${
|
|
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.
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(`
|
|
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(
|
|
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(
|
|
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, {
|
|
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)
|
|
56247
|
-
|
|
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]) => ({
|
|
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) => ({
|
|
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(
|
|
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({
|
|
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({
|
|
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(
|
|
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).`, {
|
|
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(
|
|
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({
|
|
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
|
}
|