halfcycle 0.3.36 → 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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "halfcycle",
3
- "version": "0.3.36",
3
+ "version": "0.3.37",
4
4
  "description": "Halfcycle Method bundle — resolution-stub slash commands, remote method-delivery registration, and governance-hook wiring for a Halfcycle engagement repo. It ships NO worker role files: a project's roles are authored from that project's own recorded decisions at orchestration kickoff (FX-2, W3-F-27).",
5
5
  "commands": [
6
6
  {
package/README.md CHANGED
@@ -191,7 +191,9 @@ npx halfcycle
191
191
 
192
192
  Then open the folder in Claude Code and run `/halfcycle-setup`, and answer the questions it asks about your project.
193
193
 
194
- **Accept the workspace trust prompt when Claude Code shows it.** The install writes a credential helper and a set of hooks that are shell commands, so Claude Code asks before it runs them. It is expected, and it appears once.
194
+ **Accept the workspace trust prompt when Claude Code shows it.** The install writes a credential helper and a set of hooks that are shell commands, so Claude Code asks before it runs them. It is expected.
195
+
196
+ **A second prompt follows — it is not the trust prompt again.** After you accept the trust prompt, Claude Code asks whether to use a new MCP server. The cursor starts on *Continue without using this MCP server*; choose to use the server instead, or Halfcycle will not connect.
195
197
 
196
198
  You will get a board link on install. That is where the work becomes visible to anyone who is not you.
197
199
 
@@ -14749,6 +14749,10 @@ function looksLikeSecret(text) {
14749
14749
  return false;
14750
14750
  }
14751
14751
 
14752
+ // ../core/dist/mcp-registration.js
14753
+ var MCP_REGISTRATION_REL = ".mcp.json";
14754
+ var MCP_SERVER_KEY = "halfcycle";
14755
+
14752
14756
  // dist/control-origin.js
14753
14757
  var DEFAULT_CONTROL_ORIGIN = "https://control.halfcycle.ai";
14754
14758
  function resolveControlOrigin(env) {
@@ -15295,6 +15299,22 @@ var ciTokenExchangeResponseSchema = external_exports.object({
15295
15299
  var NO_CREDENTIAL_PLACEHOLDER = "halfcycle:no-credential";
15296
15300
  var NOT_THIS_ACCOUNT_PLACEHOLDER = "halfcycle:not-this-account";
15297
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
+ });
15298
15318
 
15299
15319
  // dist/hook-protocol.js
15300
15320
  import { writeSync } from "node:fs";
@@ -15303,7 +15323,7 @@ function emitBlock(reason) {
15303
15323
  process.exit(0);
15304
15324
  }
15305
15325
  function nonBlockingReply(event, text) {
15306
- if (event === "PostToolUse") {
15326
+ if (event === "PostToolUse" || event === "SessionStart") {
15307
15327
  return JSON.stringify({
15308
15328
  systemMessage: text,
15309
15329
  hookSpecificOutput: { hookEventName: event, additionalContext: text }
@@ -15348,11 +15368,15 @@ This is not a service outage: the guard service answered. The suspect is this re
15348
15368
  }
15349
15369
  var UNCONFIGURED_EXIT_TOOL_USE = 2;
15350
15370
  var UNCONFIGURED_EXIT_STOP_FAMILY = 1;
15351
- function emitNeverConfigured(missing, exitCode, reason) {
15352
- writeSync(2, neverConfiguredReport(missing, reason));
15371
+ function emitNeverConfigured(exitCode, reason) {
15372
+ writeSync(2, neverConfiguredReport(reason));
15353
15373
  process.exitCode = exitCode;
15354
15374
  }
15355
- var HOOK_REMEDY = `Fix it by running "npx halfcycle" in this repository: it may open your browser to sign in first, then writes this engagement's credentials to ~/.halfcycle \u2014 outside the repository \u2014 and the hook loads them itself.`;
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
+ }
15356
15380
  var PERMISSIONS_BLOCK = "\n permissions:\n contents: read\n id-token: write\n\n";
15357
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.";
15358
15382
  var CI_CANNOT_PROVE_PROJECT = "has no way to prove which project it belongs to";
@@ -15360,17 +15384,17 @@ var CI_REMEDY_LEAD = "Add this to the workflow \u2014 at the top of the file, or
15360
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.";
15361
15385
  var CI_REMEDY = `This job cannot vouch for anything, because it ${CI_CANNOT_PROVE_PROJECT}. ${CI_REMEDY_LEAD} ${CI_OLDER_SETUP}`;
15362
15386
  var CI_NOT_SUPPORTED_MESSAGE = "[Halfcycle CI] This is Halfcycle-internal and is not supported in your CI.\n";
15363
- var NEVER_CONFIGURED_HEADLINE = "GUARD NEVER CONFIGURED";
15387
+ var NEVER_CONFIGURED_HEADLINE = "Halfcycle guidance and support won't be available";
15364
15388
  var NOT_A_SERVICE_OUTAGE = "This is not a service outage.";
15365
- function neverConfiguredMessage(missing, remedy, label = "[Halfcycle]") {
15366
- return `${label} ${NEVER_CONFIGURED_HEADLINE} \u2014 this repository has no guard coverage.
15367
- No evaluation was attempted, because there is nothing to attempt one with: ${missing.join(", ")} ${missing.length === 1 ? "is" : "are"} absent.
15368
- ${NOT_A_SERVICE_OUTAGE} It means the guard has never run here \u2014 not on this change, and not on any change before it.
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}
15369
15393
  ${remedy}
15370
15394
  `;
15371
15395
  }
15372
15396
  function ciNeverConfiguredMessage() {
15373
- return `[Halfcycle CI] ${NEVER_CONFIGURED_HEADLINE} \u2014 this job ${CI_CANNOT_PROVE_PROJECT}, so it checked nothing. ${CI_REMEDY_LEAD}
15397
+ return `[Halfcycle CI] ${NEVER_CONFIGURED_HEADLINE} in this job: it ${CI_CANNOT_PROVE_PROJECT}, so it checked nothing. ${CI_REMEDY_LEAD}
15374
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.
15375
15399
  ${CI_OLDER_SETUP}
15376
15400
  `;
@@ -15379,10 +15403,10 @@ function notThisAccountReport(label) {
15379
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.
15380
15404
  `;
15381
15405
  }
15382
- function neverConfiguredReport(missing, reason, label = "[Halfcycle]") {
15406
+ function neverConfiguredReport(reason, label = "[Halfcycle]") {
15383
15407
  switch (reason) {
15384
15408
  case "no-credential-yet":
15385
- return neverConfiguredMessage(missing, HOOK_REMEDY, label);
15409
+ return neverConfiguredMessage(HOOK_REMEDY, label);
15386
15410
  case "not-this-account":
15387
15411
  return notThisAccountReport(label);
15388
15412
  }
@@ -15438,6 +15462,21 @@ function credentialRenewalFailedMessage(remedy, label = "[Halfcycle]") {
15438
15462
  ${remedy}
15439
15463
  `;
15440
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
+ }
15441
15480
  function notRunReport(notRun, label = "[Halfcycle]") {
15442
15481
  if (!notRun || notRun.length === 0)
15443
15482
  return "";
@@ -17287,6 +17326,7 @@ function writeRefreshedToken(opts) {
17287
17326
 
17288
17327
  // dist/credential-refresh.js
17289
17328
  var CONTROL_TIMEOUT_MS = 5e3;
17329
+ var CONNECTION_CHECK_DEADLINE_MS = 3e3;
17290
17330
  var UNAUTHORIZED = 401;
17291
17331
  async function probeAccount(controlOrigin, credential, fetchFn = globalThis.fetch) {
17292
17332
  const controller = new AbortController();
@@ -17590,7 +17630,7 @@ async function evaluateEdit(reply, readSeat) {
17590
17630
  const configResult = readConfig();
17591
17631
  if (!configResult.config) {
17592
17632
  await emitRunRecord({ runType: "hook", outcome: "unconfigured", changeSet });
17593
- emitNeverConfigured(configResult.missing, UNCONFIGURED_EXIT_TOOL_USE, readNeverConfiguredReason());
17633
+ emitNeverConfigured(UNCONFIGURED_EXIT_TOOL_USE, readNeverConfiguredReason());
17594
17634
  return;
17595
17635
  }
17596
17636
  const { guardServiceUrl, guardServiceToken, guardEngagementId } = configResult.config;
@@ -17680,10 +17720,10 @@ ${guardLines}${notRunBlock}`);
17680
17720
  }
17681
17721
  }
17682
17722
  function readStdin() {
17683
- return new Promise((resolve3, reject) => {
17723
+ return new Promise((resolve4, reject) => {
17684
17724
  const chunks = [];
17685
17725
  process.stdin.on("data", (chunk) => chunks.push(chunk));
17686
- process.stdin.on("end", () => resolve3(Buffer.concat(chunks).toString("utf-8")));
17726
+ process.stdin.on("end", () => resolve4(Buffer.concat(chunks).toString("utf-8")));
17687
17727
  process.stdin.on("error", reject);
17688
17728
  });
17689
17729
  }
@@ -17885,7 +17925,7 @@ async function evaluateSession(input, kind, reply, readSeat) {
17885
17925
  const configResult = readConfig();
17886
17926
  if (!configResult.config) {
17887
17927
  await emitRunRecord({ runType: "hook", outcome: "unconfigured", changeSet, diffBase });
17888
- emitNeverConfigured(configResult.missing, UNCONFIGURED_EXIT_STOP_FAMILY, readNeverConfiguredReason());
17928
+ emitNeverConfigured(UNCONFIGURED_EXIT_STOP_FAMILY, readNeverConfiguredReason());
17889
17929
  return;
17890
17930
  }
17891
17931
  const { guardServiceUrl, guardServiceToken, guardEngagementId } = configResult.config;
@@ -17978,6 +18018,7 @@ ${guardLines}${notRunBlock}`);
17978
18018
  }
17979
18019
 
17980
18020
  // dist/hooks/stdin.js
18021
+ var HOOK_STDIN_GRACE_MS = 2e3;
17981
18022
  async function readHookStdin() {
17982
18023
  try {
17983
18024
  const raw = await readStdin2();
@@ -17992,9 +18033,9 @@ async function readHookStdin() {
17992
18033
  }
17993
18034
  }
17994
18035
  function readStdin2() {
17995
- return new Promise((resolve3) => {
18036
+ return new Promise((resolve4) => {
17996
18037
  if (process.stdin.isTTY) {
17997
- resolve3("");
18038
+ resolve4("");
17998
18039
  return;
17999
18040
  }
18000
18041
  const chunks = [];
@@ -18004,9 +18045,9 @@ function readStdin2() {
18004
18045
  return;
18005
18046
  settled = true;
18006
18047
  clearTimeout(timer);
18007
- resolve3(Buffer.concat(chunks).toString("utf-8"));
18048
+ resolve4(Buffer.concat(chunks).toString("utf-8"));
18008
18049
  };
18009
- const timer = setTimeout(done, 2e3);
18050
+ const timer = setTimeout(done, HOOK_STDIN_GRACE_MS);
18010
18051
  process.stdin.on("data", (chunk) => chunks.push(chunk));
18011
18052
  process.stdin.on("end", done);
18012
18053
  process.stdin.on("error", done);
@@ -18084,52 +18125,16 @@ function runResolveEngagement(cwd, registryPath) {
18084
18125
  }
18085
18126
 
18086
18127
  // dist/refresh-credential.js
18087
- var UNAUTHORIZED2 = 401;
18088
- var LIVE_PROBE_STATUSES = /* @__PURE__ */ new Set([200, 403]);
18089
- async function refreshCredentialToken(opts) {
18090
- const source = opts?.source;
18091
- if (readNeverConfiguredReason() === "not-this-account")
18092
- return { token: void 0 };
18093
- if (!readEnvFilePath(source))
18094
- return { token: void 0 };
18095
- const configResult = readConfig(source);
18096
- if (!configResult.config)
18097
- return { token: void 0 };
18098
- const { guardServiceToken, guardEngagementId } = configResult.config;
18099
- const controlOrigin = readControlOrigin(source).origin;
18100
- const fetchFn = opts?.fetchFn ?? globalThis.fetch;
18101
- const accountCredential = readAccountCredential(controlOrigin, opts?.home);
18102
- if (accountCredential)
18103
- await probeAccount(controlOrigin, accountCredential, fetchFn);
18104
- const probe = await probeAccount(controlOrigin, guardServiceToken, fetchFn);
18105
- if (!probe.reached)
18106
- return { token: void 0 };
18107
- if (probe.status !== UNAUTHORIZED2) {
18108
- return { token: LIVE_PROBE_STATUSES.has(probe.status) ? guardServiceToken : void 0 };
18109
- }
18110
- const refreshed = await refreshEngagementCredential({
18111
- engagementId: guardEngagementId,
18112
- refusedToken: guardServiceToken,
18113
- fetchFn,
18114
- home: opts?.home,
18115
- source
18116
- });
18117
- if (!refreshed.ok) {
18118
- const write = opts?.write ?? ((text) => process.stdout.write(text));
18119
- write(credentialRenewalFailedMessage(refreshed.remedy));
18120
- return { token: void 0 };
18121
- }
18122
- return { token: refreshed.token };
18123
- }
18124
- async function runRefreshCredential(opts) {
18125
- await refreshCredentialToken(opts);
18126
- return 0;
18127
- }
18128
+ import { readFileSync as readFileSync10 } from "node:fs";
18129
+ import { resolve as resolve3 } from "node:path";
18128
18130
 
18129
18131
  // dist/mcp-headers.js
18130
18132
  import { readFileSync as readFileSync9 } from "node:fs";
18131
18133
  import { homedir as homedir5 } from "node:os";
18132
18134
  import { dirname as dirname4, resolve as resolve2 } from "node:path";
18135
+ function defaultWriteErr(text) {
18136
+ process.stderr.write(text);
18137
+ }
18133
18138
  function headerLine(bearer) {
18134
18139
  return `${JSON.stringify({ Authorization: `Bearer ${bearer}` })}
18135
18140
  `;
@@ -18233,16 +18238,17 @@ async function refreshWithinDeadline(source, opts, writeErr) {
18233
18238
  clearTimeout(timer);
18234
18239
  }
18235
18240
  }
18236
- async function decide(opts, writeErr) {
18241
+ async function chooseBearer(opts) {
18242
+ const writeErr = opts.writeErr ?? defaultWriteErr;
18237
18243
  const home = opts.home ?? homedir5();
18238
18244
  const root = projectRootFromBinary(opts.binaryPath);
18239
- const environmentToken = readEnvironmentToken();
18245
+ const environmentToken = opts.environmentTokenFirst ? readEnvironmentToken() : void 0;
18240
18246
  const store = resolveStore(root, home);
18241
18247
  let storedToken;
18242
18248
  if (environmentToken === void 0) {
18243
18249
  if (!store.ok) {
18244
18250
  writeErr(storeProblemSentence(store.problem, root, home, store.engagementId));
18245
- return headerLine(store.problem === "not-this-account" ? NOT_THIS_ACCOUNT_PLACEHOLDER : NO_CREDENTIAL_PLACEHOLDER);
18251
+ return store.problem === "not-this-account" ? NOT_THIS_ACCOUNT_PLACEHOLDER : NO_CREDENTIAL_PLACEHOLDER;
18246
18252
  }
18247
18253
  storedToken = storedEngagementToken(store.values);
18248
18254
  if (storedToken === void 0) {
@@ -18250,17 +18256,17 @@ async function decide(opts, writeErr) {
18250
18256
  `GUARD_SERVICE_TOKEN is absent or empty in ${store.path}.`,
18251
18257
  `run "npx halfcycle" in this repository to rewrite this machine's credential.`
18252
18258
  ]));
18253
- return headerLine(NO_CREDENTIAL_PLACEHOLDER);
18259
+ return NO_CREDENTIAL_PLACEHOLDER;
18254
18260
  }
18255
18261
  }
18256
- const requested = readMcpServerUrl();
18262
+ const requested = opts.requestedUrl;
18257
18263
  const recorded = store.ok ? store.values["HALFCYCLE_MCP_URL"] : void 0;
18258
18264
  if (requested === void 0 && store.ok) {
18259
18265
  writeErr(sentences([
18260
18266
  "Claude Code did not say which server it is connecting to, so the Halfcycle credential was not sent.",
18261
18267
  "update Claude Code, then reconnect."
18262
18268
  ]));
18263
- return headerLine(UPDATE_CLAUDE_CODE_PLACEHOLDER);
18269
+ return UPDATE_CLAUDE_CODE_PLACEHOLDER;
18264
18270
  }
18265
18271
  if (!recorded) {
18266
18272
  if (!store.ok && store.problem === "bad-id") {
@@ -18271,7 +18277,7 @@ async function decide(opts, writeErr) {
18271
18277
  'run "npx halfcycle" in this repository on this machine to record it.'
18272
18278
  ]));
18273
18279
  }
18274
- return headerLine(NO_CREDENTIAL_PLACEHOLDER);
18280
+ return NO_CREDENTIAL_PLACEHOLDER;
18275
18281
  }
18276
18282
  const want = urlOrigin(recorded);
18277
18283
  const got = requested === void 0 ? void 0 : urlOrigin(requested);
@@ -18281,25 +18287,31 @@ async function decide(opts, writeErr) {
18281
18287
  `which is not this machine's Halfcycle server (${want ?? "none recorded"}). The credential was NOT sent.`,
18282
18288
  'if nobody meant to change .mcp.json, treat that change as suspect; "npx halfcycle" restores the entry.'
18283
18289
  ]));
18284
- return headerLine(NO_CREDENTIAL_PLACEHOLDER);
18290
+ return NO_CREDENTIAL_PLACEHOLDER;
18285
18291
  }
