@inerrata-corporation/errata 2.0.0-dev.95 → 2.0.0-dev.97
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 +212 -15
- package/package.json +1 -1
package/errata.mjs
CHANGED
|
@@ -22477,10 +22477,7 @@ function evaluateCloudEndpoint(url2, inspection) {
|
|
|
22477
22477
|
if (inspection?.kind === "gateway") {
|
|
22478
22478
|
return { allowed: true, label: "gateway-metered" };
|
|
22479
22479
|
}
|
|
22480
|
-
if (
|
|
22481
|
-
return { allowed: true, label: "gateway-configured" };
|
|
22482
|
-
}
|
|
22483
|
-
if (!inspection && looksLikeGatewayCloudUrl(url2)) {
|
|
22480
|
+
if (looksLikeGatewayCloudUrl(url2)) {
|
|
22484
22481
|
return { allowed: true, label: "gateway-configured" };
|
|
22485
22482
|
}
|
|
22486
22483
|
if (isDirectV1OverrideEnabled()) {
|
|
@@ -22526,7 +22523,10 @@ async function inspectCloudEndpoint(baseUrl, opts = {}) {
|
|
|
22526
22523
|
const local = isLocalCloudUrl(normalized);
|
|
22527
22524
|
const fetchFn = opts.fetchFn ?? fetch;
|
|
22528
22525
|
const timeoutMs = opts.timeoutMs ?? PROBE_TIMEOUT_MS;
|
|
22529
|
-
|
|
22526
|
+
let root = await fetchHealthJson(fetchFn, `${normalized}/health`, timeoutMs);
|
|
22527
|
+
if (!root.ok) {
|
|
22528
|
+
root = await fetchHealthJson(fetchFn, `${normalized}/health`, timeoutMs);
|
|
22529
|
+
}
|
|
22530
22530
|
if (root.ok && root.body?.service === GATEWAY_SERVICE) {
|
|
22531
22531
|
return {
|
|
22532
22532
|
baseUrl: normalized,
|
|
@@ -22568,11 +22568,17 @@ async function inspectCloudEndpoint(baseUrl, opts = {}) {
|
|
|
22568
22568
|
function guardCloudFetch(baseUrl, fetchFn = fetch) {
|
|
22569
22569
|
const normalized = normalizeCloudUrl(baseUrl);
|
|
22570
22570
|
let checked = null;
|
|
22571
|
+
let blockedAt = 0;
|
|
22571
22572
|
return (async (input, init2) => {
|
|
22572
22573
|
if (isProtectedCloudRequest(normalized, input)) {
|
|
22573
|
-
checked
|
|
22574
|
-
|
|
22575
|
-
|
|
22574
|
+
if (checked && blockedAt && Date.now() - blockedAt >= BLOCKED_RECHECK_MS) {
|
|
22575
|
+
checked = null;
|
|
22576
|
+
}
|
|
22577
|
+
checked ??= inspectCloudEndpoint(normalized, { fetchFn }).then((inspection) => {
|
|
22578
|
+
const decision2 = evaluateCloudEndpoint(normalized, inspection);
|
|
22579
|
+
blockedAt = decision2.allowed ? 0 : Date.now();
|
|
22580
|
+
return decision2;
|
|
22581
|
+
});
|
|
22576
22582
|
const decision = await checked;
|
|
22577
22583
|
if (!decision.allowed) {
|
|
22578
22584
|
throw new CloudEndpointPolicyError(formatCloudEndpointBlock(normalized, decision), decision);
|
|
@@ -22628,7 +22634,7 @@ async function fetchHealthJson(fetchFn, url2, timeoutMs) {
|
|
|
22628
22634
|
clearTimeout(tid);
|
|
22629
22635
|
}
|
|
22630
22636
|
}
|
|
22631
|
-
var DIRECT_V1_OVERRIDE_ENV, GATEWAY_SERVICE, PROBE_TIMEOUT_MS, CloudEndpointPolicyError;
|
|
22637
|
+
var DIRECT_V1_OVERRIDE_ENV, GATEWAY_SERVICE, PROBE_TIMEOUT_MS, CloudEndpointPolicyError, BLOCKED_RECHECK_MS;
|
|
22632
22638
|
var init_cloud_endpoint_policy = __esm({
|
|
22633
22639
|
"src/cloud-endpoint-policy.ts"() {
|
|
22634
22640
|
"use strict";
|
|
@@ -22643,6 +22649,7 @@ var init_cloud_endpoint_policy = __esm({
|
|
|
22643
22649
|
this.decision = decision;
|
|
22644
22650
|
}
|
|
22645
22651
|
};
|
|
22652
|
+
BLOCKED_RECHECK_MS = 6e4;
|
|
22646
22653
|
}
|
|
22647
22654
|
});
|
|
22648
22655
|
|
|
@@ -51464,7 +51471,7 @@ function startLoopLagMonitor(thresholdMs = 1e3) {
|
|
|
51464
51471
|
}
|
|
51465
51472
|
|
|
51466
51473
|
// src/engine.ts
|
|
51467
|
-
var DAEMON_VERSION = true ? "2.0.0-dev.
|
|
51474
|
+
var DAEMON_VERSION = true ? "2.0.0-dev.97" : "2.0.0-alpha.0";
|
|
51468
51475
|
var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
|
|
51469
51476
|
var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
|
|
51470
51477
|
var GIT_OP_MUTE_MS = 4e3;
|
|
@@ -54745,6 +54752,193 @@ async function loginOAuthLoopback(opts) {
|
|
|
54745
54752
|
return { tokens, tokenEndpoint: endpoints.tokenEndpoint, clientId };
|
|
54746
54753
|
}
|
|
54747
54754
|
|
|
54755
|
+
// src/device-login.ts
|
|
54756
|
+
init_src6();
|
|
54757
|
+
var DEFAULT_SCOPE = "openid profile graph:read graph:write mcp:tools";
|
|
54758
|
+
var BRIDGE_REDIRECT_URI = "http://127.0.0.1:1/device-bridge-callback";
|
|
54759
|
+
var strip2 = (u) => u.replace(/\/+$/, "");
|
|
54760
|
+
async function discoverDeviceEndpoint(cloudUrl, fetchFn = fetch) {
|
|
54761
|
+
try {
|
|
54762
|
+
const res = await fetchFn(`${strip2(cloudUrl)}/.well-known/oauth-authorization-server`);
|
|
54763
|
+
if (res.ok) {
|
|
54764
|
+
const j = await res.json();
|
|
54765
|
+
if (j.device_authorization_endpoint) return j.device_authorization_endpoint;
|
|
54766
|
+
}
|
|
54767
|
+
} catch {
|
|
54768
|
+
}
|
|
54769
|
+
return `${strip2(cloudUrl)}/device`;
|
|
54770
|
+
}
|
|
54771
|
+
async function requestDeviceAuthorization(fetchFn, deviceEndpoint, p) {
|
|
54772
|
+
const res = await fetchFn(deviceEndpoint, {
|
|
54773
|
+
method: "POST",
|
|
54774
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
54775
|
+
body: JSON.stringify({ client_id: p.clientId, scope: p.scope })
|
|
54776
|
+
});
|
|
54777
|
+
if (!res.ok) throw new Error(`device authorization failed: HTTP ${res.status}`);
|
|
54778
|
+
const j = await res.json();
|
|
54779
|
+
if (!j.device_code || !j.user_code || !j.verification_uri) {
|
|
54780
|
+
throw new Error("device authorization response missing device_code/user_code/verification_uri");
|
|
54781
|
+
}
|
|
54782
|
+
return {
|
|
54783
|
+
deviceCode: j.device_code,
|
|
54784
|
+
userCode: j.user_code,
|
|
54785
|
+
verificationUri: j.verification_uri,
|
|
54786
|
+
...j.verification_uri_complete ? { verificationUriComplete: j.verification_uri_complete } : {},
|
|
54787
|
+
expiresInSeconds: j.expires_in ?? 1800,
|
|
54788
|
+
intervalSeconds: j.interval ?? 5
|
|
54789
|
+
};
|
|
54790
|
+
}
|
|
54791
|
+
async function pollDeviceApproval(fetchFn, deviceEndpoint, p, sleep = (ms) => new Promise((r) => setTimeout(r, ms))) {
|
|
54792
|
+
const deadline = Date.now() + p.timeoutMs;
|
|
54793
|
+
let intervalMs = Math.max(1, p.intervalSeconds) * 1e3;
|
|
54794
|
+
for (; ; ) {
|
|
54795
|
+
if (Date.now() >= deadline) throw new Error("device approval timed out \u2014 run `errata login` again");
|
|
54796
|
+
await sleep(intervalMs);
|
|
54797
|
+
const res = await fetchFn(`${strip2(deviceEndpoint)}/token`, {
|
|
54798
|
+
method: "POST",
|
|
54799
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
54800
|
+
body: JSON.stringify({
|
|
54801
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
54802
|
+
device_code: p.deviceCode,
|
|
54803
|
+
client_id: p.clientId
|
|
54804
|
+
})
|
|
54805
|
+
});
|
|
54806
|
+
const j = await res.json().catch(() => ({}));
|
|
54807
|
+
if (res.ok && j.access_token) return j.access_token;
|
|
54808
|
+
if (j.error === "authorization_pending") continue;
|
|
54809
|
+
if (j.error === "slow_down") {
|
|
54810
|
+
intervalMs += 5e3;
|
|
54811
|
+
continue;
|
|
54812
|
+
}
|
|
54813
|
+
throw new Error(`device approval failed: ${j.error ?? `HTTP ${res.status}`}`);
|
|
54814
|
+
}
|
|
54815
|
+
}
|
|
54816
|
+
async function authorizeWithSession(fetchFn, p) {
|
|
54817
|
+
const u = new URL(p.authorizationEndpoint);
|
|
54818
|
+
u.searchParams.set("response_type", "code");
|
|
54819
|
+
u.searchParams.set("client_id", p.clientId);
|
|
54820
|
+
u.searchParams.set("redirect_uri", BRIDGE_REDIRECT_URI);
|
|
54821
|
+
u.searchParams.set("scope", p.scope);
|
|
54822
|
+
u.searchParams.set("state", p.state);
|
|
54823
|
+
u.searchParams.set("code_challenge", p.challenge);
|
|
54824
|
+
u.searchParams.set("code_challenge_method", p.method);
|
|
54825
|
+
const res = await fetchFn(u.toString(), {
|
|
54826
|
+
method: "GET",
|
|
54827
|
+
headers: { authorization: `Bearer ${p.sessionToken}`, accept: "application/json" },
|
|
54828
|
+
redirect: "manual"
|
|
54829
|
+
});
|
|
54830
|
+
const location = res.headers.get("location");
|
|
54831
|
+
if (!location) throw new Error(`authorize did not redirect (HTTP ${res.status}) \u2014 is the session valid?`);
|
|
54832
|
+
const codeFromLocation = extractCode(location, p.state);
|
|
54833
|
+
if (codeFromLocation) return { code: codeFromLocation };
|
|
54834
|
+
const consentUrl = new URL(location);
|
|
54835
|
+
const consentCode = consentUrl.searchParams.get("consent_code");
|
|
54836
|
+
if (!consentCode) throw new Error(`authorize redirected without code or consent_code: ${consentUrl.pathname}`);
|
|
54837
|
+
const pagePath = "/oauth/consent";
|
|
54838
|
+
const prefix = consentUrl.pathname.endsWith(pagePath) ? consentUrl.pathname.slice(0, -pagePath.length) : "";
|
|
54839
|
+
const consentApi = `${consentUrl.origin}${prefix}/api/auth/oauth2/consent`;
|
|
54840
|
+
const consentRes = await fetchFn(consentApi, {
|
|
54841
|
+
method: "POST",
|
|
54842
|
+
headers: {
|
|
54843
|
+
"content-type": "application/json",
|
|
54844
|
+
accept: "application/json",
|
|
54845
|
+
authorization: `Bearer ${p.sessionToken}`
|
|
54846
|
+
},
|
|
54847
|
+
body: JSON.stringify({ accept: true, consent_code: consentCode })
|
|
54848
|
+
});
|
|
54849
|
+
if (!consentRes.ok) throw new Error(`consent approval failed: HTTP ${consentRes.status}`);
|
|
54850
|
+
const consentBody = await consentRes.json();
|
|
54851
|
+
const code = consentBody.redirectURI ? extractCode(consentBody.redirectURI, p.state) : null;
|
|
54852
|
+
if (!code) throw new Error("consent approved but no authorization code returned");
|
|
54853
|
+
return { code };
|
|
54854
|
+
}
|
|
54855
|
+
function extractCode(redirectUrl, expectedState) {
|
|
54856
|
+
try {
|
|
54857
|
+
const u = new URL(redirectUrl);
|
|
54858
|
+
const code = u.searchParams.get("code");
|
|
54859
|
+
const state = u.searchParams.get("state");
|
|
54860
|
+
if (!code) return null;
|
|
54861
|
+
if (state !== expectedState) throw new Error("state mismatch in authorization redirect");
|
|
54862
|
+
return code;
|
|
54863
|
+
} catch (err2) {
|
|
54864
|
+
if (err2 instanceof Error && err2.message.includes("state mismatch")) throw err2;
|
|
54865
|
+
return null;
|
|
54866
|
+
}
|
|
54867
|
+
}
|
|
54868
|
+
async function loginViaDeviceBridge(opts) {
|
|
54869
|
+
const fetchFn = opts.fetchFn ?? fetch;
|
|
54870
|
+
const log = opts.log ?? console.log;
|
|
54871
|
+
const scope = opts.scope ?? DEFAULT_SCOPE;
|
|
54872
|
+
const timeoutMs = opts.timeoutMs ?? 15 * 6e4;
|
|
54873
|
+
const endpoints = await discoverEndpoints(opts.cloudUrl, fetchFn);
|
|
54874
|
+
const deviceEndpoint = await discoverDeviceEndpoint(opts.cloudUrl, fetchFn);
|
|
54875
|
+
if (!endpoints.registrationEndpoint) {
|
|
54876
|
+
throw new Error("cloud does not advertise dynamic client registration \u2014 try `errata login --browser`");
|
|
54877
|
+
}
|
|
54878
|
+
const { clientId } = await registerOAuthClient(fetchFn, endpoints.registrationEndpoint, {
|
|
54879
|
+
redirectUri: BRIDGE_REDIRECT_URI,
|
|
54880
|
+
scope,
|
|
54881
|
+
clientName: "errata daemon"
|
|
54882
|
+
});
|
|
54883
|
+
const grant = await requestDeviceAuthorization(fetchFn, deviceEndpoint, { clientId, scope });
|
|
54884
|
+
log(`approve this login:`);
|
|
54885
|
+
log(` visit ${grant.verificationUri}`);
|
|
54886
|
+
log(` code ${grant.userCode}`);
|
|
54887
|
+
if (grant.verificationUriComplete && opts.open) opts.open(grant.verificationUriComplete);
|
|
54888
|
+
const sessionToken = await pollDeviceApproval(fetchFn, deviceEndpoint, {
|
|
54889
|
+
clientId,
|
|
54890
|
+
deviceCode: grant.deviceCode,
|
|
54891
|
+
intervalSeconds: grant.intervalSeconds,
|
|
54892
|
+
timeoutMs
|
|
54893
|
+
});
|
|
54894
|
+
const pkce = generatePkce();
|
|
54895
|
+
const state = randomState();
|
|
54896
|
+
const { code } = await authorizeWithSession(fetchFn, {
|
|
54897
|
+
authorizationEndpoint: endpoints.authorizationEndpoint,
|
|
54898
|
+
sessionToken,
|
|
54899
|
+
clientId,
|
|
54900
|
+
scope,
|
|
54901
|
+
state,
|
|
54902
|
+
challenge: pkce.challenge,
|
|
54903
|
+
method: pkce.method
|
|
54904
|
+
});
|
|
54905
|
+
const tokens = await exchangeAuthorizationCode(fetchFn, endpoints.tokenEndpoint, {
|
|
54906
|
+
code,
|
|
54907
|
+
codeVerifier: pkce.verifier,
|
|
54908
|
+
redirectUri: BRIDGE_REDIRECT_URI,
|
|
54909
|
+
clientId
|
|
54910
|
+
});
|
|
54911
|
+
try {
|
|
54912
|
+
const consoleAuthBase = deriveAuthBase(grant.verificationUri);
|
|
54913
|
+
if (consoleAuthBase) {
|
|
54914
|
+
await fetchFn(`${consoleAuthBase}/sign-out`, {
|
|
54915
|
+
method: "POST",
|
|
54916
|
+
headers: { authorization: `Bearer ${sessionToken}`, "content-type": "application/json" },
|
|
54917
|
+
body: "{}"
|
|
54918
|
+
});
|
|
54919
|
+
}
|
|
54920
|
+
} catch {
|
|
54921
|
+
}
|
|
54922
|
+
return { tokens, tokenEndpoint: endpoints.tokenEndpoint, clientId };
|
|
54923
|
+
}
|
|
54924
|
+
function deriveAuthBase(verificationUri) {
|
|
54925
|
+
try {
|
|
54926
|
+
const origin = new URL(verificationUri).origin;
|
|
54927
|
+
return `${origin}/newapp/api/auth`;
|
|
54928
|
+
} catch {
|
|
54929
|
+
return null;
|
|
54930
|
+
}
|
|
54931
|
+
}
|
|
54932
|
+
function openInBrowser(url2) {
|
|
54933
|
+
try {
|
|
54934
|
+
void import("node:child_process").then(({ spawn: spawn4 }) => {
|
|
54935
|
+
const [cmd2, args2] = process.platform === "win32" ? ["cmd", ["/c", "start", "", url2]] : process.platform === "darwin" ? ["open", [url2]] : ["xdg-open", [url2]];
|
|
54936
|
+
spawn4(cmd2, args2, { detached: true, stdio: "ignore" }).unref();
|
|
54937
|
+
});
|
|
54938
|
+
} catch {
|
|
54939
|
+
}
|
|
54940
|
+
}
|
|
54941
|
+
|
|
54748
54942
|
// src/cli.ts
|
|
54749
54943
|
init_paths();
|
|
54750
54944
|
|
|
@@ -54997,7 +55191,9 @@ Commands:
|
|
|
54997
55191
|
Flags: --port N (default 7891)
|
|
54998
55192
|
mcp Run the MCP stdio server \u2014 the agent's full errata tool
|
|
54999
55193
|
surface (navigation, problems, claims, burst, health)
|
|
55000
|
-
login Sign in with the cloud
|
|
55194
|
+
login Sign in with the cloud. Default: short verification URL +
|
|
55195
|
+
typeable code (device-bridged OAuth). Flags: --browser
|
|
55196
|
+
(loopback code flow) \xB7 --token <key> \xB7 --device (legacy v1)
|
|
55001
55197
|
logout Clear local cloud credentials
|
|
55002
55198
|
link Corrective project link (ambient linking covers the happy path).
|
|
55003
55199
|
Flags: --project <id> adopt an existing project (fork\u2192upstream);
|
|
@@ -55353,6 +55549,7 @@ function parseFlags(args2) {
|
|
|
55353
55549
|
else if (a.startsWith("--token=")) out2.token = a.slice("--token=".length);
|
|
55354
55550
|
else if (a === "--oauth") out2.oauth = true;
|
|
55355
55551
|
else if (a === "--device") out2.device = true;
|
|
55552
|
+
else if (a === "--browser") out2.browser = true;
|
|
55356
55553
|
else if (!a.startsWith("--")) out2._.push(a);
|
|
55357
55554
|
}
|
|
55358
55555
|
return out2;
|
|
@@ -55434,7 +55631,7 @@ async function warnIfApprovalUnreachable(url2) {
|
|
|
55434
55631
|
} catch {
|
|
55435
55632
|
}
|
|
55436
55633
|
}
|
|
55437
|
-
async function cmdLoginOAuth(cfg) {
|
|
55634
|
+
async function cmdLoginOAuth(cfg, useBrowserLoopback = false) {
|
|
55438
55635
|
const inspection = await inspectCloudEndpoint(cfg.cloudUrl);
|
|
55439
55636
|
try {
|
|
55440
55637
|
assertCloudEndpointAllowed(cfg.cloudUrl, inspection);
|
|
@@ -55446,10 +55643,10 @@ async function cmdLoginOAuth(cfg) {
|
|
|
55446
55643
|
console.log(`signing in via OAuth at ${cfg.cloudUrl} \u2026`);
|
|
55447
55644
|
let result;
|
|
55448
55645
|
try {
|
|
55449
|
-
result = await loginOAuthLoopback({ cloudUrl: cfg.cloudUrl, timeoutMs: 5 * 6e4 });
|
|
55646
|
+
result = useBrowserLoopback ? await loginOAuthLoopback({ cloudUrl: cfg.cloudUrl, timeoutMs: 5 * 6e4 }) : await loginViaDeviceBridge({ cloudUrl: cfg.cloudUrl, timeoutMs: 15 * 6e4, open: openInBrowser });
|
|
55450
55647
|
} catch (err2) {
|
|
55451
55648
|
console.error(`oauth login failed: ${err2 instanceof Error ? err2.message : err2}`);
|
|
55452
|
-
console.error(`
|
|
55649
|
+
if (!useBrowserLoopback) console.error(` try the browser flow: errata login --browser`);
|
|
55453
55650
|
console.error(` or paste a key: errata login --token <key>`);
|
|
55454
55651
|
process.exitCode = 1;
|
|
55455
55652
|
return;
|
|
@@ -55580,7 +55777,7 @@ async function cmdLogin() {
|
|
|
55580
55777
|
return;
|
|
55581
55778
|
}
|
|
55582
55779
|
if (shouldUseOAuthLogin(flags2)) {
|
|
55583
|
-
await cmdLoginOAuth(cfg);
|
|
55780
|
+
await cmdLoginOAuth(cfg, flags2.browser ?? false);
|
|
55584
55781
|
return;
|
|
55585
55782
|
}
|
|
55586
55783
|
const c = new CloudClient({ baseUrl: cfg.cloudUrl });
|