halfcycle 0.3.35 → 0.3.36

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.35",
3
+ "version": "0.3.36",
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
@@ -167,6 +167,11 @@ Nothing you already had is changed. Existing settings are preserved, and a name
167
167
  - Claude Code **2.1.118+**
168
168
  - Node 20+
169
169
  - A git repository
170
+ - On Windows, if you use PowerShell: let it run the scripts Node installs, once per user. Otherwise PowerShell refuses `npx` with *running scripts is disabled on this system*:
171
+
172
+ ```powershell
173
+ Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
174
+ ```
170
175
 
171
176
  ## Getting started
172
177
 
@@ -176,6 +181,14 @@ git init
176
181
  npx halfcycle
177
182
  ```
178
183
 
184
+ In Windows PowerShell, separate the first two commands with `;` instead of `&&`:
185
+
186
+ ```powershell
187
+ mkdir my-project; cd my-project
188
+ git init
189
+ npx halfcycle
190
+ ```
191
+
179
192
  Then open the folder in Claude Code and run `/halfcycle-setup`, and answer the questions it asks about your project.
180
193
 
181
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.
@@ -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) {
@@ -14761,10 +14764,10 @@ var EXCERPT_DEFAULTS = {
14761
14764
  maxExcerptsPerFile: 5,
14762
14765
  maxExcerptsPerChangeSet: 20
14763
14766
  };
14764
- function readConfig() {
14765
- const guardServiceUrl = readGuardOrigin().origin;
14766
- const guardServiceToken = process.env["GUARD_SERVICE_TOKEN"];
14767
- const guardEngagementId = process.env["GUARD_ENGAGEMENT_ID"];
14767
+ function readConfig(env = process.env) {
14768
+ const guardServiceUrl = readGuardOrigin(env).origin;
14769
+ const guardServiceToken = env["GUARD_SERVICE_TOKEN"];
14770
+ const guardEngagementId = env["GUARD_ENGAGEMENT_ID"];
14768
14771
  const missing = [];
14769
14772
  if (!guardServiceToken)
14770
14773
  missing.push("GUARD_SERVICE_TOKEN");
@@ -14786,8 +14789,8 @@ function readNeverConfiguredReason() {
14786
14789
  function isHalfcycleOwnCi() {
14787
14790
  return process.env["HALFCYCLE_INTERNAL_CI"] === "1";
14788
14791
  }
14789
- function readGuardOrigin() {
14790
- return resolveGuardOrigin(process.env);
14792
+ function readGuardOrigin(env = process.env) {
14793
+ return resolveGuardOrigin(env);
14791
14794
  }
14792
14795
  function readActionsOidcEnv() {
14793
14796
  const requestUrl = process.env["ACTIONS_ID_TOKEN_REQUEST_URL"]?.trim();
@@ -14796,11 +14799,19 @@ function readActionsOidcEnv() {
14796
14799
  return void 0;
14797
14800
  return { requestUrl, requestToken };
14798
14801
  }
14799
- function readControlOrigin() {
14800
- return resolveControlOrigin(process.env);
14802
+ function readControlOrigin(env = process.env) {
14803
+ return resolveControlOrigin(env);
14801
14804
  }
14802
- function readEnvFilePath() {
14803
- const raw = process.env["HALFCYCLE_ENV_FILE"]?.trim();
14805
+ function readEnvFilePath(env = process.env) {
14806
+ const raw = env["HALFCYCLE_ENV_FILE"]?.trim();
14807
+ return raw ? raw : void 0;
14808
+ }
14809
+ function readEnvironmentToken() {
14810
+ const raw = process.env["HALFCYCLE_TOKEN"];
14811
+ return raw?.trim() ? raw : void 0;
14812
+ }
14813
+ function readMcpServerUrl() {
14814
+ const raw = process.env["CLAUDE_CODE_MCP_SERVER_URL"];
14804
14815
  return raw ? raw : void 0;
14805
14816
  }
14806
14817
  function readExcerptConfig() {
@@ -15281,6 +15292,9 @@ var ciTokenExchangeResponseSchema = external_exports.object({
15281
15292
  expiresAt: utcIso8601,
15282
15293
  engagementId: external_exports.string()
15283
15294
  }).strict();
15295
+ var NO_CREDENTIAL_PLACEHOLDER = "halfcycle:no-credential";
15296
+ var NOT_THIS_ACCOUNT_PLACEHOLDER = "halfcycle:not-this-account";
15297
+ var UPDATE_CLAUDE_CODE_PLACEHOLDER = "halfcycle:update-claude-code";
15284
15298
 
15285
15299
  // dist/hook-protocol.js
15286
15300
  import { writeSync } from "node:fs";
@@ -17186,7 +17200,7 @@ function cleanRunSummary(verdict, scope) {
17186
17200
  }
17187
17201
 
17188
17202
  // dist/credential-store.js
17189
- import { chmodSync, readFileSync as readFileSync5, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2, realpathSync } from "node:fs";
17203
+ import { chmodSync, existsSync as existsSync2, readFileSync as readFileSync5, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2, realpathSync } from "node:fs";
17190
17204
  import { homedir as homedir2, platform } from "node:os";
17191
17205
  import { dirname as dirname3, join as join5 } from "node:path";
17192
17206
  var REFRESHED_TOKEN_KEYS = ["HALFCYCLE_TOKEN", "GUARD_SERVICE_TOKEN"];
@@ -17194,6 +17208,9 @@ var TOKEN_READ_KEY = "GUARD_SERVICE_TOKEN";
17194
17208
  function composeEngagementEnvPath(engagementId, home) {
17195
17209
  return join5(home ?? homedir2(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, ENGAGEMENT_ENV_FILENAME);
17196
17210
  }
17211
+ function hasNotThisAccountMarker(engagementId, home) {
17212
+ return existsSync2(join5(home ?? homedir2(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, NOT_THIS_ACCOUNT_MARKER_FILENAME));
17213
+ }
17197
17214
  function readAccountCredential(controlOrigin, home) {
17198
17215
  let raw;
17199
17216
  try {
@@ -17208,13 +17225,20 @@ function readAccountCredential(controlOrigin, home) {
17208
17225
  return entry.credential;
17209
17226
  }
17210
17227
  function readStoredEngagementToken(envFilePath) {
17228
+ const values = readEngagementStore(envFilePath);
17229
+ return values === void 0 ? void 0 : storedEngagementToken(values);
17230
+ }
17231
+ function readEngagementStore(envFilePath) {
17211
17232
  let raw;
17212
17233
  try {
17213
17234
  raw = readFileSync5(envFilePath, "utf-8");
17214
17235
  } catch {
17215
17236
  return void 0;
17216
17237
  }
17217
- const value = parseEnvText(raw)[TOKEN_READ_KEY];
17238
+ return parseEnvText(raw);
17239
+ }
17240
+ function storedEngagementToken(values) {
17241
+ const value = values[TOKEN_READ_KEY];
17218
17242
  return value && value.trim() !== "" ? value : void 0;
17219
17243
  }
17220
17244
  function writeRefreshedToken(opts) {
@@ -17325,14 +17349,14 @@ var UNREACHABLE_REMEDY = "The Halfcycle service could not be reached to renew th
17325
17349
  var UNREADABLE_REFUSAL_REMEDY = "The Halfcycle service declined to renew this credential and gave no reason this version could read.";
17326
17350
  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
17351
  async function refreshEngagementCredential(opts) {
17328
- const envFilePath = readEnvFilePath();
17352
+ const envFilePath = readEnvFilePath(opts.source);
17329
17353
  if (!envFilePath)
17330
17354
  return { ok: false, remedy: HOOK_REMEDY };
17331
17355
  const stored = readStoredEngagementToken(envFilePath);
17332
17356
  if (stored !== void 0 && stored !== opts.refusedToken) {
17333
17357
  return { ok: true, token: stored, source: "disk" };
17334
17358
  }
17335
- const controlOrigin = readControlOrigin().origin;
17359
+ const controlOrigin = readControlOrigin(opts.source).origin;
17336
17360
  const accountCredential = readAccountCredential(controlOrigin, opts.home);
17337
17361
  if (!accountCredential)
17338
17362
  return { ok: false, remedy: HOOK_REMEDY };
@@ -17375,7 +17399,7 @@ async function evaluateWithRefresh(opts) {
17375
17399
  }
17376
17400
 
17377
17401
  // dist/seat-configuration.js
17378
- import { existsSync as existsSync2, readFileSync as readFileSync6, realpathSync as realpathSync2 } from "node:fs";
17402
+ import { existsSync as existsSync3, readFileSync as readFileSync6, realpathSync as realpathSync2 } from "node:fs";
17379
17403
  import { homedir as homedir3 } from "node:os";
17380
17404
  import { join as join6, relative as relative3, resolve } from "node:path";
17381
17405
  function gatherSeatConfiguration(repoRoot, read = readSeatConfiguration) {
@@ -17484,7 +17508,7 @@ function readPathStates(projectDir, repoRoot) {
17484
17508
  const status = getWiringStatus(HALFCYCLE_HOOK_WIRING_PATHS, projectDir);
17485
17509
  if (!status.ok)
17486
17510
  return unknown2();
17487
- const mapped = mapWiringStatus(status.value, prefix, HALFCYCLE_HOOK_WIRING_PATHS, (rel) => existsSync2(join6(projectDir, rel)));
17511
+ const mapped = mapWiringStatus(status.value, prefix, HALFCYCLE_HOOK_WIRING_PATHS, (rel) => existsSync3(join6(projectDir, rel)));
17488
17512
  return mapped ?? unknown2();
17489
17513
  }
17490
17514
  function mapWiringStatus(output, prefix, paths, onDisk) {
@@ -17656,17 +17680,17 @@ ${guardLines}${notRunBlock}`);
17656
17680
  }
