halfcycle 0.3.35 → 0.3.37
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/.claude-plugin/plugin.json +1 -1
- package/README.md +16 -1
- package/bin/bin.bundle.mjs +471 -61
- package/dist/banner-facts.d.ts +6 -2
- package/dist/banner-facts.d.ts.map +1 -1
- package/dist/bin.js +435 -430
- package/dist/bin.js.map +3 -3
- package/dist/claude-version-probe.d.ts +44 -0
- package/dist/claude-version-probe.d.ts.map +1 -0
- package/dist/confirm-identity.d.ts +7 -0
- package/dist/confirm-identity.d.ts.map +1 -1
- package/dist/device-signin.d.ts +93 -17
- package/dist/device-signin.d.ts.map +1 -1
- package/dist/engagement-credential.d.ts +39 -27
- package/dist/engagement-credential.d.ts.map +1 -1
- package/dist/index.js +189 -290
- package/dist/index.js.map +3 -3
- package/dist/install-manifest.d.ts +32 -4
- package/dist/install-manifest.d.ts.map +1 -1
- package/dist/install.d.ts +53 -23
- package/dist/install.d.ts.map +1 -1
- package/dist/loopback-signin.d.ts +8 -2
- package/dist/loopback-signin.d.ts.map +1 -1
- package/dist/open-phase.d.ts +2 -1
- package/dist/open-phase.d.ts.map +1 -1
- package/dist/probe-mcp.d.ts +39 -7
- package/dist/probe-mcp.d.ts.map +1 -1
- package/dist/uninstall.d.ts +1 -1
- package/dist/uninstall.d.ts.map +1 -1
- package/package.json +1 -1
package/bin/bin.bundle.mjs
CHANGED
|
@@ -14548,7 +14548,10 @@ var matcherSchema = external_exports.discriminatedUnion("kind", [
|
|
|
14548
14548
|
var HALFCYCLE_DIR_NAME = ".halfcycle";
|
|
14549
14549
|
var ENGAGEMENTS_DIR_NAME = "engagements";
|
|
14550
14550
|
var ENGAGEMENT_ENV_FILENAME = "env";
|
|
14551
|
+
var NOT_THIS_ACCOUNT_MARKER_FILENAME = "not-this-account";
|
|
14551
14552
|
var ACCOUNT_STORE_FILENAME = "account.json";
|
|
14553
|
+
var BUNDLE_PIN_REL = ".halfcycle/bundle.json";
|
|
14554
|
+
var PIN_ENGAGEMENT_ID_FIELD = "engagementId";
|
|
14552
14555
|
var ENGAGEMENT_ID_PATTERN = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$";
|
|
14553
14556
|
var ENGAGEMENT_ID_SHAPE = new RegExp(ENGAGEMENT_ID_PATTERN);
|
|
14554
14557
|
function isEngagementId(value) {
|
|
@@ -14746,6 +14749,10 @@ function looksLikeSecret(text) {
|
|
|
14746
14749
|
return false;
|
|
14747
14750
|
}
|
|
14748
14751
|
|
|
14752
|
+
// ../core/dist/mcp-registration.js
|
|
14753
|
+
var MCP_REGISTRATION_REL = ".mcp.json";
|
|
14754
|
+
var MCP_SERVER_KEY = "halfcycle";
|
|
14755
|
+
|
|
14749
14756
|
// dist/control-origin.js
|
|
14750
14757
|
var DEFAULT_CONTROL_ORIGIN = "https://control.halfcycle.ai";
|
|
14751
14758
|
function resolveControlOrigin(env) {
|
|
@@ -14761,10 +14768,10 @@ var EXCERPT_DEFAULTS = {
|
|
|
14761
14768
|
maxExcerptsPerFile: 5,
|
|
14762
14769
|
maxExcerptsPerChangeSet: 20
|
|
14763
14770
|
};
|
|
14764
|
-
function readConfig() {
|
|
14765
|
-
const guardServiceUrl = readGuardOrigin().origin;
|
|
14766
|
-
const guardServiceToken =
|
|
14767
|
-
const guardEngagementId =
|
|
14771
|
+
function readConfig(env = process.env) {
|
|
14772
|
+
const guardServiceUrl = readGuardOrigin(env).origin;
|
|
14773
|
+
const guardServiceToken = env["GUARD_SERVICE_TOKEN"];
|
|
14774
|
+
const guardEngagementId = env["GUARD_ENGAGEMENT_ID"];
|
|
14768
14775
|
const missing = [];
|
|
14769
14776
|
if (!guardServiceToken)
|
|
14770
14777
|
missing.push("GUARD_SERVICE_TOKEN");
|
|
@@ -14786,8 +14793,8 @@ function readNeverConfiguredReason() {
|
|
|
14786
14793
|
function isHalfcycleOwnCi() {
|
|
14787
14794
|
return process.env["HALFCYCLE_INTERNAL_CI"] === "1";
|
|
14788
14795
|
}
|
|
14789
|
-
function readGuardOrigin() {
|
|
14790
|
-
return resolveGuardOrigin(
|
|
14796
|
+
function readGuardOrigin(env = process.env) {
|
|
14797
|
+
return resolveGuardOrigin(env);
|
|
14791
14798
|
}
|
|
14792
14799
|
function readActionsOidcEnv() {
|
|
14793
14800
|
const requestUrl = process.env["ACTIONS_ID_TOKEN_REQUEST_URL"]?.trim();
|
|
@@ -14796,11 +14803,19 @@ function readActionsOidcEnv() {
|
|
|
14796
14803
|
return void 0;
|
|
14797
14804
|
return { requestUrl, requestToken };
|
|
14798
14805
|
}
|
|
14799
|
-
function readControlOrigin() {
|
|
14800
|
-
return resolveControlOrigin(
|
|
14806
|
+
function readControlOrigin(env = process.env) {
|
|
14807
|
+
return resolveControlOrigin(env);
|
|
14808
|
+
}
|
|
14809
|
+
function readEnvFilePath(env = process.env) {
|
|
14810
|
+
const raw = env["HALFCYCLE_ENV_FILE"]?.trim();
|
|
14811
|
+
return raw ? raw : void 0;
|
|
14812
|
+
}
|
|
14813
|
+
function readEnvironmentToken() {
|
|
14814
|
+
const raw = process.env["HALFCYCLE_TOKEN"];
|
|
14815
|
+
return raw?.trim() ? raw : void 0;
|
|
14801
14816
|
}
|
|
14802
|
-
function
|
|
14803
|
-
const raw = process.env["
|
|
14817
|
+
function readMcpServerUrl() {
|
|
14818
|
+
const raw = process.env["CLAUDE_CODE_MCP_SERVER_URL"];
|
|
14804
14819
|
return raw ? raw : void 0;
|
|
14805
14820
|
}
|
|
14806
14821
|
function readExcerptConfig() {
|
|
@@ -15281,6 +15296,25 @@ var ciTokenExchangeResponseSchema = external_exports.object({
|
|
|
15281
15296
|
expiresAt: utcIso8601,
|
|
15282
15297
|
engagementId: external_exports.string()
|
|
15283
15298
|
}).strict();
|
|
15299
|
+
var NO_CREDENTIAL_PLACEHOLDER = "halfcycle:no-credential";
|
|
15300
|
+
var NOT_THIS_ACCOUNT_PLACEHOLDER = "halfcycle:not-this-account";
|
|
15301
|
+
var UPDATE_CLAUDE_CODE_PLACEHOLDER = "halfcycle:update-claude-code";
|
|
15302
|
+
var CREDENTIAL_PLACEHOLDERS = [
|
|
15303
|
+
NO_CREDENTIAL_PLACEHOLDER,
|
|
15304
|
+
NOT_THIS_ACCOUNT_PLACEHOLDER,
|
|
15305
|
+
UPDATE_CLAUDE_CODE_PLACEHOLDER
|
|
15306
|
+
];
|
|
15307
|
+
var SWITCH_ACCOUNT_ANSWER = 'answer "s" when asked';
|
|
15308
|
+
var NO_CREDENTIAL_SENTENCE = 'Halfcycle is not connected: this machine has no working Halfcycle credential for this project. Run "npx halfcycle" in the project folder to sign in again.';
|
|
15309
|
+
var NOT_THIS_ACCOUNT_SENTENCE = `Halfcycle is not connected: this project belongs to another Halfcycle account. To switch, run "npx halfcycle" in this project and ${SWITCH_ACCOUNT_ANSWER}.`;
|
|
15310
|
+
var UPDATE_CLAUDE_CODE_SENTENCE = "Halfcycle is not connected: this Claude Code version does not name the server it connects to, so no credential was sent. Update Claude Code, then reopen.";
|
|
15311
|
+
var CANNOT_REACH_SENTENCE = "Halfcycle is not connected: its server could not be reached, so its tools are unavailable in this session. Check your network, then reopen the project.";
|
|
15312
|
+
var SERVICE_PROBLEM_SENTENCE = "Halfcycle is not connected: its service had a problem just now, so nothing in this session is being checked. Reopen the project to try again.";
|
|
15313
|
+
var PLACEHOLDER_SENTENCES = Object.freeze({
|
|
15314
|
+
[NO_CREDENTIAL_PLACEHOLDER]: NO_CREDENTIAL_SENTENCE,
|
|
15315
|
+
[NOT_THIS_ACCOUNT_PLACEHOLDER]: NOT_THIS_ACCOUNT_SENTENCE,
|
|
15316
|
+
[UPDATE_CLAUDE_CODE_PLACEHOLDER]: UPDATE_CLAUDE_CODE_SENTENCE
|
|
15317
|
+
});
|
|
15284
15318
|
|
|
15285
15319
|
// dist/hook-protocol.js
|
|
15286
15320
|
import { writeSync } from "node:fs";
|
|
@@ -15289,7 +15323,7 @@ function emitBlock(reason) {
|
|
|
15289
15323
|
process.exit(0);
|
|
15290
15324
|
}
|
|
15291
15325
|
function nonBlockingReply(event, text) {
|
|
15292
|
-
if (event === "PostToolUse") {
|
|
15326
|
+
if (event === "PostToolUse" || event === "SessionStart") {
|
|
15293
15327
|
return JSON.stringify({
|
|
15294
15328
|
systemMessage: text,
|
|
15295
15329
|
hookSpecificOutput: { hookEventName: event, additionalContext: text }
|
|
@@ -15334,11 +15368,15 @@ This is not a service outage: the guard service answered. The suspect is this re
|
|
|
15334
15368
|
}
|
|
15335
15369
|
var UNCONFIGURED_EXIT_TOOL_USE = 2;
|
|
15336
15370
|
var UNCONFIGURED_EXIT_STOP_FAMILY = 1;
|
|
15337
|
-
function emitNeverConfigured(
|
|
15338
|
-
writeSync(2, neverConfiguredReport(
|
|
15371
|
+
function emitNeverConfigured(exitCode, reason) {
|
|
15372
|
+
writeSync(2, neverConfiguredReport(reason));
|
|
15339
15373
|
process.exitCode = exitCode;
|
|
15340
15374
|
}
|
|
15341
|
-
var
|
|
15375
|
+
var SIGN_IN_COMMAND = '"npx halfcycle"';
|
|
15376
|
+
var HOOK_REMEDY = `Run ${SIGN_IN_COMMAND} in the project folder to sign in.`;
|
|
15377
|
+
function namesTheSignInCommand(text) {
|
|
15378
|
+
return text.includes(SIGN_IN_COMMAND);
|
|
15379
|
+
}
|
|
15342
15380
|
var PERMISSIONS_BLOCK = "\n permissions:\n contents: read\n id-token: write\n\n";
|
|
15343
15381
|
var BOTH_LINES_SENTENCE = "BOTH LINES. Declaring any permission replaces the defaults rather than adding to them, so a block naming only the identity token takes read access away from the checkout step and a private repository stops checking out before this check is reached.";
|
|
15344
15382
|
var CI_CANNOT_PROVE_PROJECT = "has no way to prove which project it belongs to";
|
|
@@ -15346,17 +15384,17 @@ var CI_REMEDY_LEAD = "Add this to the workflow \u2014 at the top of the file, or
|
|
|
15346
15384
|
var CI_OLDER_SETUP = "The older setup, GUARD_SERVICE_TOKEN and GUARD_ENGAGEMENT_ID supplied as repository secrets, still works unchanged, and a job that has both needs neither line.";
|
|
15347
15385
|
var CI_REMEDY = `This job cannot vouch for anything, because it ${CI_CANNOT_PROVE_PROJECT}. ${CI_REMEDY_LEAD} ${CI_OLDER_SETUP}`;
|
|
15348
15386
|
var CI_NOT_SUPPORTED_MESSAGE = "[Halfcycle CI] This is Halfcycle-internal and is not supported in your CI.\n";
|
|
15349
|
-
var NEVER_CONFIGURED_HEADLINE = "
|
|
15387
|
+
var NEVER_CONFIGURED_HEADLINE = "Halfcycle guidance and support won't be available";
|
|
15350
15388
|
var NOT_A_SERVICE_OUTAGE = "This is not a service outage.";
|
|
15351
|
-
|
|
15352
|
-
|
|
15353
|
-
|
|
15354
|
-
${NOT_A_SERVICE_OUTAGE}
|
|
15389
|
+
var NO_SIGN_IN_HERE = `${NEVER_CONFIGURED_HEADLINE} in this project on this machine: there is no Halfcycle sign-in for this project here`;
|
|
15390
|
+
function neverConfiguredMessage(remedy, label = "[Halfcycle]") {
|
|
15391
|
+
return `${label} ${NO_SIGN_IN_HERE}. Edits made in this session won't be checked.
|
|
15392
|
+
${NOT_A_SERVICE_OUTAGE}
|
|
15355
15393
|
${remedy}
|
|
15356
15394
|
`;
|
|
15357
15395
|
}
|
|
15358
15396
|
function ciNeverConfiguredMessage() {
|
|
15359
|
-
return `[Halfcycle CI] ${NEVER_CONFIGURED_HEADLINE}
|
|
15397
|
+
return `[Halfcycle CI] ${NEVER_CONFIGURED_HEADLINE} in this job: it ${CI_CANNOT_PROVE_PROJECT}, so it checked nothing. ${CI_REMEDY_LEAD}
|
|
15360
15398
|
${NOT_A_SERVICE_OUTAGE} Nothing was sent: this job holds no credential and was not permitted to ask GitHub for a signed identity token. It speaks for this job on this change only \u2014 other jobs and earlier changes it did not see.
|
|
15361
15399
|
${CI_OLDER_SETUP}
|
|
15362
15400
|
`;
|
|
@@ -15365,10 +15403,10 @@ function notThisAccountReport(label) {
|
|
|
15365
15403
|
return `${label} This project belongs to another Halfcycle account. Halfcycle is Solo today \u2014 one person works on a project at a time \u2014 so no credential exists for this machine and none will be issued. Nothing here needs fixing; this copy simply is not checked. Ask the project's owner for view access, or \u2014 if this account is wrong \u2014 sign in as the one that owns the project and run "npx halfcycle" again.
|
|
15366
15404
|
`;
|
|
15367
15405
|
}
|
|
15368
|
-
function neverConfiguredReport(
|
|
15406
|
+
function neverConfiguredReport(reason, label = "[Halfcycle]") {
|
|
15369
15407
|
switch (reason) {
|
|
15370
15408
|
case "no-credential-yet":
|
|
15371
|
-
return neverConfiguredMessage(
|
|
15409
|
+
return neverConfiguredMessage(HOOK_REMEDY, label);
|
|
15372
15410
|
case "not-this-account":
|
|
15373
15411
|
return notThisAccountReport(label);
|
|
15374
15412
|
}
|
|
@@ -15424,6 +15462,21 @@ function credentialRenewalFailedMessage(remedy, label = "[Halfcycle]") {
|
|
|
15424
15462
|
${remedy}
|
|
15425
15463
|
`;
|
|
15426
15464
|
}
|
|
15465
|
+
function noCoverageStatement(reason, remedySaid = false) {
|
|
15466
|
+
if (reason === "not-this-account") {
|
|
15467
|
+
return [
|
|
15468
|
+
"[halfcycle] This project belongs to another Halfcycle account, so nothing here is checked.",
|
|
15469
|
+
"[halfcycle] Your work carries on exactly as it would without Halfcycle \u2014 no edit is",
|
|
15470
|
+
"[halfcycle] evaluated, and nothing you do is reported as having passed.",
|
|
15471
|
+
"[halfcycle] Halfcycle is Solo today \u2014 one developer per project \u2014 with Team plans coming.",
|
|
15472
|
+
"[halfcycle] Ask the project's owner for view access.",
|
|
15473
|
+
"[halfcycle] If this account is wrong, sign in as the one that owns the project and run",
|
|
15474
|
+
'[halfcycle] "npx halfcycle" again.'
|
|
15475
|
+
].join("\n");
|
|
15476
|
+
}
|
|
15477
|
+
const statement = `${NO_SIGN_IN_HERE}, so edits made in this session won't be checked.`;
|
|
15478
|
+
return remedySaid ? statement : `${statement} ${HOOK_REMEDY}`;
|
|
15479
|
+
}
|
|
15427
15480
|
function notRunReport(notRun, label = "[Halfcycle]") {
|
|
15428
15481
|
if (!notRun || notRun.length === 0)
|
|
15429
15482
|
return "";
|
|
@@ -17186,7 +17239,7 @@ function cleanRunSummary(verdict, scope) {
|
|
|
17186
17239
|
}
|
|
17187
17240
|
|
|
17188
17241
|
// dist/credential-store.js
|
|
17189
|
-
import { chmodSync, readFileSync as readFileSync5, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2, realpathSync } from "node:fs";
|
|
17242
|
+
import { chmodSync, existsSync as existsSync2, readFileSync as readFileSync5, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2, realpathSync } from "node:fs";
|
|
17190
17243
|
import { homedir as homedir2, platform } from "node:os";
|
|
17191
17244
|
import { dirname as dirname3, join as join5 } from "node:path";
|
|
17192
17245
|
var REFRESHED_TOKEN_KEYS = ["HALFCYCLE_TOKEN", "GUARD_SERVICE_TOKEN"];
|
|
@@ -17194,6 +17247,9 @@ var TOKEN_READ_KEY = "GUARD_SERVICE_TOKEN";
|
|
|
17194
17247
|
function composeEngagementEnvPath(engagementId, home) {
|
|
17195
17248
|
return join5(home ?? homedir2(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, ENGAGEMENT_ENV_FILENAME);
|
|
17196
17249
|
}
|
|
17250
|
+
function hasNotThisAccountMarker(engagementId, home) {
|
|
17251
|
+
return existsSync2(join5(home ?? homedir2(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, NOT_THIS_ACCOUNT_MARKER_FILENAME));
|
|
17252
|
+
}
|
|
17197
17253
|
function readAccountCredential(controlOrigin, home) {
|
|
17198
17254
|
let raw;
|
|
17199
17255
|
try {
|
|
@@ -17208,13 +17264,20 @@ function readAccountCredential(controlOrigin, home) {
|
|
|
17208
17264
|
return entry.credential;
|
|
17209
17265
|
}
|
|
17210
17266
|
function readStoredEngagementToken(envFilePath) {
|
|
17267
|
+
const values = readEngagementStore(envFilePath);
|
|
17268
|
+
return values === void 0 ? void 0 : storedEngagementToken(values);
|
|
17269
|
+
}
|
|
17270
|
+
function readEngagementStore(envFilePath) {
|
|
17211
17271
|
let raw;
|
|
17212
17272
|
try {
|
|
17213
17273
|
raw = readFileSync5(envFilePath, "utf-8");
|
|
17214
17274
|
} catch {
|
|
17215
17275
|
return void 0;
|
|
17216
17276
|
}
|
|
17217
|
-
|
|
17277
|
+
return parseEnvText(raw);
|
|
17278
|
+
}
|
|
17279
|
+
function storedEngagementToken(values) {
|
|
17280
|
+
const value = values[TOKEN_READ_KEY];
|
|
17218
17281
|
return value && value.trim() !== "" ? value : void 0;
|
|
17219
17282
|
}
|
|
17220
17283
|
function writeRefreshedToken(opts) {
|
|
@@ -17263,6 +17326,7 @@ function writeRefreshedToken(opts) {
|
|
|
17263
17326
|
|
|
17264
17327
|
// dist/credential-refresh.js
|
|
17265
17328
|
var CONTROL_TIMEOUT_MS = 5e3;
|
|
17329
|
+
var CONNECTION_CHECK_DEADLINE_MS = 3e3;
|
|
17266
17330
|
var UNAUTHORIZED = 401;
|
|
17267
17331
|
async function probeAccount(controlOrigin, credential, fetchFn = globalThis.fetch) {
|
|
17268
17332
|
const controller = new AbortController();
|
|
@@ -17325,14 +17389,14 @@ var UNREACHABLE_REMEDY = "The Halfcycle service could not be reached to renew th
|
|
|
17325
17389
|
var UNREADABLE_REFUSAL_REMEDY = "The Halfcycle service declined to renew this credential and gave no reason this version could read.";
|
|
17326
17390
|
var UNREADABLE_MINT_REMEDY = 'The Halfcycle service answered with a credential this version could not read. Update the installed Halfcycle runner: run "npx halfcycle" in this repository.';
|
|
17327
17391
|
async function refreshEngagementCredential(opts) {
|
|
17328
|
-
const envFilePath = readEnvFilePath();
|
|
17392
|
+
const envFilePath = readEnvFilePath(opts.source);
|
|
17329
17393
|
if (!envFilePath)
|
|
17330
17394
|
return { ok: false, remedy: HOOK_REMEDY };
|
|
17331
17395
|
const stored = readStoredEngagementToken(envFilePath);
|
|
17332
17396
|
if (stored !== void 0 && stored !== opts.refusedToken) {
|
|
17333
17397
|
return { ok: true, token: stored, source: "disk" };
|
|
17334
17398
|
}
|
|
17335
|
-
const controlOrigin = readControlOrigin().origin;
|
|
17399
|
+
const controlOrigin = readControlOrigin(opts.source).origin;
|
|
17336
17400
|
const accountCredential = readAccountCredential(controlOrigin, opts.home);
|
|
17337
17401
|
if (!accountCredential)
|
|
17338
17402
|
return { ok: false, remedy: HOOK_REMEDY };
|
|
@@ -17375,7 +17439,7 @@ async function evaluateWithRefresh(opts) {
|
|
|
17375
17439
|
}
|
|
17376
17440
|
|
|
17377
17441
|
// dist/seat-configuration.js
|
|
17378
|
-
import { existsSync as
|
|
17442
|
+
import { existsSync as existsSync3, readFileSync as readFileSync6, realpathSync as realpathSync2 } from "node:fs";
|
|
17379
17443
|
import { homedir as homedir3 } from "node:os";
|
|
17380
17444
|
import { join as join6, relative as relative3, resolve } from "node:path";
|
|
17381
17445
|
function gatherSeatConfiguration(repoRoot, read = readSeatConfiguration) {
|
|
@@ -17484,7 +17548,7 @@ function readPathStates(projectDir, repoRoot) {
|
|
|
17484
17548
|
const status = getWiringStatus(HALFCYCLE_HOOK_WIRING_PATHS, projectDir);
|
|
17485
17549
|
if (!status.ok)
|
|
17486
17550
|
return unknown2();
|
|
17487
|
-
const mapped = mapWiringStatus(status.value, prefix, HALFCYCLE_HOOK_WIRING_PATHS, (rel) =>
|
|
17551
|
+
const mapped = mapWiringStatus(status.value, prefix, HALFCYCLE_HOOK_WIRING_PATHS, (rel) => existsSync3(join6(projectDir, rel)));
|
|
17488
17552
|
return mapped ?? unknown2();
|
|
17489
17553
|
}
|
|
17490
17554
|
function mapWiringStatus(output, prefix, paths, onDisk) {
|
|
@@ -17566,7 +17630,7 @@ async function evaluateEdit(reply, readSeat) {
|
|
|
17566
17630
|
const configResult = readConfig();
|
|
17567
17631
|
if (!configResult.config) {
|
|
17568
17632
|
await emitRunRecord({ runType: "hook", outcome: "unconfigured", changeSet });
|
|
17569
|
-
emitNeverConfigured(
|
|
17633
|
+
emitNeverConfigured(UNCONFIGURED_EXIT_TOOL_USE, readNeverConfiguredReason());
|
|
17570
17634
|
return;
|
|
17571
17635
|
}
|
|
17572
17636
|
const { guardServiceUrl, guardServiceToken, guardEngagementId } = configResult.config;
|
|
@@ -17656,17 +17720,17 @@ ${guardLines}${notRunBlock}`);
|
|
|
17656
17720
|
}
|
|
17657
17721
|
}
|
|
17658
17722
|
function readStdin() {
|
|
17659
|
-
return new Promise((
|
|
17723
|
+
return new Promise((resolve4, reject) => {
|
|
17660
17724
|
const chunks = [];
|
|
17661
17725
|
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
17662
|
-
process.stdin.on("end", () =>
|
|
17726
|
+
process.stdin.on("end", () => resolve4(Buffer.concat(chunks).toString("utf-8")));
|
|
17663
17727
|
process.stdin.on("error", reject);
|
|
17664
17728
|
});
|
|
17665
17729
|
}
|
|
17666
17730
|
|
|
17667
17731
|
// dist/hooks/marker.js
|
|
17668
17732
|
import { createHash } from "node:crypto";
|
|
17669
|
-
import { existsSync as
|
|
17733
|
+
import { existsSync as existsSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3, readdirSync as readdirSync3, statSync } from "node:fs";
|
|
17670
17734
|
import { tmpdir } from "node:os";
|
|
17671
17735
|
import { join as join7 } from "node:path";
|
|
17672
17736
|
var MARKER_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -17695,7 +17759,7 @@ function dedupePath(repoRoot, sessionId) {
|
|
|
17695
17759
|
}
|
|
17696
17760
|
function readMarker(repoRoot, sessionId) {
|
|
17697
17761
|
const path = markerPath(repoRoot, sessionId);
|
|
17698
|
-
if (!
|
|
17762
|
+
if (!existsSync4(path))
|
|
17699
17763
|
return null;
|
|
17700
17764
|
try {
|
|
17701
17765
|
return readFileSync7(path, "utf-8").trim() || null;
|
|
@@ -17706,7 +17770,7 @@ function readMarker(repoRoot, sessionId) {
|
|
|
17706
17770
|
function deleteMarker(repoRoot, sessionId) {
|
|
17707
17771
|
for (const path of [markerPath(repoRoot, sessionId), dedupePath(repoRoot, sessionId)]) {
|
|
17708
17772
|
try {
|
|
17709
|
-
if (
|
|
17773
|
+
if (existsSync4(path))
|
|
17710
17774
|
unlinkSync3(path);
|
|
17711
17775
|
} catch {
|
|
17712
17776
|
}
|
|
@@ -17714,7 +17778,7 @@ function deleteMarker(repoRoot, sessionId) {
|
|
|
17714
17778
|
}
|
|
17715
17779
|
function readDedupe(repoRoot, sessionId) {
|
|
17716
17780
|
const path = dedupePath(repoRoot, sessionId);
|
|
17717
|
-
if (!
|
|
17781
|
+
if (!existsSync4(path))
|
|
17718
17782
|
return null;
|
|
17719
17783
|
try {
|
|
17720
17784
|
const parsed = JSON.parse(readFileSync7(path, "utf-8"));
|
|
@@ -17861,7 +17925,7 @@ async function evaluateSession(input, kind, reply, readSeat) {
|
|
|
17861
17925
|
const configResult = readConfig();
|
|
17862
17926
|
if (!configResult.config) {
|
|
17863
17927
|
await emitRunRecord({ runType: "hook", outcome: "unconfigured", changeSet, diffBase });
|
|
17864
|
-
emitNeverConfigured(
|
|
17928
|
+
emitNeverConfigured(UNCONFIGURED_EXIT_STOP_FAMILY, readNeverConfiguredReason());
|
|
17865
17929
|
return;
|
|
17866
17930
|
}
|
|
17867
17931
|
const { guardServiceUrl, guardServiceToken, guardEngagementId } = configResult.config;
|
|
@@ -17954,6 +18018,7 @@ ${guardLines}${notRunBlock}`);
|
|
|
17954
18018
|
}
|
|
17955
18019
|
|
|
17956
18020
|
// dist/hooks/stdin.js
|
|
18021
|
+
var HOOK_STDIN_GRACE_MS = 2e3;
|
|
17957
18022
|
async function readHookStdin() {
|
|
17958
18023
|
try {
|
|
17959
18024
|
const raw = await readStdin2();
|
|
@@ -17968,9 +18033,9 @@ async function readHookStdin() {
|
|
|
17968
18033
|
}
|
|
17969
18034
|
}
|
|
17970
18035
|
function readStdin2() {
|
|
17971
|
-
return new Promise((
|
|
18036
|
+
return new Promise((resolve4) => {
|
|
17972
18037
|
if (process.stdin.isTTY) {
|
|
17973
|
-
|
|
18038
|
+
resolve4("");
|
|
17974
18039
|
return;
|
|
17975
18040
|
}
|
|
17976
18041
|
const chunks = [];
|
|
@@ -17980,9 +18045,9 @@ function readStdin2() {
|
|
|
17980
18045
|
return;
|
|
17981
18046
|
settled = true;
|
|
17982
18047
|
clearTimeout(timer);
|
|
17983
|
-
|
|
18048
|
+
resolve4(Buffer.concat(chunks).toString("utf-8"));
|
|
17984
18049
|
};
|
|
17985
|
-
const timer = setTimeout(done,
|
|
18050
|
+
const timer = setTimeout(done, HOOK_STDIN_GRACE_MS);
|
|
17986
18051
|
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
17987
18052
|
process.stdin.on("end", done);
|
|
17988
18053
|
process.stdin.on("error", done);
|
|
@@ -18002,14 +18067,14 @@ async function runStop() {
|
|
|
18002
18067
|
}
|
|
18003
18068
|
|
|
18004
18069
|
// dist/resolve-engagement.js
|
|
18005
|
-
import { existsSync as
|
|
18070
|
+
import { existsSync as existsSync5, readFileSync as readFileSync8, realpathSync as realpathSync3 } from "node:fs";
|
|
18006
18071
|
import { join as join8, sep as sep2 } from "node:path";
|
|
18007
18072
|
import { homedir as homedir4 } from "node:os";
|
|
18008
18073
|
function defaultRegistryPath() {
|
|
18009
18074
|
return join8(homedir4(), ".halfcycle", "registry.json");
|
|
18010
18075
|
}
|
|
18011
18076
|
function readRegistry(registryPath = defaultRegistryPath()) {
|
|
18012
|
-
if (!
|
|
18077
|
+
if (!existsSync5(registryPath))
|
|
18013
18078
|
return {};
|
|
18014
18079
|
try {
|
|
18015
18080
|
const parsed = JSON.parse(readFileSync8(registryPath, "utf-8"));
|
|
@@ -18059,41 +18124,380 @@ function runResolveEngagement(cwd, registryPath) {
|
|
|
18059
18124
|
return 0;
|
|
18060
18125
|
}
|
|
18061
18126
|
|
|
18127
|
+
// dist/refresh-credential.js
|
|
18128
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
18129
|
+
import { resolve as resolve3 } from "node:path";
|
|
18130
|
+
|
|
18131
|
+
// dist/mcp-headers.js
|
|
18132
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
18133
|
+
import { homedir as homedir5 } from "node:os";
|
|
18134
|
+
import { dirname as dirname4, resolve as resolve2 } from "node:path";
|
|
18135
|
+
function defaultWriteErr(text) {
|
|
18136
|
+
process.stderr.write(text);
|
|
18137
|
+
}
|
|
18138
|
+
function headerLine(bearer) {
|
|
18139
|
+
return `${JSON.stringify({ Authorization: `Bearer ${bearer}` })}
|
|
18140
|
+
`;
|
|
18141
|
+
}
|
|
18142
|
+
function projectRootFromBinary(binaryPath) {
|
|
18143
|
+
const depth = VENDORED_BIN_REL.split("/").length - 1;
|
|
18144
|
+
return resolve2(dirname4(binaryPath), ...Array(depth).fill(".."));
|
|
18145
|
+
}
|
|
18146
|
+
function readPinnedId(root) {
|
|
18147
|
+
let raw;
|
|
18148
|
+
try {
|
|
18149
|
+
raw = readFileSync9(resolve2(root, BUNDLE_PIN_REL), "utf-8");
|
|
18150
|
+
} catch {
|
|
18151
|
+
return { problem: "no-pin" };
|
|
18152
|
+
}
|
|
18153
|
+
let pin;
|
|
18154
|
+
try {
|
|
18155
|
+
pin = JSON.parse(raw);
|
|
18156
|
+
} catch {
|
|
18157
|
+
return { problem: "no-id" };
|
|
18158
|
+
}
|
|
18159
|
+
const id = typeof pin === "object" && pin !== null ? pin[PIN_ENGAGEMENT_ID_FIELD] : void 0;
|
|
18160
|
+
return typeof id === "string" && id !== "" ? { id } : { problem: "no-id" };
|
|
18161
|
+
}
|
|
18162
|
+
function resolveStore(root, home) {
|
|
18163
|
+
const pinned = readPinnedId(root);
|
|
18164
|
+
if ("problem" in pinned)
|
|
18165
|
+
return { ok: false, problem: pinned.problem };
|
|
18166
|
+
if (!isEngagementId(pinned.id))
|
|
18167
|
+
return { ok: false, problem: "bad-id" };
|
|
18168
|
+
const notThisAccount = hasNotThisAccountMarker(pinned.id, home);
|
|
18169
|
+
const path = composeEngagementEnvPath(pinned.id, home);
|
|
18170
|
+
const values = readEngagementStore(path);
|
|
18171
|
+
if (values === void 0) {
|
|
18172
|
+
return { ok: false, problem: notThisAccount ? "not-this-account" : "no-credential", engagementId: pinned.id };
|
|
18173
|
+
}
|
|
18174
|
+
return { ok: true, path, values };
|
|
18175
|
+
}
|
|
18176
|
+
function urlOrigin(url2) {
|
|
18177
|
+
if (/[\s\u0000-\u001f\u007f-\u009f]/u.test(url2))
|
|
18178
|
+
return void 0;
|
|
18179
|
+
const match = /^([A-Za-z][A-Za-z0-9+.-]*:\/\/[^/?#\\]*)/.exec(url2);
|
|
18180
|
+
return match ? match[1].toLowerCase() : void 0;
|
|
18181
|
+
}
|
|
18182
|
+
function shownUrl(url2) {
|
|
18183
|
+
return url2.replace(/[^\x21-\x7e]/g, "").slice(0, 200);
|
|
18184
|
+
}
|
|
18185
|
+
function sentences(lines) {
|
|
18186
|
+
return lines.map((line) => `halfcycle: ${line}
|
|
18187
|
+
`).join("");
|
|
18188
|
+
}
|
|
18189
|
+
function badIdSentence(root) {
|
|
18190
|
+
return sentences([
|
|
18191
|
+
`${resolve2(root, BUNDLE_PIN_REL)} names an engagement id that is not a Halfcycle project id,`,
|
|
18192
|
+
'so no credential was read. Delete that file and run "npx halfcycle" again: this repository',
|
|
18193
|
+
"is then set up as a new Halfcycle project."
|
|
18194
|
+
]);
|
|
18195
|
+
}
|
|
18196
|
+
function storeProblemSentence(problem, root, home, engagementId) {
|
|
18197
|
+
switch (problem) {
|
|
18198
|
+
case "no-pin":
|
|
18199
|
+
return sentences([
|
|
18200
|
+
`no ${BUNDLE_PIN_REL} in ${root}, so this repository has no Halfcycle engagement.`,
|
|
18201
|
+
'run "npx halfcycle" here to install one.'
|
|
18202
|
+
]);
|
|
18203
|
+
case "no-id":
|
|
18204
|
+
return sentences([
|
|
18205
|
+
`${resolve2(root, BUNDLE_PIN_REL)} names no engagement id.`,
|
|
18206
|
+
'run "npx halfcycle" here to rewrite it.'
|
|
18207
|
+
]);
|
|
18208
|
+
case "bad-id":
|
|
18209
|
+
return badIdSentence(root);
|
|
18210
|
+
case "not-this-account":
|
|
18211
|
+
return sentences([
|
|
18212
|
+
"this project belongs to another Halfcycle account, so this machine has no",
|
|
18213
|
+
"credential for it and the Halfcycle tools are not available here. Nothing is",
|
|
18214
|
+
"broken \u2014 ask the project's owner for view access, or sign in as the account",
|
|
18215
|
+
'that owns it and run "npx halfcycle" again.'
|
|
18216
|
+
]);
|
|
18217
|
+
case "no-credential":
|
|
18218
|
+
return sentences([
|
|
18219
|
+
`this machine holds no credential for engagement ${engagementId ?? ""}.`,
|
|
18220
|
+
`run "npx halfcycle" in this repository \u2014 it will sign you in through your browser if needed, and write the credential to ${resolve2(home, HALFCYCLE_DIR_NAME)}, outside this repository.`
|
|
18221
|
+
]);
|
|
18222
|
+
}
|
|
18223
|
+
}
|
|
18224
|
+
async function refreshWithinDeadline(source, opts, writeErr) {
|
|
18225
|
+
let timer;
|
|
18226
|
+
const deadline = new Promise((settle) => {
|
|
18227
|
+
timer = setTimeout(() => settle(void 0), opts.deadlineMs ?? CONTROL_TIMEOUT_MS);
|
|
18228
|
+
});
|
|
18229
|
+
const refresh = refreshCredentialToken({
|
|
18230
|
+
fetchFn: opts.fetchFn,
|
|
18231
|
+
home: opts.home,
|
|
18232
|
+
write: writeErr,
|
|
18233
|
+
source
|
|
18234
|
+
}).then((result) => result.token, () => void 0);
|
|
18235
|
+
try {
|
|
18236
|
+
return await Promise.race([refresh, deadline]);
|
|
18237
|
+
} finally {
|
|
18238
|
+
clearTimeout(timer);
|
|
18239
|
+
}
|
|
18240
|
+
}
|
|
18241
|
+
async function chooseBearer(opts) {
|
|
18242
|
+
const writeErr = opts.writeErr ?? defaultWriteErr;
|
|
18243
|
+
const home = opts.home ?? homedir5();
|
|
18244
|
+
const root = projectRootFromBinary(opts.binaryPath);
|
|
18245
|
+
const environmentToken = opts.environmentTokenFirst ? readEnvironmentToken() : void 0;
|
|
18246
|
+
const store = resolveStore(root, home);
|
|
18247
|
+
let storedToken;
|
|
18248
|
+
if (environmentToken === void 0) {
|
|
18249
|
+
if (!store.ok) {
|
|
18250
|
+
writeErr(storeProblemSentence(store.problem, root, home, store.engagementId));
|
|
18251
|
+
return store.problem === "not-this-account" ? NOT_THIS_ACCOUNT_PLACEHOLDER : NO_CREDENTIAL_PLACEHOLDER;
|
|
18252
|
+
}
|
|
18253
|
+
storedToken = storedEngagementToken(store.values);
|
|
18254
|
+
if (storedToken === void 0) {
|
|
18255
|
+
writeErr(sentences([
|
|
18256
|
+
`GUARD_SERVICE_TOKEN is absent or empty in ${store.path}.`,
|
|
18257
|
+
`run "npx halfcycle" in this repository to rewrite this machine's credential.`
|
|
18258
|
+
]));
|
|
18259
|
+
return NO_CREDENTIAL_PLACEHOLDER;
|
|
18260
|
+
}
|
|
18261
|
+
}
|
|
18262
|
+
const requested = opts.requestedUrl;
|
|
18263
|
+
const recorded = store.ok ? store.values["HALFCYCLE_MCP_URL"] : void 0;
|
|
18264
|
+
if (requested === void 0 && store.ok) {
|
|
18265
|
+
writeErr(sentences([
|
|
18266
|
+
"Claude Code did not say which server it is connecting to, so the Halfcycle credential was not sent.",
|
|
18267
|
+
"update Claude Code, then reconnect."
|
|
18268
|
+
]));
|
|
18269
|
+
return UPDATE_CLAUDE_CODE_PLACEHOLDER;
|
|
18270
|
+
}
|
|
18271
|
+
if (!recorded) {
|
|
18272
|
+
if (!store.ok && store.problem === "bad-id") {
|
|
18273
|
+
writeErr(badIdSentence(root));
|
|
18274
|
+
} else {
|
|
18275
|
+
writeErr(sentences([
|
|
18276
|
+
"this machine has no Halfcycle server address recorded for this repository, so the credential was not sent.",
|
|
18277
|
+
'run "npx halfcycle" in this repository on this machine to record it.'
|
|
18278
|
+
]));
|
|
18279
|
+
}
|
|
18280
|
+
return NO_CREDENTIAL_PLACEHOLDER;
|
|
18281
|
+
}
|
|
18282
|
+
const want = urlOrigin(recorded);
|
|
18283
|
+
const got = requested === void 0 ? void 0 : urlOrigin(requested);
|
|
18284
|
+
if (want === void 0 || got !== want) {
|
|
18285
|
+
writeErr(sentences([
|
|
18286
|
+
`.mcp.json asks for this repository's Halfcycle credential to be sent to ${shownUrl(requested ?? "")},`,
|
|
18287
|
+
`which is not this machine's Halfcycle server (${want ?? "none recorded"}). The credential was NOT sent.`,
|
|
18288
|
+
'if nobody meant to change .mcp.json, treat that change as suspect; "npx halfcycle" restores the entry.'
|
|
18289
|
+
]));
|
|
18290
|
+
return NO_CREDENTIAL_PLACEHOLDER;
|
|
18291
|
+
}
|
|
18292
|
+
if (environmentToken !== void 0)
|
|
18293
|
+
return environmentToken;
|
|
18294
|
+
if (!store.ok || storedToken === void 0)
|
|
18295
|
+
return NO_CREDENTIAL_PLACEHOLDER;
|
|
18296
|
+
const source = {
|
|
18297
|
+
HALFCYCLE_ENV_FILE: store.path,
|
|
18298
|
+
GUARD_SERVICE_TOKEN: storedToken,
|
|
18299
|
+
GUARD_ENGAGEMENT_ID: store.values["GUARD_ENGAGEMENT_ID"],
|
|
18300
|
+
HALFCYCLE_SERVICE_URL: store.values["HALFCYCLE_SERVICE_URL"]
|
|
18301
|
+
};
|
|
18302
|
+
const renewed = opts.renewal === "renew-here" ? await refreshWithinDeadline(source, opts, writeErr) : opts.renewal.token;
|
|
18303
|
+
return renewed ?? storedToken;
|
|
18304
|
+
}
|
|
18305
|
+
async function mcpHeaders(opts) {
|
|
18306
|
+
const writeErr = opts.writeErr ?? defaultWriteErr;
|
|
18307
|
+
try {
|
|
18308
|
+
return headerLine(await chooseBearer({
|
|
18309
|
+
...opts,
|
|
18310
|
+
writeErr,
|
|
18311
|
+
requestedUrl: readMcpServerUrl(),
|
|
18312
|
+
renewal: "renew-here",
|
|
18313
|
+
environmentTokenFirst: true
|
|
18314
|
+
}));
|
|
18315
|
+
} catch (err) {
|
|
18316
|
+
try {
|
|
18317
|
+
writeErr(sentences([
|
|
18318
|
+
`the credential could not be read: ${err instanceof Error ? err.message : String(err)}`,
|
|
18319
|
+
`run "npx halfcycle" in this repository to rewrite this machine's credential.`
|
|
18320
|
+
]));
|
|
18321
|
+
} catch {
|
|
18322
|
+
return headerLine(NO_CREDENTIAL_PLACEHOLDER);
|
|
18323
|
+
}
|
|
18324
|
+
return headerLine(NO_CREDENTIAL_PLACEHOLDER);
|
|
18325
|
+
}
|
|
18326
|
+
}
|
|
18327
|
+
|
|
18062
18328
|
// dist/refresh-credential.js
|
|
18063
18329
|
var UNAUTHORIZED2 = 401;
|
|
18064
|
-
|
|
18330
|
+
var LIVE_PROBE_STATUSES = /* @__PURE__ */ new Set([200, 403]);
|
|
18331
|
+
async function refreshCredentialToken(opts) {
|
|
18332
|
+
const source = opts?.source;
|
|
18065
18333
|
if (readNeverConfiguredReason() === "not-this-account")
|
|
18066
|
-
return 0;
|
|
18067
|
-
if (!readEnvFilePath())
|
|
18068
|
-
return 0;
|
|
18069
|
-
const configResult = readConfig();
|
|
18334
|
+
return { token: void 0 };
|
|
18335
|
+
if (!readEnvFilePath(source))
|
|
18336
|
+
return { token: void 0 };
|
|
18337
|
+
const configResult = readConfig(source);
|
|
18070
18338
|
if (!configResult.config)
|
|
18071
|
-
return 0;
|
|
18339
|
+
return { token: void 0 };
|
|
18072
18340
|
const { guardServiceToken, guardEngagementId } = configResult.config;
|
|
18073
|
-
const controlOrigin = readControlOrigin().origin;
|
|
18341
|
+
const controlOrigin = readControlOrigin(source).origin;
|
|
18074
18342
|
const fetchFn = opts?.fetchFn ?? globalThis.fetch;
|
|
18075
18343
|
const accountCredential = readAccountCredential(controlOrigin, opts?.home);
|
|
18076
18344
|
if (accountCredential)
|
|
18077
18345
|
await probeAccount(controlOrigin, accountCredential, fetchFn);
|
|
18078
18346
|
const probe = await probeAccount(controlOrigin, guardServiceToken, fetchFn);
|
|
18079
|
-
if (!probe.reached
|
|
18080
|
-
return 0;
|
|
18347
|
+
if (!probe.reached)
|
|
18348
|
+
return { token: void 0 };
|
|
18349
|
+
if (probe.status !== UNAUTHORIZED2) {
|
|
18350
|
+
return { token: LIVE_PROBE_STATUSES.has(probe.status) ? guardServiceToken : void 0 };
|
|
18351
|
+
}
|
|
18081
18352
|
const refreshed = await refreshEngagementCredential({
|
|
18082
18353
|
engagementId: guardEngagementId,
|
|
18083
18354
|
refusedToken: guardServiceToken,
|
|
18084
18355
|
fetchFn,
|
|
18085
|
-
home: opts?.home
|
|
18356
|
+
home: opts?.home,
|
|
18357
|
+
source
|
|
18086
18358
|
});
|
|
18087
18359
|
if (!refreshed.ok) {
|
|
18088
18360
|
const write = opts?.write ?? ((text) => process.stdout.write(text));
|
|
18089
18361
|
write(credentialRenewalFailedMessage(refreshed.remedy));
|
|
18362
|
+
return { token: void 0 };
|
|
18090
18363
|
}
|
|
18091
|
-
return
|
|
18364
|
+
return { token: refreshed.token };
|
|
18092
18365
|
}
|
|
18366
|
+
var CHECKED_SOURCES = /* @__PURE__ */ new Set(["startup", "resume"]);
|
|
18367
|
+
var INITIALIZE_BODY = JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: {} });
|
|
18368
|
+
function registeredMcpUrl(projectRoot) {
|
|
18369
|
+
let parsed;
|
|
18370
|
+
try {
|
|
18371
|
+
parsed = JSON.parse(readFileSync10(resolve3(projectRoot, MCP_REGISTRATION_REL), "utf-8"));
|
|
18372
|
+
} catch {
|
|
18373
|
+
return void 0;
|
|
18374
|
+
}
|
|
18375
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
18376
|
+
return void 0;
|
|
18377
|
+
const servers = parsed["mcpServers"];
|
|
18378
|
+
if (typeof servers !== "object" || servers === null || !Object.hasOwn(servers, MCP_SERVER_KEY))
|
|
18379
|
+
return void 0;
|
|
18380
|
+
const entry = servers[MCP_SERVER_KEY];
|
|
18381
|
+
if (typeof entry !== "object" || entry === null)
|
|
18382
|
+
return void 0;
|
|
18383
|
+
const url2 = entry["url"];
|
|
18384
|
+
return typeof url2 === "string" ? url2 : void 0;
|
|
18385
|
+
}
|
|
18386
|
+
function namesItsServer(body) {
|
|
18387
|
+
if (typeof body !== "object" || body === null)
|
|
18388
|
+
return false;
|
|
18389
|
+
const result = body["result"];
|
|
18390
|
+
if (typeof result !== "object" || result === null)
|
|
18391
|
+
return false;
|
|
18392
|
+
const info = result["serverInfo"];
|
|
18393
|
+
if (typeof info !== "object" || info === null)
|
|
18394
|
+
return false;
|
|
18395
|
+
const name = info["name"];
|
|
18396
|
+
return typeof name === "string" && name !== "";
|
|
18397
|
+
}
|
|
18398
|
+
function discardBody(res) {
|
|
18399
|
+
res.body?.cancel().catch(() => void 0);
|
|
18400
|
+
}
|
|
18401
|
+
async function probeDelivery(url2, bearer, fetchFn, deadlineMs) {
|
|
18402
|
+
const controller = new AbortController();
|
|
18403
|
+
const timer = setTimeout(() => controller.abort(), deadlineMs);
|
|
18404
|
+
try {
|
|
18405
|
+
let res;
|
|
18406
|
+
try {
|
|
18407
|
+
res = await fetchFn(url2, {
|
|
18408
|
+
method: "POST",
|
|
18409
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${bearer}` },
|
|
18410
|
+
body: INITIALIZE_BODY,
|
|
18411
|
+
redirect: "manual",
|
|
18412
|
+
signal: controller.signal
|
|
18413
|
+
});
|
|
18414
|
+
} catch {
|
|
18415
|
+
return CANNOT_REACH_SENTENCE;
|
|
18416
|
+
}
|
|
18417
|
+
if (res.status === 401) {
|
|
18418
|
+
discardBody(res);
|
|
18419
|
+
return NO_CREDENTIAL_SENTENCE;
|
|
18420
|
+
}
|
|
18421
|
+
if (res.status < 200 || res.status > 299) {
|
|
18422
|
+
discardBody(res);
|
|
18423
|
+
return SERVICE_PROBLEM_SENTENCE;
|
|
18424
|
+
}
|
|
18425
|
+
let body;
|
|
18426
|
+
try {
|
|
18427
|
+
body = await res.json();
|
|
18428
|
+
} catch {
|
|
18429
|
+
return controller.signal.aborted ? CANNOT_REACH_SENTENCE : SERVICE_PROBLEM_SENTENCE;
|
|
18430
|
+
}
|
|
18431
|
+
return namesItsServer(body) ? void 0 : SERVICE_PROBLEM_SENTENCE;
|
|
18432
|
+
} finally {
|
|
18433
|
+
clearTimeout(timer);
|
|
18434
|
+
}
|
|
18435
|
+
}
|
|
18436
|
+
async function checkConnection(renewedToken, opts) {
|
|
18437
|
+
const url2 = registeredMcpUrl(projectRootFromBinary(opts.binaryPath));
|
|
18438
|
+
if (url2 === void 0)
|
|
18439
|
+
return void 0;
|
|
18440
|
+
const bearer = await chooseBearer({
|
|
18441
|
+
binaryPath: opts.binaryPath,
|
|
18442
|
+
home: opts.home,
|
|
18443
|
+
fetchFn: opts.fetchFn,
|
|
18444
|
+
writeErr: opts.writeErr,
|
|
18445
|
+
requestedUrl: url2,
|
|
18446
|
+
renewal: { token: renewedToken },
|
|
18447
|
+
environmentTokenFirst: false
|
|
18448
|
+
});
|
|
18449
|
+
if (CREDENTIAL_PLACEHOLDERS.includes(bearer)) {
|
|
18450
|
+
return PLACEHOLDER_SENTENCES[bearer];
|
|
18451
|
+
}
|
|
18452
|
+
return probeDelivery(url2, bearer, opts.fetchFn ?? globalThis.fetch, opts.checkDeadlineMs ?? CONNECTION_CHECK_DEADLINE_MS);
|
|
18453
|
+
}
|
|
18454
|
+
function defaultWriteErr2(text) {
|
|
18455
|
+
process.stderr.write(text);
|
|
18456
|
+
}
|
|
18457
|
+
async function runRefreshCredential(opts) {
|
|
18458
|
+
const writeErr = opts.writeErr ?? defaultWriteErr2;
|
|
18459
|
+
try {
|
|
18460
|
+
const renewalSaid = [];
|
|
18461
|
+
let renewalOpen = true;
|
|
18462
|
+
const [input, renewedToken] = await Promise.all([
|
|
18463
|
+
(opts.readInput ?? readHookStdin)(),
|
|
18464
|
+
refreshWithinDeadline(void 0, { fetchFn: opts.fetchFn, home: opts.home, deadlineMs: opts.renewalDeadlineMs }, (text) => {
|
|
18465
|
+
if (renewalOpen)
|
|
18466
|
+
renewalSaid.push(text);
|
|
18467
|
+
})
|
|
18468
|
+
]);
|
|
18469
|
+
renewalOpen = false;
|
|
18470
|
+
const parts = [];
|
|
18471
|
+
if (CHECKED_SOURCES.has(input["source"])) {
|
|
18472
|
+
const outcome = await checkConnection(renewedToken, { ...opts, writeErr });
|
|
18473
|
+
if (outcome !== void 0)
|
|
18474
|
+
parts.push(outcome);
|
|
18475
|
+
}
|
|
18476
|
+
if (readConfig().config === null) {
|
|
18477
|
+
const remedySaid = parts.some(namesTheSignInCommand);
|
|
18478
|
+
parts.push(noCoverageStatement(readNeverConfiguredReason(), remedySaid));
|
|
18479
|
+
}
|
|
18480
|
+
for (const said of renewalSaid)
|
|
18481
|
+
parts.push(said.trimEnd());
|
|
18482
|
+
return parts.length === 0 ? "" : `${nonBlockingReply("SessionStart", parts.join("\n"))}
|
|
18483
|
+
`;
|
|
18484
|
+
} catch (err) {
|
|
18485
|
+
try {
|
|
18486
|
+
writeErr(`halfcycle: the session-start check could not finish: ${(err instanceof Error ? err.message : String(err)).replace(/\s+/g, " ")}
|
|
18487
|
+
`);
|
|
18488
|
+
} catch {
|
|
18489
|
+
return "";
|
|
18490
|
+
}
|
|
18491
|
+
return "";
|
|
18492
|
+
}
|
|
18493
|
+
}
|
|
18494
|
+
|
|
18495
|
+
// dist/bin.js
|
|
18496
|
+
import { fileURLToPath } from "node:url";
|
|
18093
18497
|
|
|
18094
18498
|
// dist/capture.js
|
|
18095
|
-
import { readFileSync as
|
|
18096
|
-
import { dirname as
|
|
18499
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, existsSync as existsSync6 } from "node:fs";
|
|
18500
|
+
import { dirname as dirname5 } from "node:path";
|
|
18097
18501
|
function runCapture(provider, endpoint, opts = {}) {
|
|
18098
18502
|
const repoRoot = opts.repoRoot ?? process.cwd();
|
|
18099
18503
|
const raw = opts.input ?? readInputFile(opts.fromFile);
|
|
@@ -18128,7 +18532,7 @@ function runCapture(provider, endpoint, opts = {}) {
|
|
|
18128
18532
|
const fixture = fixturePath(repoRoot, provider, slug);
|
|
18129
18533
|
const manifestFile = manifestPath(repoRoot);
|
|
18130
18534
|
try {
|
|
18131
|
-
mkdirSync3(
|
|
18535
|
+
mkdirSync3(dirname5(fixture), { recursive: true });
|
|
18132
18536
|
writeFileSync4(fixture, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
18133
18537
|
} catch (err) {
|
|
18134
18538
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -18160,7 +18564,7 @@ function readInputFile(fromFile) {
|
|
|
18160
18564
|
if (!fromFile)
|
|
18161
18565
|
return void 0;
|
|
18162
18566
|
try {
|
|
18163
|
-
return
|
|
18567
|
+
return readFileSync11(fromFile, "utf-8");
|
|
18164
18568
|
} catch {
|
|
18165
18569
|
return void 0;
|
|
18166
18570
|
}
|
|
@@ -18180,8 +18584,13 @@ async function main() {
|
|
|
18180
18584
|
return;
|
|
18181
18585
|
}
|
|
18182
18586
|
if (cmd === "refresh-credential") {
|
|
18183
|
-
const
|
|
18184
|
-
process.exit(
|
|
18587
|
+
const reply = await runRefreshCredential({ binaryPath: fileURLToPath(import.meta.url) });
|
|
18588
|
+
process.stdout.write(reply, () => process.exit(0));
|
|
18589
|
+
return;
|
|
18590
|
+
}
|
|
18591
|
+
if (cmd === "mcp-headers") {
|
|
18592
|
+
const line = await mcpHeaders({ binaryPath: fileURLToPath(import.meta.url) });
|
|
18593
|
+
process.stdout.write(line, () => process.exit(0));
|
|
18185
18594
|
return;
|
|
18186
18595
|
}
|
|
18187
18596
|
if (cmd === "resolve-engagement") {
|
|
@@ -18249,16 +18658,17 @@ Commit the fixture; the next guard evaluation of this endpoint will pass.
|
|
|
18249
18658
|
halfcycle-runner hook stop
|
|
18250
18659
|
halfcycle-runner hook subagent-stop
|
|
18251
18660
|
halfcycle-runner refresh-credential
|
|
18661
|
+
halfcycle-runner mcp-headers
|
|
18252
18662
|
halfcycle-runner resolve-engagement <cwd>
|
|
18253
18663
|
halfcycle-runner capture <provider> <endpoint> [--from <file>]
|
|
18254
18664
|
`);
|
|
18255
18665
|
process.exit(1);
|
|
18256
18666
|
}
|
|
18257
18667
|
function readStdin3() {
|
|
18258
|
-
return new Promise((
|
|
18668
|
+
return new Promise((resolve4, reject) => {
|
|
18259
18669
|
const chunks = [];
|
|
18260
18670
|
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
18261
|
-
process.stdin.on("end", () =>
|
|
18671
|
+
process.stdin.on("end", () => resolve4(Buffer.concat(chunks).toString("utf-8")));
|
|
18262
18672
|
process.stdin.on("error", reject);
|
|
18263
18673
|
});
|
|
18264
18674
|
}
|