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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +13 -0
- package/bin/bin.bundle.mjs +274 -43
- package/dist/banner-facts.d.ts +6 -2
- package/dist/banner-facts.d.ts.map +1 -1
- package/dist/bin.js +273 -292
- 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/engagement-credential.d.ts +31 -23
- package/dist/engagement-credential.d.ts.map +1 -1
- package/dist/index.js +149 -227
- package/dist/index.js.map +1 -1
- package/dist/install-manifest.d.ts +32 -4
- package/dist/install-manifest.d.ts.map +1 -1
- package/dist/install.d.ts +52 -16
- package/dist/install.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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "halfcycle",
|
|
3
|
-
"version": "0.3.
|
|
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.
|
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) {
|
|
@@ -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 =
|
|
14767
|
-
const guardEngagementId =
|
|
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(
|
|
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(
|
|
14802
|
+
function readControlOrigin(env = process.env) {
|
|
14803
|
+
return resolveControlOrigin(env);
|
|
14801
14804
|
}
|
|
14802
|
-
function readEnvFilePath() {
|
|
14803
|
-
const raw =
|
|
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
|
-
|
|
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
|
|
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) =>
|
|
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((
|
|
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", () =>
|
|
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
|
|
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 (!
|
|
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 (
|
|
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 (!
|
|
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((
|
|
17995
|
+
return new Promise((resolve3) => {
|
|
17972
17996
|
if (process.stdin.isTTY) {
|
|
17973
|
-
|
|
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
|
-
|
|
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
|
|
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 (!
|
|
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
|
-
|
|
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
|
|
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
|
|
18096
|
-
import { dirname as
|
|
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(
|
|
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
|
|
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((
|
|
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", () =>
|
|
18492
|
+
process.stdin.on("end", () => resolve3(Buffer.concat(chunks).toString("utf-8")));
|
|
18262
18493
|
process.stdin.on("error", reject);
|
|
18263
18494
|
});
|
|
18264
18495
|
}
|
package/dist/banner-facts.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|