17657
17681
  }
17658
17682
  function readStdin() {
17659
- return new Promise((resolve2, reject) => {
17683
+ return new Promise((resolve3, reject) => {
17660
17684
  const chunks = [];
17661
17685
  process.stdin.on("data", (chunk) => chunks.push(chunk));
17662
- process.stdin.on("end", () => resolve2(Buffer.concat(chunks).toString("utf-8")));
17686
+ process.stdin.on("end", () => resolve3(Buffer.concat(chunks).toString("utf-8")));
17663
17687
  process.stdin.on("error", reject);
17664
17688
  });
17665
17689
  }
17666
17690
 
17667
17691
  // dist/hooks/marker.js
17668
17692
  import { createHash } from "node:crypto";
17669
- import { existsSync as existsSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3, readdirSync as readdirSync3, statSync } from "node:fs";
17693
+ import { existsSync as existsSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3, readdirSync as readdirSync3, statSync } from "node:fs";
17670
17694
  import { tmpdir } from "node:os";
17671
17695
  import { join as join7 } from "node:path";
17672
17696
  var MARKER_TTL_MS = 24 * 60 * 60 * 1e3;
@@ -17695,7 +17719,7 @@ function dedupePath(repoRoot, sessionId) {
17695
17719
  }
17696
17720
  function readMarker(repoRoot, sessionId) {
17697
17721
  const path = markerPath(repoRoot, sessionId);
17698
- if (!existsSync3(path))
17722
+ if (!existsSync4(path))
17699
17723
  return null;
17700
17724
  try {
17701
17725
  return readFileSync7(path, "utf-8").trim() || null;
@@ -17706,7 +17730,7 @@ function readMarker(repoRoot, sessionId) {
17706
17730
  function deleteMarker(repoRoot, sessionId) {
17707
17731
  for (const path of [markerPath(repoRoot, sessionId), dedupePath(repoRoot, sessionId)]) {
17708
17732
  try {
17709
- if (existsSync3(path))
17733
+ if (existsSync4(path))
17710
17734
  unlinkSync3(path);
17711
17735
  } catch {
17712
17736
  }
@@ -17714,7 +17738,7 @@ function deleteMarker(repoRoot, sessionId) {
17714
17738
  }
17715
17739
  function readDedupe(repoRoot, sessionId) {
17716
17740
  const path = dedupePath(repoRoot, sessionId);
17717
- if (!existsSync3(path))
17741
+ if (!existsSync4(path))
17718
17742
  return null;
17719
17743
  try {
17720
17744
  const parsed = JSON.parse(readFileSync7(path, "utf-8"));
@@ -17968,9 +17992,9 @@ async function readHookStdin() {
17968
17992
  }
17969
17993
  }
17970
17994
  function readStdin2() {
17971
- return new Promise((resolve2) => {
17995
+ return new Promise((resolve3) => {
17972
17996
  if (process.stdin.isTTY) {
17973
- resolve2("");
17997
+ resolve3("");
17974
17998
  return;
17975
17999
  }
17976
18000
  const chunks = [];
@@ -17980,7 +18004,7 @@ function readStdin2() {
17980
18004
  return;
17981
18005
  settled = true;
17982
18006
  clearTimeout(timer);
17983
- resolve2(Buffer.concat(chunks).toString("utf-8"));
18007
+ resolve3(Buffer.concat(chunks).toString("utf-8"));
17984
18008
  };
17985
18009
  const timer = setTimeout(done, 2e3);
17986
18010
  process.stdin.on("data", (chunk) => chunks.push(chunk));
@@ -18002,14 +18026,14 @@ async function runStop() {
18002
18026
  }
18003
18027
 
18004
18028
  // dist/resolve-engagement.js
18005
- import { existsSync as existsSync4, readFileSync as readFileSync8, realpathSync as realpathSync3 } from "node:fs";
18029
+ import { existsSync as existsSync5, readFileSync as readFileSync8, realpathSync as realpathSync3 } from "node:fs";
18006
18030
  import { join as join8, sep as sep2 } from "node:path";
18007
18031
  import { homedir as homedir4 } from "node:os";
18008
18032
  function defaultRegistryPath() {
18009
18033
  return join8(homedir4(), ".halfcycle", "registry.json");
18010
18034
  }
18011
18035
  function readRegistry(registryPath = defaultRegistryPath()) {
18012
- if (!existsSync4(registryPath))
18036
+ if (!existsSync5(registryPath))
18013
18037
  return {};
18014
18038
  try {
18015
18039
  const parsed = JSON.parse(readFileSync8(registryPath, "utf-8"));
@@ -18061,39 +18085,240 @@ function runResolveEngagement(cwd, registryPath) {
18061
18085
 
18062
18086
  // dist/refresh-credential.js
18063
18087
  var UNAUTHORIZED2 = 401;
18064
- async function runRefreshCredential(opts) {
18088
+ var LIVE_PROBE_STATUSES = /* @__PURE__ */ new Set([200, 403]);
18089
+ async function refreshCredentialToken(opts) {
18090
+ const source = opts?.source;
18065
18091
  if (readNeverConfiguredReason() === "not-this-account")
18066
- return 0;
18067
- if (!readEnvFilePath())
18068
- return 0;
18069
- const configResult = readConfig();
18092
+ return { token: void 0 };
18093
+ if (!readEnvFilePath(source))
18094
+ return { token: void 0 };
18095
+ const configResult = readConfig(source);
18070
18096
  if (!configResult.config)
18071
- return 0;
18097
+ return { token: void 0 };
18072
18098
  const { guardServiceToken, guardEngagementId } = configResult.config;
18073
- const controlOrigin = readControlOrigin().origin;
18099
+ const controlOrigin = readControlOrigin(source).origin;
18074
18100
  const fetchFn = opts?.fetchFn ?? globalThis.fetch;
18075
18101
  const accountCredential = readAccountCredential(controlOrigin, opts?.home);
18076
18102
  if (accountCredential)
18077
18103
  await probeAccount(controlOrigin, accountCredential, fetchFn);
18078
18104
  const probe = await probeAccount(controlOrigin, guardServiceToken, fetchFn);
18079
- if (!probe.reached || probe.status !== UNAUTHORIZED2)
18080
- return 0;
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
+ }
18081
18110
  const refreshed = await refreshEngagementCredential({
18082
18111
  engagementId: guardEngagementId,
18083
18112
  refusedToken: guardServiceToken,
18084
18113
  fetchFn,
18085
- home: opts?.home
18114
+ home: opts?.home,
18115
+ source
18086
18116
  });
18087
18117
  if (!refreshed.ok) {
18088
18118
  const write = opts?.write ?? ((text) => process.stdout.write(text));
18089
18119
  write(credentialRenewalFailedMessage(refreshed.remedy));
18120
+ return { token: void 0 };
18090
18121
  }
18122
+ return { token: refreshed.token };
18123
+ }
18124
+ async function runRefreshCredential(opts) {
18125
+ await refreshCredentialToken(opts);
18091
18126
  return 0;
18092
18127
  }
18093
18128
 
18129
+ // dist/mcp-headers.js
18130
+ import { readFileSync as readFileSync9 } from "node:fs";
18131
+ import { homedir as homedir5 } from "node:os";
18132
+ import { dirname as dirname4, resolve as resolve2 } from "node:path";
18133
+ function headerLine(bearer) {
18134
+ return `${JSON.stringify({ Authorization: `Bearer ${bearer}` })}
18135
+ `;
18136
+ }
18137
+ function projectRootFromBinary(binaryPath) {
18138
+ const depth = VENDORED_BIN_REL.split("/").length - 1;
18139
+ return resolve2(dirname4(binaryPath), ...Array(depth).fill(".."));
18140
+ }
18141
+ function readPinnedId(root) {
18142
+ let raw;
18143
+ try {
18144
+ raw = readFileSync9(resolve2(root, BUNDLE_PIN_REL), "utf-8");
18145
+ } catch {
18146
+ return { problem: "no-pin" };
18147
+ }
18148
+ let pin;
18149
+ try {
18150
+ pin = JSON.parse(raw);
18151
+ } catch {
18152
+ return { problem: "no-id" };
18153
+ }
18154
+ const id = typeof pin === "object" && pin !== null ? pin[PIN_ENGAGEMENT_ID_FIELD] : void 0;
18155
+ return typeof id === "string" && id !== "" ? { id } : { problem: "no-id" };
18156
+ }
18157
+ function resolveStore(root, home) {
18158
+ const pinned = readPinnedId(root);
18159
+ if ("problem" in pinned)
18160
+ return { ok: false, problem: pinned.problem };
18161
+ if (!isEngagementId(pinned.id))
18162
+ return { ok: false, problem: "bad-id" };
18163
+ const notThisAccount = hasNotThisAccountMarker(pinned.id, home);
18164
+ const path = composeEngagementEnvPath(pinned.id, home);
18165
+ const values = readEngagementStore(path);
18166
+ if (values === void 0) {
18167
+ return { ok: false, problem: notThisAccount ? "not-this-account" : "no-credential", engagementId: pinned.id };
18168
+ }
18169
+ return { ok: true, path, values };
18170
+ }
18171
+ function urlOrigin(url2) {
18172
+ if (/[\s\u0000-\u001f\u007f-\u009f]/u.test(url2))
18173
+ return void 0;
18174
+ const match = /^([A-Za-z][A-Za-z0-9+.-]*:\/\/[^/?#\\]*)/.exec(url2);
18175
+ return match ? match[1].toLowerCase() : void 0;
18176
+ }
18177
+ function shownUrl(url2) {
18178
+ return url2.replace(/[^\x21-\x7e]/g, "").slice(0, 200);
18179
+ }
18180
+ function sentences(lines) {
18181
+ return lines.map((line) => `halfcycle: ${line}
18182
+ `).join("");
18183
+ }
18184
+ function badIdSentence(root) {
18185
+ return sentences([
18186
+ `${resolve2(root, BUNDLE_PIN_REL)} names an engagement id that is not a Halfcycle project id,`,
18187
+ 'so no credential was read. Delete that file and run "npx halfcycle" again: this repository',
18188
+ "is then set up as a new Halfcycle project."
18189
+ ]);
18190
+ }
18191
+ function storeProblemSentence(problem, root, home, engagementId) {
18192
+ switch (problem) {
18193
+ case "no-pin":
18194
+ return sentences([
18195
+ `no ${BUNDLE_PIN_REL} in ${root}, so this repository has no Halfcycle engagement.`,
18196
+ 'run "npx halfcycle" here to install one.'
18197
+ ]);
18198
+ case "no-id":
18199
+ return sentences([
18200
+ `${resolve2(root, BUNDLE_PIN_REL)} names no engagement id.`,
18201
+ 'run "npx halfcycle" here to rewrite it.'
18202
+ ]);
18203
+ case "bad-id":
18204
+ return badIdSentence(root);
18205
+ case "not-this-account":
18206
+ return sentences([
18207
+ "this project belongs to another Halfcycle account, so this machine has no",
18208
+ "credential for it and the Halfcycle tools are not available here. Nothing is",
18209
+ "broken \u2014 ask the project's owner for view access, or sign in as the account",
18210
+ 'that owns it and run "npx halfcycle" again.'
18211
+ ]);
18212
+ case "no-credential":
18213
+ return sentences([
18214
+ `this machine holds no credential for engagement ${engagementId ?? ""}.`,
18215
+ `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.`
18216
+ ]);
18217
+ }
18218
+ }
18219
+ async function refreshWithinDeadline(source, opts, writeErr) {
18220
+ let timer;
18221
+ const deadline = new Promise((settle) => {
18222
+ timer = setTimeout(() => settle(void 0), opts.deadlineMs ?? CONTROL_TIMEOUT_MS);
18223
+ });
18224
+ const refresh = refreshCredentialToken({
18225
+ fetchFn: opts.fetchFn,
18226
+ home: opts.home,
18227
+ write: writeErr,
18228
+ source
18229
+ }).then((result) => result.token, () => void 0);
18230
+ try {
18231
+ return await Promise.race([refresh, deadline]);
18232
+ } finally {
18233
+ clearTimeout(timer);
18234
+ }
18235
+ }
18236
+ async function decide(opts, writeErr) {
18237
+ const home = opts.home ?? homedir5();
18238
+ const root = projectRootFromBinary(opts.binaryPath);
18239
+ const environmentToken = readEnvironmentToken();
18240
+ const store = resolveStore(root, home);
18241
+ let storedToken;
18242
+ if (environmentToken === void 0) {
18243
+ if (!store.ok) {
18244
+ writeErr(storeProblemSentence(store.problem, root, home, store.engagementId));
18245
+ return headerLine(store.problem === "not-this-account" ? NOT_THIS_ACCOUNT_PLACEHOLDER : NO_CREDENTIAL_PLACEHOLDER);
18246
+ }
18247
+ storedToken = storedEngagementToken(store.values);
18248
+ if (storedToken === void 0) {
18249
+ writeErr(sentences([
18250
+ `GUARD_SERVICE_TOKEN is absent or empty in ${store.path}.`,
18251
+ `run "npx halfcycle" in this repository to rewrite this machine's credential.`
18252
+ ]));
18253
+ return headerLine(NO_CREDENTIAL_PLACEHOLDER);
18254
+ }
18255
+ }
18256
+ const requested = readMcpServerUrl();
18257
+ const recorded = store.ok ? store.values["HALFCYCLE_MCP_URL"] : void 0;
18258
+ if (requested === void 0 && store.ok) {
18259
+ writeErr(sentences([
18260
+ "Claude Code did not say which server it is connecting to, so the Halfcycle credential was not sent.",
18261
+ "update Claude Code, then reconnect."
18262
+ ]));
18263
+ return headerLine(UPDATE_CLAUDE_CODE_PLACEHOLDER);
18264
+ }
18265
+ if (!recorded) {
18266
+ if (!store.ok && store.problem === "bad-id") {
18267
+ writeErr(badIdSentence(root));
18268
+ } else {
18269
+ writeErr(sentences([
18270
+ "this machine has no Halfcycle server address recorded for this repository, so the credential was not sent.",
18271
+ 'run "npx halfcycle" in this repository on this machine to record it.'
18272
+ ]));
18273
+ }
18274
+ return headerLine(NO_CREDENTIAL_PLACEHOLDER);
18275
+ }
18276
+ const want = urlOrigin(recorded);
18277
+ const got = requested === void 0 ? void 0 : urlOrigin(requested);
18278
+ if (want === void 0 || got !== want) {
18279
+ writeErr(sentences([
18280
+ `.mcp.json asks for this repository's Halfcycle credential to be sent to ${shownUrl(requested ?? "")},`,
18281
+ `which is not this machine's Halfcycle server (${want ?? "none recorded"}). The credential was NOT sent.`,
18282
+ 'if nobody meant to change .mcp.json, treat that change as suspect; "npx halfcycle" restores the entry.'
18283
+ ]));
18284
+ return headerLine(NO_CREDENTIAL_PLACEHOLDER);
18285
+ }
18286
+ if (environmentToken !== void 0)
18287
+ return headerLine(environmentToken);
18288
+ if (!store.ok || storedToken === void 0)
18289
+ return headerLine(NO_CREDENTIAL_PLACEHOLDER);
18290
+ const source = {
18291
+ HALFCYCLE_ENV_FILE: store.path,
18292
+ GUARD_SERVICE_TOKEN: storedToken,
18293
+ GUARD_ENGAGEMENT_ID: store.values["GUARD_ENGAGEMENT_ID"],
18294
+ HALFCYCLE_SERVICE_URL: store.values["HALFCYCLE_SERVICE_URL"]
18295
+ };
18296
+ const refreshed = await refreshWithinDeadline(source, opts, writeErr);
18297
+ return headerLine(refreshed ?? storedToken);
18298
+ }
18299
+ async function mcpHeaders(opts) {
18300
+ const writeErr = opts.writeErr ?? ((text) => void process.stderr.write(text));
18301
+ try {
18302
+ return await decide(opts, writeErr);
18303
+ } catch (err) {
18304
+ try {
18305
+ writeErr(sentences([
18306
+ `the credential could not be read: ${err instanceof Error ? err.message : String(err)}`,
18307
+ `run "npx halfcycle" in this repository to rewrite this machine's credential.`
18308
+ ]));
18309
+ } catch {
18310
+ return headerLine(NO_CREDENTIAL_PLACEHOLDER);
18311
+ }
18312
+ return headerLine(NO_CREDENTIAL_PLACEHOLDER);
18313
+ }
18314
+ }
18315
+
18316
+ // dist/bin.js
18317
+ import { fileURLToPath } from "node:url";
18318
+
18094
18319
  // dist/capture.js
18095
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, existsSync as existsSync5 } from "node:fs";
18096
- import { dirname as dirname4 } from "node:path";
18320
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, existsSync as existsSync6 } from "node:fs";
18321
+ import { dirname as dirname5 } from "node:path";
18097
18322
  function runCapture(provider, endpoint, opts = {}) {
18098
18323
  const repoRoot = opts.repoRoot ?? process.cwd();
18099
18324
  const raw = opts.input ?? readInputFile(opts.fromFile);
@@ -18128,7 +18353,7 @@ function runCapture(provider, endpoint, opts = {}) {
18128
18353
  const fixture = fixturePath(repoRoot, provider, slug);
18129
18354
  const manifestFile = manifestPath(repoRoot);
18130
18355
  try {
18131
- mkdirSync3(dirname4(fixture), { recursive: true });
18356
+ mkdirSync3(dirname5(fixture), { recursive: true });
18132
18357
  writeFileSync4(fixture, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
18133
18358
  } catch (err) {
18134
18359
  const msg = err instanceof Error ? err.message : String(err);
@@ -18160,7 +18385,7 @@ function readInputFile(fromFile) {
18160
18385
  if (!fromFile)
18161
18386
  return void 0;
18162
18387
  try {
18163
- return readFileSync9(fromFile, "utf-8");
18388
+ return readFileSync10(fromFile, "utf-8");
18164
18389
  } catch {
18165
18390
  return void 0;
18166
18391
  }
@@ -18184,6 +18409,11 @@ async function main() {
18184
18409
  process.exit(code);
18185
18410
  return;
18186
18411
  }
18412
+ if (cmd === "mcp-headers") {
18413
+ const line = await mcpHeaders({ binaryPath: fileURLToPath(import.meta.url) });
18414
+ process.stdout.write(line, () => process.exit(0));
18415
+ return;
18416
+ }
18187
18417
  if (cmd === "resolve-engagement") {
18188
18418
  const cwd = sub ?? process.cwd();
18189
18419
  const code = runResolveEngagement(cwd);
@@ -18249,16 +18479,17 @@ Commit the fixture; the next guard evaluation of this endpoint will pass.
18249
18479
  halfcycle-runner hook stop
18250
18480
  halfcycle-runner hook subagent-stop
18251
18481
  halfcycle-runner refresh-credential
18482
+ halfcycle-runner mcp-headers
18252
18483
  halfcycle-runner resolve-engagement <cwd>
18253
18484
  halfcycle-runner capture <provider> <endpoint> [--from <file>]
18254
18485
  `);
18255
18486
  process.exit(1);
18256
18487
  }
18257
18488
  function readStdin3() {
18258
- return new Promise((resolve2, reject) => {
18489
+ return new Promise((resolve3, reject) => {
18259
18490
  const chunks = [];
18260
18491
  process.stdin.on("data", (chunk) => chunks.push(chunk));
18261
- process.stdin.on("end", () => resolve2(Buffer.concat(chunks).toString("utf-8")));
18492
+ process.stdin.on("end", () => resolve3(Buffer.concat(chunks).toString("utf-8")));
18262
18493
  process.stdin.on("error", reject);
18263
18494
  });
18264
18495
  }
@@ -18,6 +18,7 @@
18
18
  *
19
19
  * INV-001: Node builtins and modules inside this package only.
20
20
  */
21
+ import { type ClaudeVersionProbeDeps } from './claude-version-probe.js';
21
22
  import { type BannerContent } from './banner.js';
22
23
  /**
23
24
  * The brand's front door.
@@ -50,9 +51,12 @@ export declare function bundleVersion(): string | undefined;
50
51
  * treating it as a problem would put a warning on a correct install. This is the second
51
52
  * place in the CLI that asks; they are deliberately separate calls because that one exists
52
53
  * to WARN about an old version and this one exists to state a fact, and collapsing them
53
- * would make the banner responsible for a compatibility check.
54
+ * would make the banner responsible for a compatibility check. Both go through
55
+ * `probeClaudeCodeVersion` (`claude-version-probe.ts`), which is what runs `claude.cmd`
56
+ * through `cmd.exe` on Windows; `deps` exists so a test can inject the platform and the exec
57
+ * call rather than depending on this actually running on Windows.
54
58
  */
55
- export declare function claudeCodeVersion(): string | undefined;
59
+ export declare function claudeCodeVersion(deps?: ClaudeVersionProbeDeps): string | undefined;
56
60
  /** Everything the fact collector is allowed to reach outside itself, so a test can hand it
57
61
  * a world instead of standing in one. */
58
62
  export interface FactProbes {
@@ -1 +1 @@
1
- {"version":3,"file":"banner-facts.d.ts","sourceRoot":"","sources":["../src/banner-facts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AASH,OAAO,EAAkB,KAAK,aAAa,EAAmB,MAAM,aAAa,CAAC;AAElF;;;;;;;;;GASG;AACH,eAAO,MAAM,SAAS,iBAAiB,CAAC;AAExC;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,EAAE,SAAS,MAAM,EAGpC,CAAC;AAEF,uFAAuF;AACvF,eAAO,MAAM,cAAc,EAAE,SAAS,MAAM,EAI3C,CAAC;AAEF,iFAAiF;AACjF,wBAAgB,aAAa,IAAI,MAAM,GAAG,SAAS,CAOlD;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,GAAG,SAAS,CAWtD;AAED;0CAC0C;AAC1C,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,aAAa,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACjD,QAAQ,CAAC,iBAAiB,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACrD,QAAQ,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IAC1D,QAAQ,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACrD,QAAQ,CAAC,gBAAgB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IAC/D,4EAA4E;IAC5E,QAAQ,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,KAAK,MAAM,GAAG,SAAS,CAAC;CACtE;AAED,2FAA2F;AAC3F,eAAO,MAAM,WAAW,EAAE,UAqCzB,CAAC;AAQF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,EAClB,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,MAAM,GAAE,UAAwB,GAC/B,aAAa,CAsCf"}
1
+ {"version":3,"file":"banner-facts.d.ts","sourceRoot":"","sources":["../src/banner-facts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAQH,OAAO,EAA0B,KAAK,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAChG,OAAO,EAAkB,KAAK,aAAa,EAAmB,MAAM,aAAa,CAAC;AAElF;;;;;;;;;GASG;AACH,eAAO,MAAM,SAAS,iBAAiB,CAAC;AAExC;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,EAAE,SAAS,MAAM,EAGpC,CAAC;AAEF,uFAAuF;AACvF,eAAO,MAAM,cAAc,EAAE,SAAS,MAAM,EAI3C,CAAC;AAEF,iFAAiF;AACjF,wBAAgB,aAAa,IAAI,MAAM,GAAG,SAAS,CAOlD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,GAAE,sBAA2B,GAAG,MAAM,GAAG,SAAS,CAEvF;AAED;0CAC0C;AAC1C,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,aAAa,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACjD,QAAQ,CAAC,iBAAiB,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACrD,QAAQ,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IAC1D,QAAQ,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACrD,QAAQ,CAAC,gBAAgB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IAC/D,4EAA4E;IAC5E,QAAQ,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,KAAK,MAAM,GAAG,SAAS,CAAC;CACtE;AAED,2FAA2F;AAC3F,eAAO,MAAM,WAAW,EAAE,UAqCzB,CAAC;AAQF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,EAClB,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,MAAM,GAAE,UAAwB,GAC/B,aAAa,CAsCf"}