18286
18292
  if (environmentToken !== void 0)
18287
- return headerLine(environmentToken);
18293
+ return environmentToken;
18288
18294
  if (!store.ok || storedToken === void 0)
18289
- return headerLine(NO_CREDENTIAL_PLACEHOLDER);
18295
+ return NO_CREDENTIAL_PLACEHOLDER;
18290
18296
  const source = {
18291
18297
  HALFCYCLE_ENV_FILE: store.path,
18292
18298
  GUARD_SERVICE_TOKEN: storedToken,
18293
18299
  GUARD_ENGAGEMENT_ID: store.values["GUARD_ENGAGEMENT_ID"],
18294
18300
  HALFCYCLE_SERVICE_URL: store.values["HALFCYCLE_SERVICE_URL"]
18295
18301
  };
18296
- const refreshed = await refreshWithinDeadline(source, opts, writeErr);
18297
- return headerLine(refreshed ?? storedToken);
18302
+ const renewed = opts.renewal === "renew-here" ? await refreshWithinDeadline(source, opts, writeErr) : opts.renewal.token;
18303
+ return renewed ?? storedToken;
18298
18304
  }
18299
18305
  async function mcpHeaders(opts) {
18300
- const writeErr = opts.writeErr ?? ((text) => void process.stderr.write(text));
18306
+ const writeErr = opts.writeErr ?? defaultWriteErr;
18301
18307
  try {
18302
- return await decide(opts, writeErr);
18308
+ return headerLine(await chooseBearer({
18309
+ ...opts,
18310
+ writeErr,
18311
+ requestedUrl: readMcpServerUrl(),
18312
+ renewal: "renew-here",
18313
+ environmentTokenFirst: true
18314
+ }));
18303
18315
  } catch (err) {
18304
18316
  try {
18305
18317
  writeErr(sentences([
@@ -18313,11 +18325,178 @@ async function mcpHeaders(opts) {
18313
18325
  }
18314
18326
  }
18315
18327
 
18328
+ // dist/refresh-credential.js
18329
+ var UNAUTHORIZED2 = 401;
18330
+ var LIVE_PROBE_STATUSES = /* @__PURE__ */ new Set([200, 403]);
18331
+ async function refreshCredentialToken(opts) {
18332
+ const source = opts?.source;
18333
+ if (readNeverConfiguredReason() === "not-this-account")
18334
+ return { token: void 0 };
18335
+ if (!readEnvFilePath(source))
18336
+ return { token: void 0 };
18337
+ const configResult = readConfig(source);
18338
+ if (!configResult.config)
18339
+ return { token: void 0 };
18340
+ const { guardServiceToken, guardEngagementId } = configResult.config;
18341
+ const controlOrigin = readControlOrigin(source).origin;
18342
+ const fetchFn = opts?.fetchFn ?? globalThis.fetch;
18343
+ const accountCredential = readAccountCredential(controlOrigin, opts?.home);
18344
+ if (accountCredential)
18345
+ await probeAccount(controlOrigin, accountCredential, fetchFn);
18346
+ const probe = await probeAccount(controlOrigin, guardServiceToken, fetchFn);
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
+ }
18352
+ const refreshed = await refreshEngagementCredential({
18353
+ engagementId: guardEngagementId,
18354
+ refusedToken: guardServiceToken,
18355
+ fetchFn,
18356
+ home: opts?.home,
18357
+ source
18358
+ });
18359
+ if (!refreshed.ok) {
18360
+ const write = opts?.write ?? ((text) => process.stdout.write(text));
18361
+ write(credentialRenewalFailedMessage(refreshed.remedy));
18362
+ return { token: void 0 };
18363
+ }
18364
+ return { token: refreshed.token };
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
+
18316
18495
  // dist/bin.js
18317
18496
  import { fileURLToPath } from "node:url";
18318
18497
 
18319
18498
  // dist/capture.js
18320
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, existsSync as existsSync6 } from "node:fs";
18499
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, existsSync as existsSync6 } from "node:fs";
18321
18500
  import { dirname as dirname5 } from "node:path";
18322
18501
  function runCapture(provider, endpoint, opts = {}) {
18323
18502
  const repoRoot = opts.repoRoot ?? process.cwd();
@@ -18385,7 +18564,7 @@ function readInputFile(fromFile) {
18385
18564
  if (!fromFile)
18386
18565
  return void 0;
18387
18566
  try {
18388
- return readFileSync10(fromFile, "utf-8");
18567
+ return readFileSync11(fromFile, "utf-8");
18389
18568
  } catch {
18390
18569
  return void 0;
18391
18570
  }
@@ -18405,8 +18584,8 @@ async function main() {
18405
18584
  return;
18406
18585
  }
18407
18586
  if (cmd === "refresh-credential") {
18408
- const code = await runRefreshCredential();
18409
- process.exit(code);
18587
+ const reply = await runRefreshCredential({ binaryPath: fileURLToPath(import.meta.url) });
18588
+ process.stdout.write(reply, () => process.exit(0));
18410
18589
  return;
18411
18590
  }
18412
18591
  if (cmd === "mcp-headers") {
@@ -18486,10 +18665,10 @@ Commit the fixture; the next guard evaluation of this endpoint will pass.
18486
18665
  process.exit(1);
18487
18666
  }
18488
18667
  function readStdin3() {
18489
- return new Promise((resolve3, reject) => {
18668
+ return new Promise((resolve4, reject) => {
18490
18669
  const chunks = [];
18491
18670
  process.stdin.on("data", (chunk) => chunks.push(chunk));
18492
- process.stdin.on("end", () => resolve3(Buffer.concat(chunks).toString("utf-8")));
18671
+ process.stdin.on("end", () => resolve4(Buffer.concat(chunks).toString("utf-8")));
18493
18672
  process.stdin.on("error", reject);
18494
18673
  });
18495
18674
  }