halfcycle 0.4.1 → 0.4.2
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 +1 -1
- package/bin/bin.bundle.mjs +26 -19
- package/dist/cli.js +67 -40
- package/dist/cli.js.map +1 -1
- package/dist/confirm-identity.d.ts +4 -19
- package/dist/confirm-identity.d.ts.map +1 -1
- package/dist/create-engagement.d.ts +6 -4
- package/dist/create-engagement.d.ts.map +1 -1
- package/dist/device-signin.d.ts +35 -2
- package/dist/device-signin.d.ts.map +1 -1
- package/dist/identity.d.ts +7 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/index.js +20 -7
- package/dist/index.js.map +1 -1
- package/dist/install.d.ts +21 -2
- package/dist/install.d.ts.map +1 -1
- package/dist/pre-create-checks.d.ts +27 -0
- package/dist/pre-create-checks.d.ts.map +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.4.
|
|
3
|
+
"version": "0.4.2",
|
|
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
|
@@ -165,7 +165,7 @@ Nothing you already had is changed. Existing settings are preserved, and a name
|
|
|
165
165
|
## Requirements
|
|
166
166
|
|
|
167
167
|
- Claude Code **2.1.118+**
|
|
168
|
-
- Node
|
|
168
|
+
- Node 18+
|
|
169
169
|
- A git repository
|
|
170
170
|
- On Windows, in PowerShell, use `npx.cmd` instead of `npx` (PowerShell's default settings block the `npx` script; `npx.cmd` always works, with nothing to change).
|
|
171
171
|
|
package/bin/bin.bundle.mjs
CHANGED
|
@@ -15376,6 +15376,9 @@ function infraWarningText() {
|
|
|
15376
15376
|
function localFaultText(message) {
|
|
15377
15377
|
return `[Halfcycle] WARNING: Local fault \u2014 ${message}. The guard service was not contacted. Session continues (fail-open).`;
|
|
15378
15378
|
}
|
|
15379
|
+
function noRepositoryText(failure) {
|
|
15380
|
+
return failure.gitMissing ? "[Halfcycle] WARNING: Git is not installed on this machine, and Halfcycle's checks need it, so nothing will be checked in this session. Install Git, then start a new session." : localFaultText(`Could not find git repo root: ${failure.message}`);
|
|
15381
|
+
}
|
|
15379
15382
|
function contractFaultText(message) {
|
|
15380
15383
|
return `[Halfcycle] WARNING: Guard response could not be understood \u2014 ${message}
|
|
15381
15384
|
This is not a service outage: the guard service answered. The suspect is this repository's own installed halfcycle runner, which is out of date with what the service now sends. Update it: run "npx halfcycle" in this repository, then retry. Session continues (fail-open).`;
|
|
@@ -15711,7 +15714,7 @@ async function attempt(url2, token, request, fetchFn, retry) {
|
|
|
15711
15714
|
|
|
15712
15715
|
// dist/git.js
|
|
15713
15716
|
import { spawnSync } from "node:child_process";
|
|
15714
|
-
import { readFileSync } from "node:fs";
|
|
15717
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
15715
15718
|
import { relative } from "node:path";
|
|
15716
15719
|
var COLOR_AWARE_SUBCOMMANDS = /* @__PURE__ */ new Set(["diff", "show", "log"]);
|
|
15717
15720
|
function git(args2, cwd, timeoutMs) {
|
|
@@ -15722,7 +15725,8 @@ function git(args2, cwd, timeoutMs) {
|
|
|
15722
15725
|
...timeoutMs === void 0 ? {} : { timeout: timeoutMs }
|
|
15723
15726
|
});
|
|
15724
15727
|
if (result.error) {
|
|
15725
|
-
|
|
15728
|
+
const missing = result.error.code === "ENOENT" && existsSync(cwd);
|
|
15729
|
+
return { ok: false, message: `git error: ${result.error.message}`, ...missing ? { gitMissing: true } : {} };
|
|
15726
15730
|
}
|
|
15727
15731
|
if (result.status !== 0) {
|
|
15728
15732
|
const stderr = result.stderr?.trim() ?? "";
|
|
@@ -16605,7 +16609,7 @@ function resolveWireFields(lines, decls) {
|
|
|
16605
16609
|
}
|
|
16606
16610
|
|
|
16607
16611
|
// dist/fixtures-manifest.js
|
|
16608
|
-
import { existsSync, mkdirSync, readFileSync as readFileSync3, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
16612
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync3, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
16609
16613
|
import { dirname, join as join2 } from "node:path";
|
|
16610
16614
|
function manifestPath(repoRoot) {
|
|
16611
16615
|
return join2(repoRoot, "test", "fixtures", "captured", "manifest.json");
|
|
@@ -16644,7 +16648,7 @@ function upsertManifest(repoRoot, entry) {
|
|
|
16644
16648
|
}
|
|
16645
16649
|
function removeFixtureIfExists(path) {
|
|
16646
16650
|
try {
|
|
16647
|
-
if (
|
|
16651
|
+
if (existsSync2(path))
|
|
16648
16652
|
unlinkSync(path);
|
|
16649
16653
|
} catch {
|
|
16650
16654
|
}
|
|
@@ -17266,7 +17270,7 @@ function cleanRunSummary(verdict, scope) {
|
|
|
17266
17270
|
}
|
|
17267
17271
|
|
|
17268
17272
|
// dist/credential-store.js
|
|
17269
|
-
import { chmodSync, existsSync as
|
|
17273
|
+
import { chmodSync, existsSync as existsSync3, readFileSync as readFileSync5, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2, realpathSync } from "node:fs";
|
|
17270
17274
|
import { homedir as homedir2, platform } from "node:os";
|
|
17271
17275
|
import { dirname as dirname3, join as join5 } from "node:path";
|
|
17272
17276
|
var REFRESHED_TOKEN_KEYS = ["HALFCYCLE_TOKEN", "GUARD_SERVICE_TOKEN"];
|
|
@@ -17275,7 +17279,7 @@ function composeEngagementEnvPath(engagementId, home) {
|
|
|
17275
17279
|
return join5(home ?? homedir2(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, ENGAGEMENT_ENV_FILENAME);
|
|
17276
17280
|
}
|
|
17277
17281
|
function hasNotThisAccountMarker(engagementId, home) {
|
|
17278
|
-
return
|
|
17282
|
+
return existsSync3(join5(home ?? homedir2(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, NOT_THIS_ACCOUNT_MARKER_FILENAME));
|
|
17279
17283
|
}
|
|
17280
17284
|
function readAccountCredential(controlOrigin, home) {
|
|
17281
17285
|
let raw;
|
|
@@ -17467,7 +17471,7 @@ async function evaluateWithRefresh(opts) {
|
|
|
17467
17471
|
|
|
17468
17472
|
// dist/hooks/marker.js
|
|
17469
17473
|
import { createHash } from "node:crypto";
|
|
17470
|
-
import { existsSync as
|
|
17474
|
+
import { existsSync as existsSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3, readdirSync as readdirSync3, statSync } from "node:fs";
|
|
17471
17475
|
import { tmpdir } from "node:os";
|
|
17472
17476
|
import { join as join6 } from "node:path";
|
|
17473
17477
|
var MARKER_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -17496,7 +17500,7 @@ function dedupePath(repoRoot, sessionId) {
|
|
|
17496
17500
|
}
|
|
17497
17501
|
function readMarker(repoRoot, sessionId) {
|
|
17498
17502
|
const path = markerPath(repoRoot, sessionId);
|
|
17499
|
-
if (!
|
|
17503
|
+
if (!existsSync4(path))
|
|
17500
17504
|
return null;
|
|
17501
17505
|
try {
|
|
17502
17506
|
return readFileSync6(path, "utf-8").trim() || null;
|
|
@@ -17507,7 +17511,7 @@ function readMarker(repoRoot, sessionId) {
|
|
|
17507
17511
|
function deleteMarker(repoRoot, sessionId) {
|
|
17508
17512
|
for (const path of [markerPath(repoRoot, sessionId), dedupePath(repoRoot, sessionId)]) {
|
|
17509
17513
|
try {
|
|
17510
|
-
if (
|
|
17514
|
+
if (existsSync4(path))
|
|
17511
17515
|
unlinkSync3(path);
|
|
17512
17516
|
} catch {
|
|
17513
17517
|
}
|
|
@@ -17527,7 +17531,7 @@ function firstNoRepoNoticeThisSession(folder, sessionId) {
|
|
|
17527
17531
|
}
|
|
17528
17532
|
function readDedupe(repoRoot, sessionId) {
|
|
17529
17533
|
const path = dedupePath(repoRoot, sessionId);
|
|
17530
|
-
if (!
|
|
17534
|
+
if (!existsSync4(path))
|
|
17531
17535
|
return null;
|
|
17532
17536
|
try {
|
|
17533
17537
|
const parsed = JSON.parse(readFileSync6(path, "utf-8"));
|
|
@@ -17599,7 +17603,7 @@ function orphanSweep(repoRoot, refKnown, keepSessionId, now = Date.now()) {
|
|
|
17599
17603
|
}
|
|
17600
17604
|
|
|
17601
17605
|
// dist/seat-configuration.js
|
|
17602
|
-
import { existsSync as
|
|
17606
|
+
import { existsSync as existsSync5, readFileSync as readFileSync7, realpathSync as realpathSync2 } from "node:fs";
|
|
17603
17607
|
import { homedir as homedir3 } from "node:os";
|
|
17604
17608
|
import { join as join7, relative as relative3, resolve } from "node:path";
|
|
17605
17609
|
function gatherSeatConfiguration(repoRoot, read = readSeatConfiguration) {
|
|
@@ -17708,7 +17712,7 @@ function readPathStates(projectDir, repoRoot) {
|
|
|
17708
17712
|
const status = getWiringStatus(HALFCYCLE_HOOK_WIRING_PATHS, projectDir);
|
|
17709
17713
|
if (!status.ok)
|
|
17710
17714
|
return unknown2();
|
|
17711
|
-
const mapped = mapWiringStatus(status.value, prefix, HALFCYCLE_HOOK_WIRING_PATHS, (rel) =>
|
|
17715
|
+
const mapped = mapWiringStatus(status.value, prefix, HALFCYCLE_HOOK_WIRING_PATHS, (rel) => existsSync5(join7(projectDir, rel)));
|
|
17712
17716
|
return mapped ?? unknown2();
|
|
17713
17717
|
}
|
|
17714
17718
|
function mapWiringStatus(output, prefix, paths, onDisk) {
|
|
@@ -17774,7 +17778,7 @@ async function evaluateEdit(reply, readSeat) {
|
|
|
17774
17778
|
const repoRootResult = getRepoRoot(process.cwd());
|
|
17775
17779
|
if (!repoRootResult.ok) {
|
|
17776
17780
|
if (firstNoRepoNoticeThisSession(process.cwd(), input.session_id)) {
|
|
17777
|
-
reply.notice(
|
|
17781
|
+
reply.notice(noRepositoryText(repoRootResult));
|
|
17778
17782
|
}
|
|
17779
17783
|
return;
|
|
17780
17784
|
}
|
|
@@ -17934,7 +17938,7 @@ async function evaluateSession(input, kind, reply, readSeat) {
|
|
|
17934
17938
|
const repoRootResult = getRepoRoot(process.cwd());
|
|
17935
17939
|
if (!repoRootResult.ok) {
|
|
17936
17940
|
if (firstNoRepoNoticeThisSession(process.cwd(), input.session_id)) {
|
|
17937
|
-
reply.notice(
|
|
17941
|
+
reply.notice(noRepositoryText(repoRootResult));
|
|
17938
17942
|
}
|
|
17939
17943
|
return;
|
|
17940
17944
|
}
|
|
@@ -18110,14 +18114,14 @@ async function runStop() {
|
|
|
18110
18114
|
}
|
|
18111
18115
|
|
|
18112
18116
|
// dist/resolve-engagement.js
|
|
18113
|
-
import { existsSync as
|
|
18117
|
+
import { existsSync as existsSync6, readFileSync as readFileSync8, realpathSync as realpathSync3 } from "node:fs";
|
|
18114
18118
|
import { join as join8, sep as sep2 } from "node:path";
|
|
18115
18119
|
import { homedir as homedir4 } from "node:os";
|
|
18116
18120
|
function defaultRegistryPath() {
|
|
18117
18121
|
return join8(homedir4(), ".halfcycle", "registry.json");
|
|
18118
18122
|
}
|
|
18119
18123
|
function readRegistry(registryPath = defaultRegistryPath()) {
|
|
18120
|
-
if (!
|
|
18124
|
+
if (!existsSync6(registryPath))
|
|
18121
18125
|
return {};
|
|
18122
18126
|
try {
|
|
18123
18127
|
const parsed = JSON.parse(readFileSync8(registryPath, "utf-8"));
|
|
@@ -18407,7 +18411,7 @@ async function refreshCredentialToken(opts) {
|
|
|
18407
18411
|
return { token: refreshed.token };
|
|
18408
18412
|
}
|
|
18409
18413
|
var CHECKED_SOURCES = /* @__PURE__ */ new Set(["startup", "resume"]);
|
|
18410
|
-
var CONNECTED_SENTENCE =
|
|
18414
|
+
var CONNECTED_SENTENCE = `Halfcycle's sign-in works and its service answered. If Halfcycle's tools are missing, turn on ${MCP_SERVER_KEY} in /mcp; if /mcp does not list it, run "claude mcp reset-project-choices" in this folder and restart Claude Code.`;
|
|
18411
18415
|
var INITIALIZE_BODY = JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: {} });
|
|
18412
18416
|
function registeredMcpUrl(projectRoot) {
|
|
18413
18417
|
let parsed;
|
|
@@ -18522,8 +18526,11 @@ async function runRefreshCredential(opts) {
|
|
|
18522
18526
|
const remedySaid = parts.some(namesTheSignInCommand);
|
|
18523
18527
|
parts.push(noCoverageStatement(readNeverConfiguredReason(), remedySaid));
|
|
18524
18528
|
}
|
|
18525
|
-
for (const said of renewalSaid)
|
|
18529
|
+
for (const said of renewalSaid) {
|
|
18530
|
+
if (namesTheSignInCommand(said) && parts.some(namesTheSignInCommand))
|
|
18531
|
+
continue;
|
|
18526
18532
|
parts.push(said.trimEnd());
|
|
18533
|
+
}
|
|
18527
18534
|
return parts.length === 0 ? "" : `${nonBlockingReply("SessionStart", parts.join("\n"))}
|
|
18528
18535
|
`;
|
|
18529
18536
|
} catch (err) {
|
|
@@ -18541,7 +18548,7 @@ async function runRefreshCredential(opts) {
|
|
|
18541
18548
|
import { fileURLToPath } from "node:url";
|
|
18542
18549
|
|
|
18543
18550
|
// dist/capture.js
|
|
18544
|
-
import { readFileSync as readFileSync11, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, existsSync as
|
|
18551
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, existsSync as existsSync7 } from "node:fs";
|
|
18545
18552
|
import { dirname as dirname5 } from "node:path";
|
|
18546
18553
|
function runCapture(provider, endpoint, opts = {}) {
|
|
18547
18554
|
const repoRoot = opts.repoRoot ?? process.cwd();
|
package/dist/cli.js
CHANGED
|
@@ -1566,6 +1566,14 @@ function readRootCommit(targetRepoRoot) {
|
|
|
1566
1566
|
function isGitWorkTree(targetRepoRoot) {
|
|
1567
1567
|
return git(targetRepoRoot, ["rev-parse", "--is-inside-work-tree"]) === "true";
|
|
1568
1568
|
}
|
|
1569
|
+
function gitIsInstalled() {
|
|
1570
|
+
try {
|
|
1571
|
+
execFileSync("git", ["--version"], { stdio: "ignore", env: gitEnvForNamedRepo() });
|
|
1572
|
+
return true;
|
|
1573
|
+
} catch {
|
|
1574
|
+
return false;
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1569
1577
|
function readRemote(targetRepoRoot) {
|
|
1570
1578
|
return git(targetRepoRoot, ["remote", "get-url", "origin"]);
|
|
1571
1579
|
}
|
|
@@ -2143,6 +2151,11 @@ function sha256OfWhatThisVersionWrites(commands) {
|
|
|
2143
2151
|
fromFile(CAPTURED_INDEX_REL, capturedIndexScaffold());
|
|
2144
2152
|
return out;
|
|
2145
2153
|
}
|
|
2154
|
+
function bytesAreOurs(path, bytes, recorded, writeSet) {
|
|
2155
|
+
const hash = sha256Hex(bytes);
|
|
2156
|
+
const recordedHash = recorded !== void 0 && "sha256" in recorded ? recorded.sha256 : void 0;
|
|
2157
|
+
return hash === recordedHash || hash === writeSet.generatedSha256.get(path);
|
|
2158
|
+
}
|
|
2146
2159
|
function readPreviousManifest(targetRepo) {
|
|
2147
2160
|
try {
|
|
2148
2161
|
return parseManifest(readFileSync5(join5(targetRepo, INSTALL_MANIFEST_REL), "utf-8"));
|
|
@@ -2291,7 +2304,7 @@ function readPluginManifest() {
|
|
|
2291
2304
|
const raw = readFileSync5(manifestPath, "utf-8");
|
|
2292
2305
|
return JSON.parse(raw);
|
|
2293
2306
|
}
|
|
2294
|
-
function copyManifestCommands(manifest, targetRepo, report, draft) {
|
|
2307
|
+
function copyManifestCommands(manifest, targetRepo, report, draft, writeSet) {
|
|
2295
2308
|
for (const cmd of manifest.commands ?? []) {
|
|
2296
2309
|
const srcFile = join5(BUNDLE_ROOT, ".claude-plugin", cmd.path);
|
|
2297
2310
|
if (!existsSync3(srcFile)) {
|
|
@@ -2300,8 +2313,11 @@ function copyManifestCommands(manifest, targetRepo, report, draft) {
|
|
|
2300
2313
|
const rel = commandStubRel(cmd.name);
|
|
2301
2314
|
const destFile = join5(targetRepo, rel);
|
|
2302
2315
|
const content = readFileSync5(srcFile, "utf-8");
|
|
2303
|
-
const
|
|
2316
|
+
const recorded = draft.files.get(rel);
|
|
2317
|
+
const { outcome, replacedExisting } = writeCollisionSafe(destFile, targetRepo, content, (current) => bytesAreOurs(rel, current, recorded, writeSet));
|
|
2304
2318
|
record(report, outcome, rel);
|
|
2319
|
+
if (replacedExisting)
|
|
2320
|
+
report.replacedPaths.push(rel);
|
|
2305
2321
|
noteFile(draft, targetRepo, rel, outcome);
|
|
2306
2322
|
}
|
|
2307
2323
|
}
|
|
@@ -2316,18 +2332,23 @@ function writeAllowlisted(targetAbsPath, targetRepoRoot, content, writtenPaths)
|
|
|
2316
2332
|
writeFileSync5(targetAbsPath, content, "utf-8");
|
|
2317
2333
|
writtenPaths.push(rel);
|
|
2318
2334
|
}
|
|
2319
|
-
function writeCollisionSafe(targetAbsPath, targetRepoRoot, content) {
|
|
2335
|
+
function writeCollisionSafe(targetAbsPath, targetRepoRoot, content, isOurs) {
|
|
2320
2336
|
const rel = relative(targetRepoRoot, targetAbsPath).replace(/\\/g, "/");
|
|
2321
2337
|
if (!isAllowlisted(rel)) {
|
|
2322
2338
|
throw new Error(`[halfcycle] Write-allowlist violation: attempted to write "${rel}". Only the method surface and scaffolding paths are writable.`);
|
|
2323
2339
|
}
|
|
2340
|
+
let replacedExisting = false;
|
|
2324
2341
|
if (existsSync3(targetAbsPath)) {
|
|
2325
|
-
const current = readFileSync5(targetAbsPath
|
|
2326
|
-
|
|
2342
|
+
const current = readFileSync5(targetAbsPath);
|
|
2343
|
+
if (current.toString("utf-8") === content)
|
|
2344
|
+
return { outcome: "skipped", replacedExisting: false };
|
|
2345
|
+
if (!isOurs(current))
|
|
2346
|
+
return { outcome: "collided", replacedExisting: false };
|
|
2347
|
+
replacedExisting = true;
|
|
2327
2348
|
}
|
|
2328
2349
|
mkdirSync5(dirname3(targetAbsPath), { recursive: true });
|
|
2329
2350
|
writeFileSync5(targetAbsPath, content, "utf-8");
|
|
2330
|
-
return "written";
|
|
2351
|
+
return { outcome: "written", replacedExisting };
|
|
2331
2352
|
}
|
|
2332
2353
|
var INSTALLER_OWNED_DIR = ".halfcycle/";
|
|
2333
2354
|
function writeOwned(targetAbsPath, targetRepoRoot, content) {
|
|
@@ -3003,7 +3024,7 @@ async function install(options) {
|
|
|
3003
3024
|
const writeSet = closedWriteSet();
|
|
3004
3025
|
const draft = startManifest(readPreviousManifest(targetRepo), (path) => writeSet.files.has(path), (path) => writeSet.dirs.has(path));
|
|
3005
3026
|
const dirsBefore = new Set([...writeSet.dirs].filter((dir) => existsSync3(join5(targetRepo, dir))));
|
|
3006
|
-
copyManifestCommands(manifest, targetRepo, report, draft);
|
|
3027
|
+
copyManifestCommands(manifest, targetRepo, report, draft, writeSet);
|
|
3007
3028
|
const vendoredBinSrc = resolveVendoredBinary();
|
|
3008
3029
|
const vendoredBinDest = join5(targetRepo, ".halfcycle", "bin", "bin.bundle.mjs");
|
|
3009
3030
|
const vendoredOutcome = recordOwned(report, vendoredBinDest, targetRepo, readFileSync5(vendoredBinSrc, "utf-8"), VENDORED_BIN_REL);
|
|
@@ -3329,8 +3350,8 @@ var SignInRefused = class extends Error {
|
|
|
3329
3350
|
}
|
|
3330
3351
|
};
|
|
3331
3352
|
var SIGN_IN_TIMED_OUT = "timed-out";
|
|
3332
|
-
var SIGN_IN_TIMEOUT_MESSAGE = "Sign-in timed out: the Halfcycle service stopped answering while this terminal was waiting for the browser.
|
|
3333
|
-
var SIGN_IN_NON_INTERACTIVE_MESSAGE = "Halfcycle needs a signed-in account and this looks like a non-interactive run (CI is set), so there is no browser to sign in with. Set HALFCYCLE_TOKEN in the environment to a Halfcycle credential and run this again.
|
|
3353
|
+
var SIGN_IN_TIMEOUT_MESSAGE = "Sign-in timed out: the Halfcycle service stopped answering while this terminal was waiting for the browser. Check your connection and run the command again.";
|
|
3354
|
+
var SIGN_IN_NON_INTERACTIVE_MESSAGE = "Halfcycle needs a signed-in account and this looks like a non-interactive run (CI is set), so there is no browser to sign in with. Set HALFCYCLE_TOKEN in the environment to a Halfcycle credential and run this again.";
|
|
3334
3355
|
var DEADLINE_SLACK_INTERVALS = 1;
|
|
3335
3356
|
var STILL_WAITING_MS = 15e3;
|
|
3336
3357
|
var DEFAULT_SIGN_IN_REASON = "set up this project";
|
|
@@ -3425,6 +3446,9 @@ function isNonInteractive(env = process.env) {
|
|
|
3425
3446
|
const ci = (env["CI"] ?? "").trim().toLowerCase();
|
|
3426
3447
|
return ci !== "" && ci !== "0" && ci !== "false";
|
|
3427
3448
|
}
|
|
3449
|
+
function isInteractiveRun(env = process.env) {
|
|
3450
|
+
return process.stdin.isTTY === true && !isNonInteractive(env);
|
|
3451
|
+
}
|
|
3428
3452
|
async function startDeviceAuth(serviceUrl) {
|
|
3429
3453
|
const url = `${serviceUrl.replace(/\/+$/, "")}/auth/device/start`;
|
|
3430
3454
|
let res;
|
|
@@ -3435,16 +3459,16 @@ async function startDeviceAuth(serviceUrl) {
|
|
|
3435
3459
|
body: "{}"
|
|
3436
3460
|
});
|
|
3437
3461
|
} catch (err) {
|
|
3438
|
-
throw new SignInRefused(SIGN_IN_TIMED_OUT, `Could not reach the Halfcycle service at ${url} to start a sign-in: ${err instanceof Error ? err.message : String(err)}
|
|
3462
|
+
throw new SignInRefused(SIGN_IN_TIMED_OUT, `Could not reach the Halfcycle service at ${url} to start a sign-in: ${err instanceof Error ? err.message : String(err)}.`);
|
|
3439
3463
|
}
|
|
3440
3464
|
const body = await res.json().catch(() => null);
|
|
3441
3465
|
if (res.status !== 201) {
|
|
3442
3466
|
const refusal = tryRefusal(body);
|
|
3443
|
-
throw new SignInRefused(refusal?.status ?? String(res.status), refusal?.message ?? `Sign-in could not be started: ${url} answered ${res.status}
|
|
3467
|
+
throw new SignInRefused(refusal?.status ?? String(res.status), refusal?.message ?? `Sign-in could not be started: ${url} answered ${res.status}.`);
|
|
3444
3468
|
}
|
|
3445
3469
|
const parsed = deviceAuthStartResponseSchema.safeParse(body);
|
|
3446
3470
|
if (!parsed.success) {
|
|
3447
|
-
throw new SignInRefused(SIGN_IN_TIMED_OUT, `The Halfcycle service at ${url} answered a sign-in start this client cannot read. Either that service is older than this installer, or the address is not a Halfcycle service
|
|
3471
|
+
throw new SignInRefused(SIGN_IN_TIMED_OUT, `The Halfcycle service at ${url} answered a sign-in start this client cannot read. Either that service is older than this installer, or the address is not a Halfcycle service.`);
|
|
3448
3472
|
}
|
|
3449
3473
|
return parsed.data;
|
|
3450
3474
|
}
|
|
@@ -3709,7 +3733,7 @@ async function createEngagementForProject(baseUrl, projectId, credential, derive
|
|
|
3709
3733
|
const body = { derivedName };
|
|
3710
3734
|
return requestEngagementValues("PUT", `${baseUrl.replace(/\/+$/, "")}/engagements/by-project/${encodeURIComponent(projectId)}`, body, credential, {
|
|
3711
3735
|
action: "Project setup",
|
|
3712
|
-
nothingHappened: "
|
|
3736
|
+
nothingHappened: "Halfcycle did not finish setting up this folder",
|
|
3713
3737
|
route: "PUT /engagements/by-project/:projectId",
|
|
3714
3738
|
// FALSE on this call, and only this one: the folder's id is already on
|
|
3715
3739
|
// disk by the time this request is sent. See CallShape.alsoNothingWritten.
|
|
@@ -4159,9 +4183,6 @@ function accountLabel(identity, storedAccountId, verbose2 = false, confirmed = t
|
|
|
4159
4183
|
return `account ${accountId} (no email on record for it)`;
|
|
4160
4184
|
return "the account this credential belongs to, which this service did not name";
|
|
4161
4185
|
}
|
|
4162
|
-
function isInteractiveRun(env = process.env) {
|
|
4163
|
-
return process.stdin.isTTY === true && !isNonInteractive(env);
|
|
4164
|
-
}
|
|
4165
4186
|
function onlyDate(iso) {
|
|
4166
4187
|
if (iso === void 0)
|
|
4167
4188
|
return void 0;
|
|
@@ -4232,7 +4253,7 @@ function armScript(credential, act, label, plainLabel, home) {
|
|
|
4232
4253
|
noTerminalNote: "",
|
|
4233
4254
|
switchOpening: `[halfcycle] Signing you in through your browser \u2014 this install will use that account instead.
|
|
4234
4255
|
`,
|
|
4235
|
-
stopRemedy: `Stopped before anything was
|
|
4256
|
+
stopRemedy: `Stopped before anything was set up. HALFCYCLE_TOKEN in this environment is the credential for ${plainLabel}, and it takes precedence over any saved sign-in \u2014 so unset it, or set it to your own account's credential, before running this again.`,
|
|
4236
4257
|
// The switch is real for THIS run (the new credential is what gets used) and it
|
|
4237
4258
|
// does not persist: `obtainCredential` reads the variable first, so the next run
|
|
4238
4259
|
// is the same variable again. Said here rather than discovered tomorrow.
|
|
@@ -4258,7 +4279,7 @@ function armScript(credential, act, label, plainLabel, home) {
|
|
|
4258
4279
|
`,
|
|
4259
4280
|
switchOpening: `[halfcycle] Forgetting this machine's saved sign-in and signing you in through your browser.
|
|
4260
4281
|
`,
|
|
4261
|
-
stopRemedy: `Stopped before anything was
|
|
4282
|
+
stopRemedy: `Stopped before anything was set up. This machine's saved sign-in is for ${plainLabel}. Run this again and answer "s" to sign in as a different account, or remove ${accountStorePath(home)} to be asked for a sign-in next time.`,
|
|
4262
4283
|
afterSwitch: ""
|
|
4263
4284
|
};
|
|
4264
4285
|
}
|
|
@@ -4415,6 +4436,7 @@ async function joinPinnedEngagement(serviceUrl, engagementId, targetRepo, deps =
|
|
|
4415
4436
|
import { spawnSync } from "node:child_process";
|
|
4416
4437
|
import { existsSync as existsSync4, readFileSync as readFileSync6, realpathSync } from "node:fs";
|
|
4417
4438
|
import { delimiter, dirname as dirname4, join as join6 } from "node:path";
|
|
4439
|
+
var KEPT_FOLDER_ID_LINE = 'Nothing on the Halfcycle service needs undoing, and this folder keeps its project id, so running "npx halfcycle" again picks up where it left off.';
|
|
4418
4440
|
var PreCreateRefused = class extends Error {
|
|
4419
4441
|
constructor(message) {
|
|
4420
4442
|
super(message);
|
|
@@ -4497,18 +4519,22 @@ function claimFolderId(targetRepo) {
|
|
|
4497
4519
|
} catch {
|
|
4498
4520
|
throw new PreCreateRefused(`cannot write to ${targetRepo} \u2014 run "npx halfcycle" in a folder you can write to.`);
|
|
4499
4521
|
}
|
|
4500
|
-
const
|
|
4522
|
+
const folderId = readFolderId(targetRepo);
|
|
4523
|
+
if (folderId === void 0) {
|
|
4524
|
+
throw new PreCreateRefused(`${join6(targetRepo, PROJECT_IDENTITY_REL)} could not be read as a Halfcycle project file \u2014 delete the .halfcycle folder in ${targetRepo} and run "npx halfcycle" again to start a new project here.`);
|
|
4525
|
+
}
|
|
4526
|
+
return folderId;
|
|
4527
|
+
}
|
|
4528
|
+
function readFolderId(targetRepo) {
|
|
4501
4529
|
let folderId;
|
|
4502
4530
|
try {
|
|
4503
|
-
|
|
4531
|
+
const text = readFileSync6(join6(targetRepo, PROJECT_IDENTITY_REL), "utf-8");
|
|
4532
|
+
folderId = JSON.parse(text)?.projectId;
|
|
4504
4533
|
} catch {
|
|
4505
|
-
|
|
4534
|
+
return void 0;
|
|
4506
4535
|
}
|
|
4507
4536
|
const parsed = projectIdSchema.safeParse(folderId);
|
|
4508
|
-
|
|
4509
|
-
throw new PreCreateRefused(`${file} could not be read as a Halfcycle project file \u2014 delete the .halfcycle folder in ${targetRepo} and run "npx halfcycle" again to start a new project here.`);
|
|
4510
|
-
}
|
|
4511
|
-
return parsed.data;
|
|
4537
|
+
return parsed.success ? parsed.data : void 0;
|
|
4512
4538
|
}
|
|
4513
4539
|
function checkSettingsParse(targetRepo) {
|
|
4514
4540
|
try {
|
|
@@ -6111,16 +6137,15 @@ function removeInstalledFile(targetRepo, path, entry, writeSet, lists, name) {
|
|
|
6111
6137
|
return;
|
|
6112
6138
|
}
|
|
6113
6139
|
if (!writeSet.ownedDirs.has(path.slice(0, path.lastIndexOf("/")))) {
|
|
6114
|
-
let
|
|
6140
|
+
let bytes;
|
|
6115
6141
|
try {
|
|
6116
|
-
|
|
6142
|
+
bytes = readFileSync9(abs);
|
|
6117
6143
|
} catch {
|
|
6118
6144
|
name(lists.failed, path, path);
|
|
6119
6145
|
return;
|
|
6120
6146
|
}
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
name(recordedHash !== void 0 ? lists.kept : lists.leftAlone, path, path);
|
|
6147
|
+
if (!bytesAreOurs(path, bytes, entry, writeSet)) {
|
|
6148
|
+
name(entry !== void 0 && "sha256" in entry ? lists.kept : lists.leftAlone, path, path);
|
|
6124
6149
|
return;
|
|
6125
6150
|
}
|
|
6126
6151
|
}
|
|
@@ -6732,7 +6757,8 @@ ${PROJECT_RESET_LINE}
|
|
|
6732
6757
|
process.stdout.write(`[halfcycle] Commit the files Halfcycle set up here, including ${BUNDLE_PIN_REL} and ${PROJECT_IDENTITY_REL}, so every copy of this project joins it and runs the same checks.
|
|
6733
6758
|
`);
|
|
6734
6759
|
} else if (credential) {
|
|
6735
|
-
process.stdout.write(`[halfcycle]
|
|
6760
|
+
process.stdout.write(process.platform !== "win32" && !gitIsInstalled() ? `[halfcycle] Halfcycle needs Git, and it is not installed on this machine \u2014 get it from https://git-scm.com/downloads, then run "git init" here to get checks on every edit.
|
|
6761
|
+
` : `[halfcycle] This folder is not a git repository. Halfcycle's checks compare against your git history, so run "git init" here to get checks on every edit.
|
|
6736
6762
|
`);
|
|
6737
6763
|
}
|
|
6738
6764
|
if (credential) {
|
|
@@ -6759,22 +6785,23 @@ ${PROJECT_RESET_LINE}
|
|
|
6759
6785
|
`);
|
|
6760
6786
|
process.exit(0);
|
|
6761
6787
|
} catch (err) {
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
`
|
|
6788
|
+
const stop = (message) => {
|
|
6789
|
+
const kept = readFolderId(targetRepo) !== void 0 ? `${KEPT_FOLDER_ID_LINE}
|
|
6790
|
+
` : "";
|
|
6791
|
+
process.stderr.write(`${message}
|
|
6792
|
+
${kept}`);
|
|
6765
6793
|
process.exit(1);
|
|
6794
|
+
};
|
|
6795
|
+
if (err instanceof SignInRefused || err instanceof PreCreateRefused || err instanceof SettingsParseRefused) {
|
|
6796
|
+
stop(`halfcycle: ${err.message}`);
|
|
6766
6797
|
return;
|
|
6767
6798
|
}
|
|
6768
6799
|
const fsCode = typeof err === "object" && err !== null && "code" in err ? String(err.code) : void 0;
|
|
6769
6800
|
if (fsCode !== void 0) {
|
|
6770
|
-
|
|
6771
|
-
`);
|
|
6772
|
-
process.exit(1);
|
|
6801
|
+
stop(`halfcycle: could not write to ${targetRepo} \u2014 check that you can write to this folder and run "npx halfcycle" again.`);
|
|
6773
6802
|
return;
|
|
6774
6803
|
}
|
|
6775
|
-
|
|
6776
|
-
`);
|
|
6777
|
-
process.exit(1);
|
|
6804
|
+
stop(`halfcycle install failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
6778
6805
|
}
|
|
6779
6806
|
return;
|
|
6780
6807
|
}
|