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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +3 -1
- package/bin/bin.bundle.mjs +260 -81
- package/dist/bin.js +162 -138
- package/dist/bin.js.map +3 -3
- package/dist/device-signin.d.ts +93 -17
- package/dist/device-signin.d.ts.map +1 -1
- package/dist/engagement-credential.d.ts +8 -4
- package/dist/engagement-credential.d.ts.map +1 -1
- package/dist/index.js +40 -63
- package/dist/index.js.map +3 -3
- package/dist/install.d.ts +2 -8
- package/dist/install.d.ts.map +1 -1
- package/dist/loopback-signin.d.ts +8 -2
- package/dist/loopback-signin.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "halfcycle",
|
|
3
|
-
"version": "0.3.
|
|
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
|
|
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
|
|
package/bin/bin.bundle.mjs
CHANGED
|
@@ -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(
|
|
15352
|
-
writeSync(2, neverConfiguredReport(
|
|
15371
|
+
function emitNeverConfigured(exitCode, reason) {
|
|
15372
|
+
writeSync(2, neverConfiguredReport(reason));
|
|
15353
15373
|
process.exitCode = exitCode;
|
|
15354
15374
|
}
|
|
15355
|
-
var
|
|
15375
|
+
var SIGN_IN_COMMAND = '"npx halfcycle"';
|
|
15376
|
+
var HOOK_REMEDY = `Run ${SIGN_IN_COMMAND} in the project folder to sign in.`;
|
|
15377
|
+
function namesTheSignInCommand(text) {
|
|
15378
|
+
return text.includes(SIGN_IN_COMMAND);
|
|
15379
|
+
}
|
|
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 = "
|
|
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
|
-
|
|
15366
|
-
|
|
15367
|
-
|
|
15368
|
-
${NOT_A_SERVICE_OUTAGE}
|
|
15389
|
+
var NO_SIGN_IN_HERE = `${NEVER_CONFIGURED_HEADLINE} in this project on this machine: there is no Halfcycle sign-in for this project here`;
|
|
15390
|
+
function neverConfiguredMessage(remedy, label = "[Halfcycle]") {
|
|
15391
|
+
return `${label} ${NO_SIGN_IN_HERE}. Edits made in this session won't be checked.
|
|
15392
|
+
${NOT_A_SERVICE_OUTAGE}
|
|
15369
15393
|
${remedy}
|
|
15370
15394
|
`;
|
|
15371
15395
|
}
|
|
15372
15396
|
function ciNeverConfiguredMessage() {
|
|
15373
|
-
return `[Halfcycle CI] ${NEVER_CONFIGURED_HEADLINE}
|
|
15397
|
+
return `[Halfcycle CI] ${NEVER_CONFIGURED_HEADLINE} in this job: it ${CI_CANNOT_PROVE_PROJECT}, so it checked nothing. ${CI_REMEDY_LEAD}
|
|
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(
|
|
15406
|
+
function neverConfiguredReport(reason, label = "[Halfcycle]") {
|
|
15383
15407
|
switch (reason) {
|
|
15384
15408
|
case "no-credential-yet":
|
|
15385
|
-
return neverConfiguredMessage(
|
|
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(
|
|
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((
|
|
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", () =>
|
|
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(
|
|
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((
|
|
18036
|
+
return new Promise((resolve4) => {
|
|
17996
18037
|
if (process.stdin.isTTY) {
|
|
17997
|
-
|
|
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
|
-
|
|
18048
|
+
resolve4(Buffer.concat(chunks).toString("utf-8"));
|
|
18008
18049
|
};
|
|
18009
|
-
const timer = setTimeout(done,
|
|
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
|
-
|
|
18088
|
-
|
|
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
|
|
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
|
|
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
|
|
18259
|
+
return NO_CREDENTIAL_PLACEHOLDER;
|
|
18254
18260
|
}
|
|
18255
18261
|
}
|
|
18256
|
-
const requested =
|
|
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
|
|
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
|
|
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
|
|
18290
|
+
return NO_CREDENTIAL_PLACEHOLDER;
|
|
18285
18291
|
}
|
|
18286
18292
|
if (environmentToken !== void 0)
|
|
18287
|
-
return
|
|
18293
|
+
return environmentToken;
|
|
18288
18294
|
if (!store.ok || storedToken === void 0)
|
|
18289
|
-
return
|
|
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
|
|
18297
|
-
return
|
|
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 ??
|
|
18306
|
+
const writeErr = opts.writeErr ?? defaultWriteErr;
|
|
18301
18307
|
try {
|
|
18302
|
-
return await
|
|
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
|
|
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
|
|
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
|
|
18409
|
-
process.exit(
|
|
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((
|
|
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", () =>
|
|
18671
|
+
process.stdin.on("end", () => resolve4(Buffer.concat(chunks).toString("utf-8")));
|
|
18493
18672
|
process.stdin.on("error", reject);
|
|
18494
18673
|
});
|
|
18495
18674
|
}
|