halfcycle 0.4.3 → 0.4.5
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/bin/bin.bundle.mjs +47 -27
- package/dist/cli.js +59 -37
- package/dist/cli.js.map +2 -2
- package/dist/confirm-identity.d.ts +28 -18
- package/dist/confirm-identity.d.ts.map +1 -1
- package/dist/index.js +28 -17
- package/dist/index.js.map +1 -1
- package/dist/install-manifest.d.ts +27 -2
- package/dist/install-manifest.d.ts.map +1 -1
- package/dist/install.d.ts +20 -10
- package/dist/install.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.5",
|
|
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/bin/bin.bundle.mjs
CHANGED
|
@@ -17520,7 +17520,8 @@ function deleteMarker(repoRoot, sessionId) {
|
|
|
17520
17520
|
var SESSION_NOTICE_SUFFIX = {
|
|
17521
17521
|
"no-repository": ".norepo",
|
|
17522
17522
|
"credential-rejected": ".credential",
|
|
17523
|
-
"credential-renewed-for-this-run": ".renewed"
|
|
17523
|
+
"credential-renewed-for-this-run": ".renewed",
|
|
17524
|
+
"credential-missing": ".nocredential"
|
|
17524
17525
|
};
|
|
17525
17526
|
function firstNoticeThisSession(notice, folder, sessionId) {
|
|
17526
17527
|
if (!sessionId)
|
|
@@ -17810,6 +17811,8 @@ async function evaluateEdit(reply, readSeat) {
|
|
|
17810
17811
|
const configResult = readConfig();
|
|
17811
17812
|
if (!configResult.config) {
|
|
17812
17813
|
await emitRunRecord({ runType: "hook", outcome: "unconfigured", changeSet });
|
|
17814
|
+
if (!firstNoticeThisSession("credential-missing", repoRoot, input.session_id))
|
|
17815
|
+
return;
|
|
17813
17816
|
emitNeverConfigured(UNCONFIGURED_EXIT_TOOL_USE, readNeverConfiguredReason());
|
|
17814
17817
|
return;
|
|
17815
17818
|
}
|
|
@@ -17989,6 +17992,8 @@ async function evaluateSession(input, kind, reply, readSeat) {
|
|
|
17989
17992
|
const configResult = readConfig();
|
|
17990
17993
|
if (!configResult.config) {
|
|
17991
17994
|
await emitRunRecord({ runType: "hook", outcome: "unconfigured", changeSet, diffBase });
|
|
17995
|
+
if (!firstNoticeThisSession("credential-missing", repoRoot, sessionId))
|
|
17996
|
+
return;
|
|
17992
17997
|
emitNeverConfigured(UNCONFIGURED_EXIT_STOP_FAMILY, readNeverConfiguredReason());
|
|
17993
17998
|
return;
|
|
17994
17999
|
}
|
|
@@ -18466,37 +18471,52 @@ function namesItsServer(body) {
|
|
|
18466
18471
|
function discardBody(res) {
|
|
18467
18472
|
res.body?.cancel().catch(() => void 0);
|
|
18468
18473
|
}
|
|
18474
|
+
var CUT_OFF = /* @__PURE__ */ Symbol("cut-off");
|
|
18475
|
+
async function postInitialize(url2, bearer, fetchFn, signal) {
|
|
18476
|
+
let res;
|
|
18477
|
+
try {
|
|
18478
|
+
res = await fetchFn(url2, {
|
|
18479
|
+
method: "POST",
|
|
18480
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${bearer}` },
|
|
18481
|
+
body: INITIALIZE_BODY,
|
|
18482
|
+
redirect: "manual",
|
|
18483
|
+
signal
|
|
18484
|
+
});
|
|
18485
|
+
} catch {
|
|
18486
|
+
return CANNOT_REACH_SENTENCE;
|
|
18487
|
+
}
|
|
18488
|
+
if (res.status === 401) {
|
|
18489
|
+
discardBody(res);
|
|
18490
|
+
return NO_CREDENTIAL_SENTENCE;
|
|
18491
|
+
}
|
|
18492
|
+
if (res.status < 200 || res.status > 299) {
|
|
18493
|
+
discardBody(res);
|
|
18494
|
+
return SERVICE_PROBLEM_SENTENCE;
|
|
18495
|
+
}
|
|
18496
|
+
let text;
|
|
18497
|
+
try {
|
|
18498
|
+
text = await res.text();
|
|
18499
|
+
} catch {
|
|
18500
|
+
return signal.aborted ? CANNOT_REACH_SENTENCE : CUT_OFF;
|
|
18501
|
+
}
|
|
18502
|
+
let body;
|
|
18503
|
+
try {
|
|
18504
|
+
body = JSON.parse(text);
|
|
18505
|
+
} catch {
|
|
18506
|
+
return SERVICE_PROBLEM_SENTENCE;
|
|
18507
|
+
}
|
|
18508
|
+
return namesItsServer(body) ? void 0 : SERVICE_PROBLEM_SENTENCE;
|
|
18509
|
+
}
|
|
18469
18510
|
async function probeDelivery(url2, bearer, fetchFn, deadlineMs) {
|
|
18470
18511
|
const controller = new AbortController();
|
|
18471
18512
|
const timer = setTimeout(() => controller.abort(), deadlineMs);
|
|
18472
18513
|
try {
|
|
18473
|
-
let
|
|
18474
|
-
|
|
18475
|
-
|
|
18476
|
-
|
|
18477
|
-
headers: { "content-type": "application/json", authorization: `Bearer ${bearer}` },
|
|
18478
|
-
body: INITIALIZE_BODY,
|
|
18479
|
-
redirect: "manual",
|
|
18480
|
-
signal: controller.signal
|
|
18481
|
-
});
|
|
18482
|
-
} catch {
|
|
18483
|
-
return CANNOT_REACH_SENTENCE;
|
|
18484
|
-
}
|
|
18485
|
-
if (res.status === 401) {
|
|
18486
|
-
discardBody(res);
|
|
18487
|
-
return NO_CREDENTIAL_SENTENCE;
|
|
18488
|
-
}
|
|
18489
|
-
if (res.status < 200 || res.status > 299) {
|
|
18490
|
-
discardBody(res);
|
|
18491
|
-
return SERVICE_PROBLEM_SENTENCE;
|
|
18492
|
-
}
|
|
18493
|
-
let body;
|
|
18494
|
-
try {
|
|
18495
|
-
body = await res.json();
|
|
18496
|
-
} catch {
|
|
18497
|
-
return controller.signal.aborted ? CANNOT_REACH_SENTENCE : SERVICE_PROBLEM_SENTENCE;
|
|
18514
|
+
for (let attempt2 = 0; attempt2 < 2; attempt2++) {
|
|
18515
|
+
const outcome = await postInitialize(url2, bearer, fetchFn, controller.signal);
|
|
18516
|
+
if (outcome !== CUT_OFF)
|
|
18517
|
+
return outcome;
|
|
18498
18518
|
}
|
|
18499
|
-
return
|
|
18519
|
+
return CANNOT_REACH_SENTENCE;
|
|
18500
18520
|
} finally {
|
|
18501
18521
|
clearTimeout(timer);
|
|
18502
18522
|
}
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// dist/bin.js
|
|
4
|
-
import { existsSync as existsSync7, readFileSync as readFileSync11, realpathSync as realpathSync3, statSync } from "node:fs";
|
|
4
|
+
import { existsSync as existsSync7, readFileSync as readFileSync11, realpathSync as realpathSync3, statSync, writeSync as writeSync2 } from "node:fs";
|
|
5
5
|
import { homedir as homedir3 } from "node:os";
|
|
6
6
|
import { join as join12 } from "node:path";
|
|
7
7
|
|
|
@@ -1648,12 +1648,20 @@ function createProjectIdentityFile(targetRepoRoot) {
|
|
|
1648
1648
|
}
|
|
1649
1649
|
|
|
1650
1650
|
// dist/install-manifest.js
|
|
1651
|
+
import { Buffer as Buffer2 } from "node:buffer";
|
|
1651
1652
|
import { createHash } from "node:crypto";
|
|
1652
1653
|
var INSTALL_MANIFEST_REL = ".halfcycle/install-manifest.json";
|
|
1653
1654
|
var INSTALL_MANIFEST_FORMAT = "halfcycle-install-manifest/v1";
|
|
1654
1655
|
function sha256Hex(bytes) {
|
|
1655
1656
|
return createHash("sha256").update(bytes).digest("hex");
|
|
1656
1657
|
}
|
|
1658
|
+
function lineKey(line) {
|
|
1659
|
+
return line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
1660
|
+
}
|
|
1661
|
+
function fileSha256(bytes) {
|
|
1662
|
+
const text = typeof bytes === "string" ? bytes : Buffer2.from(bytes).toString("utf-8");
|
|
1663
|
+
return sha256Hex(text.split("\n").map(lineKey).join("\n"));
|
|
1664
|
+
}
|
|
1657
1665
|
function canonicalJson(value) {
|
|
1658
1666
|
if (Array.isArray(value))
|
|
1659
1667
|
return value.map(canonicalJson);
|
|
@@ -1836,7 +1844,7 @@ function startManifest(previous, isMember, isMemberDir) {
|
|
|
1836
1844
|
};
|
|
1837
1845
|
}
|
|
1838
1846
|
function recordInstalledFile(draft, path, bytes) {
|
|
1839
|
-
draft.files.set(path, { path, sha256:
|
|
1847
|
+
draft.files.set(path, { path, sha256: fileSha256(bytes) });
|
|
1840
1848
|
}
|
|
1841
1849
|
function recordRemovedFile(draft, path) {
|
|
1842
1850
|
draft.files.delete(path);
|
|
@@ -2155,17 +2163,17 @@ function sha256OfWhatThisVersionWrites(commands) {
|
|
|
2155
2163
|
const out = /* @__PURE__ */ new Map();
|
|
2156
2164
|
const fromFile = (rel, src) => {
|
|
2157
2165
|
if (existsSync3(src))
|
|
2158
|
-
out.set(rel,
|
|
2166
|
+
out.set(rel, fileSha256(readFileSync5(src)));
|
|
2159
2167
|
};
|
|
2160
2168
|
for (const cmd of commands)
|
|
2161
2169
|
fromFile(commandStubRel(cmd.name), join5(BUNDLE_ROOT, ".claude-plugin", cmd.path));
|
|
2162
2170
|
for (const [rel, content] of hookScriptsThisVersionWrites())
|
|
2163
|
-
out.set(rel,
|
|
2171
|
+
out.set(rel, fileSha256(content));
|
|
2164
2172
|
fromFile(CAPTURED_INDEX_REL, capturedIndexScaffold());
|
|
2165
2173
|
return out;
|
|
2166
2174
|
}
|
|
2167
2175
|
function bytesAreOurs(path, bytes, recorded, writeSet) {
|
|
2168
|
-
const hash =
|
|
2176
|
+
const hash = fileSha256(bytes);
|
|
2169
2177
|
const recordedHash = recorded !== void 0 && "sha256" in recorded ? recorded.sha256 : void 0;
|
|
2170
2178
|
return hash === recordedHash || hash === writeSet.generatedSha256.get(path);
|
|
2171
2179
|
}
|
|
@@ -2353,16 +2361,17 @@ function writeCollisionSafe(targetAbsPath, targetRepoRoot, content, isOurs) {
|
|
|
2353
2361
|
throw new Error(`[halfcycle] Write-allowlist violation: attempted to write "${rel}". Only the method surface and scaffolding paths are writable.`);
|
|
2354
2362
|
}
|
|
2355
2363
|
let replacedExisting = false;
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2364
|
+
const current = existsSync3(targetAbsPath) ? readFileSync5(targetAbsPath) : null;
|
|
2365
|
+
const text = inLineEndingOf(current?.toString("utf-8") ?? null, content);
|
|
2366
|
+
if (current !== null) {
|
|
2367
|
+
if (current.toString("utf-8") === text)
|
|
2359
2368
|
return { outcome: "skipped", replacedExisting: false };
|
|
2360
2369
|
if (!isOurs(current))
|
|
2361
2370
|
return { outcome: "collided", replacedExisting: false };
|
|
2362
2371
|
replacedExisting = true;
|
|
2363
2372
|
}
|
|
2364
2373
|
mkdirSync5(dirname3(targetAbsPath), { recursive: true });
|
|
2365
|
-
writeFileSync5(targetAbsPath,
|
|
2374
|
+
writeFileSync5(targetAbsPath, text, "utf-8");
|
|
2366
2375
|
return { outcome: "written", replacedExisting };
|
|
2367
2376
|
}
|
|
2368
2377
|
var INSTALLER_OWNED_DIR = ".halfcycle/";
|
|
@@ -2374,11 +2383,12 @@ function writeOwned(targetAbsPath, targetRepoRoot, content) {
|
|
|
2374
2383
|
if (!rel.startsWith(INSTALLER_OWNED_DIR)) {
|
|
2375
2384
|
throw new Error(`[halfcycle] Path-ownership violation: attempted to claim "${rel}" by path. Only ${INSTALLER_OWNED_DIR} is owned outright by this installer; everywhere else a differing file may be a developer's, so ownership must be proved from the file's own contents (writeOwnedGenerated) rather than assumed from where it sits.`);
|
|
2376
2385
|
}
|
|
2377
|
-
|
|
2386
|
+
const current = readFileOrNull(targetAbsPath);
|
|
2387
|
+
const text = inLineEndingOf(current, content);
|
|
2388
|
+
if (current === text)
|
|
2378
2389
|
return "skipped";
|
|
2379
|
-
}
|
|
2380
2390
|
mkdirSync5(dirname3(targetAbsPath), { recursive: true });
|
|
2381
|
-
writeFileSync5(targetAbsPath,
|
|
2391
|
+
writeFileSync5(targetAbsPath, text, "utf-8");
|
|
2382
2392
|
return "written";
|
|
2383
2393
|
}
|
|
2384
2394
|
var GENERATED_HEADER_LINE_INDEX = 1;
|
|
@@ -2780,13 +2790,13 @@ function adoptBlockUnderHeader(before, header, claimable) {
|
|
|
2780
2790
|
}
|
|
2781
2791
|
return claimed.length > 0 ? claimed.join("\n") + "\n" : "";
|
|
2782
2792
|
}
|
|
2783
|
-
function lineKey(line) {
|
|
2784
|
-
return line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
2785
|
-
}
|
|
2786
2793
|
function lineEndingOf(text) {
|
|
2787
2794
|
const at = text.indexOf("\n");
|
|
2788
2795
|
return at > 0 && text[at - 1] === "\r" ? "\r\n" : "\n";
|
|
2789
2796
|
}
|
|
2797
|
+
function inLineEndingOf(existing, generated) {
|
|
2798
|
+
return existing === null ? generated : generated.split("\n").join(lineEndingOf(existing));
|
|
2799
|
+
}
|
|
2790
2800
|
function gitignoreCovers(content) {
|
|
2791
2801
|
const lines = new Set(content.split("\n").map((l) => l.trim()));
|
|
2792
2802
|
return REQUIRED_GITIGNORE_ENTRIES.every((entry) => lines.has(entry));
|
|
@@ -2879,7 +2889,8 @@ function writeBundlePin(targetRepoRoot, version, engagementId, engagementType, c
|
|
|
2879
2889
|
...projectPage !== void 0 ? { projectPage } : {}
|
|
2880
2890
|
};
|
|
2881
2891
|
const pinPath = join5(targetRepoRoot, BUNDLE_PIN_REL);
|
|
2882
|
-
|
|
2892
|
+
const pinText = inLineEndingOf(readFileOrNull(pinPath), JSON.stringify(pin, null, 2) + "\n");
|
|
2893
|
+
writeAllowlisted(pinPath, targetRepoRoot, pinText, writtenPaths);
|
|
2883
2894
|
}
|
|
2884
2895
|
function writeCrewRoster(targetRepoRoot, report) {
|
|
2885
2896
|
const doc = {
|
|
@@ -3068,7 +3079,7 @@ async function install(options) {
|
|
|
3068
3079
|
}
|
|
3069
3080
|
recordSettingsMerge(draft, existingSettings, generatedSettings, settingsPreexisted);
|
|
3070
3081
|
const mergedSettings = mergeSettings(existingSettings, generatedSettings);
|
|
3071
|
-
const mergedSettingsText = JSON.stringify(mergedSettings, null, 2) + "\n";
|
|
3082
|
+
const mergedSettingsText = inLineEndingOf(readFileOrNull(settingsPath), JSON.stringify(mergedSettings, null, 2) + "\n");
|
|
3072
3083
|
mkdirSync5(dirname3(settingsPath), { recursive: true });
|
|
3073
3084
|
writeFileSync5(settingsPath, mergedSettingsText, "utf-8");
|
|
3074
3085
|
(settingsPreexisted ? report.mergedPaths : report.writtenPaths).push(".claude/settings.json");
|
|
@@ -3096,7 +3107,7 @@ async function install(options) {
|
|
|
3096
3107
|
if (credential) {
|
|
3097
3108
|
const mcpPath = join5(targetRepo, MCP_REGISTRATION_REL);
|
|
3098
3109
|
const existingMcp = existsSync3(mcpPath) ? readFileSync5(mcpPath, "utf-8") : null;
|
|
3099
|
-
const mcpContent = generateMcpRegistration(existingMcp, credential.mcpUrl);
|
|
3110
|
+
const mcpContent = inLineEndingOf(existingMcp, generateMcpRegistration(existingMcp, credential.mcpUrl));
|
|
3100
3111
|
recordMcpMerge(draft, existingMcp, mcpContent);
|
|
3101
3112
|
if (existingMcp === null) {
|
|
3102
3113
|
writeAllowlisted(mcpPath, targetRepo, mcpContent, report.writtenPaths);
|
|
@@ -4201,13 +4212,12 @@ async function describeAccount(serviceUrl, credential, fetchImpl = fetch) {
|
|
|
4201
4212
|
return (await askAccount(serviceUrl, credential, fetchImpl)).identity;
|
|
4202
4213
|
}
|
|
4203
4214
|
function accountLabel(identity, storedAccountId, verbose2 = false) {
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4215
|
+
if (identity === void 0)
|
|
4216
|
+
return void 0;
|
|
4217
|
+
if (identity.email !== void 0 && identity.email.trim() !== "") {
|
|
4218
|
+
return verbose2 ? `${identity.email} (account ${identity.accountId})` : identity.email;
|
|
4207
4219
|
}
|
|
4208
|
-
|
|
4209
|
-
return `account ${accountId} (no email on record for it)`;
|
|
4210
|
-
return "the account this credential belongs to, which this service did not name";
|
|
4220
|
+
return `account ${identity.accountId} (no email on record for it)`;
|
|
4211
4221
|
}
|
|
4212
4222
|
function onlyDate(iso) {
|
|
4213
4223
|
if (iso === void 0)
|
|
@@ -4253,7 +4263,11 @@ function actingAs(credential, identity) {
|
|
|
4253
4263
|
function question(colour) {
|
|
4254
4264
|
return `[halfcycle] Continue as that account? ${ink("good", "[y]", colour)} yes ${ink("key", "[s]", colour)} sign in as someone else ${ink("faint", "[anything else]", colour)} stop: `;
|
|
4255
4265
|
}
|
|
4256
|
-
function
|
|
4266
|
+
function accountLine(identity, label) {
|
|
4267
|
+
return identity === void 0 ? "" : `[halfcycle] Account: ${label}
|
|
4268
|
+
`;
|
|
4269
|
+
}
|
|
4270
|
+
function armScript(credential, act, accountLineText, plainLabel, home) {
|
|
4257
4271
|
switch (credential.source) {
|
|
4258
4272
|
case "browser-handoff":
|
|
4259
4273
|
return {
|
|
@@ -4265,11 +4279,11 @@ function armScript(credential, act, label, plainLabel, home) {
|
|
|
4265
4279
|
stopRemedy: "",
|
|
4266
4280
|
afterSwitch: ""
|
|
4267
4281
|
};
|
|
4268
|
-
case "environment":
|
|
4282
|
+
case "environment": {
|
|
4283
|
+
const credentialSentence = plainLabel !== void 0 ? `HALFCYCLE_TOKEN in this environment is the credential for ${plainLabel}, and it takes precedence over any saved sign-in` : `The credential in your environment could not be checked, because the Halfcycle service did not answer, and it takes precedence over any saved sign-in`;
|
|
4269
4284
|
return {
|
|
4270
4285
|
preamble: `[halfcycle] HALFCYCLE_TOKEN is set in this environment, and this install will use it.
|
|
4271
|
-
[halfcycle]
|
|
4272
|
-
[halfcycle] About to ${actClause(act)}
|
|
4286
|
+
` + accountLineText + `[halfcycle] About to ${actClause(act)}
|
|
4273
4287
|
[halfcycle] A variable exported by a shell profile is not a choice about this run \u2014 if that is not your account, ${wrongAccountConsequence(act)}.
|
|
4274
4288
|
`,
|
|
4275
4289
|
asks: true,
|
|
@@ -4279,19 +4293,20 @@ function armScript(credential, act, label, plainLabel, home) {
|
|
|
4279
4293
|
noTerminalNote: "",
|
|
4280
4294
|
switchOpening: `[halfcycle] Signing you in through your browser \u2014 this install will use that account instead.
|
|
4281
4295
|
`,
|
|
4282
|
-
stopRemedy: `Stopped before anything was set up.
|
|
4296
|
+
stopRemedy: `Stopped before anything was set up. ${credentialSentence} \u2014 so unset it, or set it to your own account's credential, before running this again.`,
|
|
4283
4297
|
// The switch is real for THIS run (the new credential is what gets used) and it
|
|
4284
4298
|
// does not persist: `obtainCredential` reads the variable first, so the next run
|
|
4285
4299
|
// is the same variable again. Said here rather than discovered tomorrow.
|
|
4286
4300
|
afterSwitch: `[halfcycle] HALFCYCLE_TOKEN is still set in this environment and still wins over a saved sign-in \u2014 unset it, or the next run will use it again.
|
|
4287
4301
|
`
|
|
4288
4302
|
};
|
|
4303
|
+
}
|
|
4289
4304
|
case "stored": {
|
|
4290
4305
|
const savedOn = onlyDate(credential.signedInAt);
|
|
4306
|
+
const savedSignInSentence = plainLabel !== void 0 ? `This machine's saved sign-in is for ${plainLabel}.` : `This machine's saved sign-in could not be checked, because the Halfcycle service did not answer.`;
|
|
4291
4307
|
return {
|
|
4292
4308
|
preamble: `[halfcycle] This machine already has a Halfcycle sign-in saved, and this install will use it.
|
|
4293
|
-
[halfcycle]
|
|
4294
|
-
` + (savedOn !== void 0 ? `[halfcycle] Saved on this machine: ${savedOn}
|
|
4309
|
+
` + accountLineText + (savedOn !== void 0 ? `[halfcycle] Saved on this machine: ${savedOn}
|
|
4295
4310
|
` : "") + `[halfcycle] About to ${actClause(act)}
|
|
4296
4311
|
[halfcycle] If that is not your account, ${wrongAccountConsequence(act)}${wrongAccountVisibility(act) === void 0 ? "" : ` \u2014 ${wrongAccountVisibility(act)}`}.
|
|
4297
4312
|
`,
|
|
@@ -4305,7 +4320,7 @@ function armScript(credential, act, label, plainLabel, home) {
|
|
|
4305
4320
|
`,
|
|
4306
4321
|
switchOpening: `[halfcycle] Forgetting this machine's saved sign-in and signing you in through your browser.
|
|
4307
4322
|
`,
|
|
4308
|
-
stopRemedy: `Stopped before anything was set up.
|
|
4323
|
+
stopRemedy: `Stopped before anything was set up. ${savedSignInSentence} 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.`,
|
|
4309
4324
|
afterSwitch: ""
|
|
4310
4325
|
};
|
|
4311
4326
|
}
|
|
@@ -4320,9 +4335,9 @@ async function confirmActingIdentity(serviceUrl, credential, act, deps = {}) {
|
|
|
4320
4335
|
const colour = deps.colour ?? decideColour({ isTTY: process.stdout.isTTY === true, env: deps.env ?? process.env });
|
|
4321
4336
|
const identity = await describeAccount(serviceUrl, credential.credential, deps.fetchImpl);
|
|
4322
4337
|
const plainLabel = accountLabel(identity, credential.accountId, deps.verbose === true);
|
|
4323
|
-
const label = ink("keyBold", plainLabel, colour);
|
|
4338
|
+
const label = plainLabel !== void 0 ? ink("keyBold", plainLabel, colour) : "";
|
|
4324
4339
|
const acting = actingAs(credential, identity);
|
|
4325
|
-
const script = armScript(credential, act, label, plainLabel, deps.home);
|
|
4340
|
+
const script = armScript(credential, act, accountLine(identity, label), plainLabel, deps.home);
|
|
4326
4341
|
write(script.preamble);
|
|
4327
4342
|
if (!script.asks)
|
|
4328
4343
|
return acting;
|
|
@@ -6164,7 +6179,7 @@ function applyMergedJson(targetRepo, rule, file, deleteWhenEmpty, lists, name, e
|
|
|
6164
6179
|
if (!changed)
|
|
6165
6180
|
return;
|
|
6166
6181
|
const empty = Object.keys(value).length === 0 && deleteWhenEmpty;
|
|
6167
|
-
writeBack(targetRepo, rel, file.text, empty ? null : serializeJson(value), lists, name);
|
|
6182
|
+
writeBack(targetRepo, rel, file.text, empty ? null : inLineEndingOf(file.text, serializeJson(value)), lists, name);
|
|
6168
6183
|
if (adopted && lexists(join10(targetRepo, rel))) {
|
|
6169
6184
|
name(lists.leftAlone, `${rel} (Halfcycle's hooks were taken out. There is no record of this file from before the install, so a permission rule or "$schema" Halfcycle added may still be there \u2014 it cannot be told apart from your own)`, rel);
|
|
6170
6185
|
}
|
|
@@ -6608,14 +6623,21 @@ ${USAGE}`);
|
|
|
6608
6623
|
process.exit(1);
|
|
6609
6624
|
return;
|
|
6610
6625
|
}
|
|
6626
|
+
let interrupted = false;
|
|
6611
6627
|
const onInstallSigint = () => {
|
|
6628
|
+
if (interrupted)
|
|
6629
|
+
return;
|
|
6630
|
+
interrupted = true;
|
|
6612
6631
|
const kept = readFolderId(targetRepo) !== void 0 ? `${KEPT_FOLDER_ID_LINE}
|
|
6613
6632
|
` : "";
|
|
6614
|
-
|
|
6633
|
+
try {
|
|
6634
|
+
writeSync2(2, `halfcycle: interrupted.
|
|
6615
6635
|
${kept}`);
|
|
6636
|
+
} catch {
|
|
6637
|
+
}
|
|
6616
6638
|
process.exit(1);
|
|
6617
6639
|
};
|
|
6618
|
-
process.
|
|
6640
|
+
process.on("SIGINT", onInstallSigint);
|
|
6619
6641
|
try {
|
|
6620
6642
|
const controlOrigin = resolveControlOrigin(process.env);
|
|
6621
6643
|
const serviceUrl = controlOrigin.origin;
|