halfcycle 0.3.13 → 0.3.14

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/dist/bin.js CHANGED
@@ -1625,7 +1625,7 @@ function resolveControlOrigin(env = process.env) {
1625
1625
  return { origin: DEFAULT_CONTROL_ORIGIN, source: "default" };
1626
1626
  }
1627
1627
  function controlOriginNote(resolved) {
1628
- return resolved.source === "environment" ? `${resolved.origin} (from HALFCYCLE_SERVICE_URL)` : `${resolved.origin} (the Halfcycle plane)`;
1628
+ return resolved.source === "environment" ? `${resolved.origin} (from HALFCYCLE_SERVICE_URL)` : `${resolved.origin} (the published one)`;
1629
1629
  }
1630
1630
 
1631
1631
  // dist/device-signin.js
@@ -1772,7 +1772,7 @@ var SignInRefused = class extends Error {
1772
1772
  }
1773
1773
  };
1774
1774
  var SIGN_IN_TIMED_OUT = "timed-out";
1775
- var SIGN_IN_TIMEOUT_MESSAGE = "Sign-in timed out: the Halfcycle plane stopped answering while this terminal was waiting for the browser. Nothing was created. Check your connection and run the command again.";
1775
+ var SIGN_IN_TIMEOUT_MESSAGE = "Sign-in timed out: the Halfcycle service stopped answering while this terminal was waiting for the browser. Nothing was created. Check your connection and run the command again.";
1776
1776
  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. Nothing was created.";
1777
1777
  var DEADLINE_SLACK_INTERVALS = 1;
1778
1778
  var STILL_WAITING_MS = 15e3;
@@ -1859,7 +1859,7 @@ async function startDeviceAuth(serviceUrl) {
1859
1859
  body: "{}"
1860
1860
  });
1861
1861
  } catch (err) {
1862
- throw new SignInRefused(SIGN_IN_TIMED_OUT, `Could not reach the Halfcycle plane at ${url} to start a sign-in: ${err instanceof Error ? err.message : String(err)}. Nothing was created.`);
1862
+ 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)}. Nothing was created.`);
1863
1863
  }
1864
1864
  const body = await res.json().catch(() => null);
1865
1865
  if (res.status !== 201) {
@@ -1868,7 +1868,7 @@ async function startDeviceAuth(serviceUrl) {
1868
1868
  }
1869
1869
  const parsed = deviceAuthStartResponseSchema.safeParse(body);
1870
1870
  if (!parsed.success) {
1871
- throw new SignInRefused(SIGN_IN_TIMED_OUT, `The Halfcycle plane at ${url} answered a sign-in start this client cannot read. Either that plane is older than this installer, or the address is not a Halfcycle control plane. Nothing was created.`);
1871
+ 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. Nothing was created.`);
1872
1872
  }
1873
1873
  return parsed.data;
1874
1874
  }
@@ -1943,7 +1943,7 @@ async function collectDecidedCredential(serviceUrl, started, deps) {
1943
1943
  });
1944
1944
  body = await res.json().catch(() => null);
1945
1945
  } catch (err) {
1946
- lastProblem = `The browser finished signing you in, but this terminal could not reach the Halfcycle plane to collect the credential: ${err instanceof Error ? err.message : String(err)}. Run the command again.`;
1946
+ lastProblem = `The browser finished signing you in, but this terminal could not reach the Halfcycle service to collect the credential: ${err instanceof Error ? err.message : String(err)}. Run the command again.`;
1947
1947
  continue;
1948
1948
  }
1949
1949
  const parsed = devicePollResponseSchema.safeParse(body);
@@ -2132,7 +2132,7 @@ async function requestEngagementValues(url, requestBody, credential, shape) {
2132
2132
  body: JSON.stringify(requestBody)
2133
2133
  });
2134
2134
  } catch (err) {
2135
- throw new Error(`[bundle install] Could not reach the Halfcycle control plane at ${url}: ${err instanceof Error ? err.message : String(err)}. ${shape.nothingHappened}. ` + CONTROL_ORIGIN_HINT);
2135
+ throw new Error(`[bundle install] Could not reach the Halfcycle service at ${url}: ${err instanceof Error ? err.message : String(err)}. ${shape.nothingHappened}. ` + CONTROL_ORIGIN_HINT);
2136
2136
  }
2137
2137
  if (!res.ok) {
2138
2138
  const detail = await res.text().catch(() => "");
@@ -2140,7 +2140,7 @@ async function requestEngagementValues(url, requestBody, credential, shape) {
2140
2140
  if (refusal) {
2141
2141
  throw new CreateEngagementRefused(res.status, refusal.message, refusal.error, `[bundle install] ${shape.action} failed: ${url} returned ${res.status}. ${refusal.message} ${shape.nothingHappened}.`);
2142
2142
  }
2143
- const wrongOrigin = res.status === 404 ? `That address answered, but it does not serve ${shape.route} \u2014 so it is not the control plane. ` : "";
2143
+ const wrongOrigin = res.status === 404 ? `That address answered, but it does not serve ${shape.route} \u2014 so it is not the Halfcycle service. ` : "";
2144
2144
  throw new Error(`[bundle install] ${shape.action} failed: ${url} returned ${res.status}. ${wrongOrigin}${detail ? `Response: ${detail.slice(0, 300)}. ` : ""}${shape.nothingHappened}. ` + CONTROL_ORIGIN_HINT);
2145
2145
  }
2146
2146
  const body = await res.json().catch(() => null);
@@ -2152,14 +2152,14 @@ async function requestEngagementValues(url, requestBody, credential, shape) {
2152
2152
  ["sessionToken", body.sessionToken]
2153
2153
  ]) {
2154
2154
  if (value.trim() === "") {
2155
- throw new Error(`[bundle install] The Halfcycle plane at ${url} answered with a blank ${field} (the field is present but empty). An install cannot proceed on it: it would be written into this engagement's credential store, read back as a configured value, and every guard evaluation would fail against it for the life of the engagement. ${shape.nothingHappened} and nothing was written. ` + CONTROL_ORIGIN_HINT);
2155
+ throw new Error(`[bundle install] The Halfcycle service at ${url} answered with a blank ${field} (the field is present but empty). An install cannot proceed on it: it would be written into this engagement's credential store, read back as a configured value, and every guard evaluation would fail against it for the life of the engagement. ${shape.nothingHappened} and nothing was written. ` + CONTROL_ORIGIN_HINT);
2156
2156
  }
2157
2157
  }
2158
2158
  if (typeof body.mcpUrl !== "string" || body.mcpUrl.trim() === "") {
2159
- throw new Error(`[bundle install] The Halfcycle plane at ${url} created an engagement but did not say where its MCP server lives (no mcpUrl on the response). Without that address the install would write an MCP registration pointing nowhere, so nothing was written. Either that plane is older than this installer, or ${url} is not a Halfcycle control plane.`);
2159
+ throw new Error(`[bundle install] The Halfcycle service at ${url} created an engagement but did not say where its MCP server lives (no mcpUrl on the response). Without that address the install would write an MCP registration pointing nowhere, so nothing was written. Either that service is older than this installer, or ${url} is not a Halfcycle service.`);
2160
2160
  }
2161
2161
  if (typeof body.guardUrl !== "string" || body.guardUrl.trim() === "") {
2162
- throw new Error(`[bundle install] The Halfcycle plane at ${url} created an engagement but did not say where its guard service lives (no guardUrl on the response). Without that address the install would wire a guard hook that evaluates nothing, so nothing was written. Either that plane is older than this installer, or ${url} is not a Halfcycle control plane.`);
2162
+ throw new Error(`[bundle install] The Halfcycle service at ${url} created an engagement but did not say where its guard service lives (no guardUrl on the response). Without that address the install would wire a guard hook that evaluates nothing, so nothing was written. Either that service is older than this installer, or ${url} is not a Halfcycle service.`);
2163
2163
  }
2164
2164
  const controlTelemetryUrl = typeof body.controlTelemetryUrl === "string" && body.controlTelemetryUrl.trim() !== "" ? body.controlTelemetryUrl.trim().replace(/\/+$/, "") : void 0;
2165
2165
  return {
@@ -2218,12 +2218,284 @@ function refusedCredentialRemedy(source) {
2218
2218
  case "stored":
2219
2219
  return "That was this machine's saved sign-in and it has been discarded. Run the command again to sign in through your browser.";
2220
2220
  case "browser-handoff":
2221
- return "That credential was issued by this sign-in moments ago, so signing in again would produce the same result. This is a problem on the Halfcycle plane rather than with your account.";
2221
+ return "That credential was issued by this sign-in moments ago, so signing in again would produce the same result. This is a problem on the Halfcycle service rather than with your account.";
2222
2222
  }
2223
2223
  }
2224
2224
 
2225
2225
  // dist/confirm-identity.js
2226
2226
  import { createInterface } from "node:readline/promises";
2227
+
2228
+ // dist/banner.js
2229
+ var MARK_FULL = {
2230
+ width: 32,
2231
+ discRows: 7,
2232
+ lines: [
2233
+ " :+oshhhhso+:",
2234
+ " :sdMMMMMMMMMMMMds:",
2235
+ " :hMMMMMMMMMMMMMMMMMMh:",
2236
+ " oMMMMMMMMMMMMMMMMMMMMMMo",
2237
+ " oMMMMMMMMMMMMMMMMMMMMMMMMo",
2238
+ " NMMMMMMMMMMMMMMMMMMMMMMMMN",
2239
+ " :ssssssssssssssssssssssssss:",
2240
+ " +dddddddddddddddddddddddddd+",
2241
+ " Nd dN",
2242
+ " oMy yMo",
2243
+ " oMh: :hMo",
2244
+ " :hMy- -yMh:",
2245
+ " :sdNys++--++syNds:",
2246
+ " :+oshhhhso+:"
2247
+ ]
2248
+ };
2249
+ var MARK_NARROW = {
2250
+ width: 24,
2251
+ discRows: 5,
2252
+ lines: [
2253
+ " -ohdMMMMdho-",
2254
+ " +dMMMMMMMMMMMMd+",
2255
+ " yMMMMMMMMMMMMMMMMy",
2256
+ " sMMMMMMMMMMMMMMMMMMs",
2257
+ " yhhhhhhhhhhhhhhhhhhy",
2258
+ " NdhhhhhhhhhhhhhhhhdN",
2259
+ " sd ds",
2260
+ " yd: :dy",
2261
+ " +dy+ +yd+",
2262
+ " -ohhhhhhhho-"
2263
+ ]
2264
+ };
2265
+ var UNICODE_GLYPHS = {
2266
+ rule: "\u2500",
2267
+ swatch: "\u2584",
2268
+ dot: "\xB7",
2269
+ dash: "\u2014"
2270
+ };
2271
+ var ASCII_GLYPHS = {
2272
+ rule: "-",
2273
+ swatch: "#",
2274
+ dot: "|",
2275
+ dash: "-"
2276
+ };
2277
+ function toAscii(text) {
2278
+ let out = text;
2279
+ for (const key of Object.keys(UNICODE_GLYPHS)) {
2280
+ out = out.split(UNICODE_GLYPHS[key]).join(ASCII_GLYPHS[key]);
2281
+ }
2282
+ return out.replace(/[^ -~]/g, "?");
2283
+ }
2284
+ var INK = {
2285
+ /** `--text` — the terminal's own foreground, never one we picked. */
2286
+ plain: "",
2287
+ /** `--muted` — dimmed default ink, so it is grey against whatever the background is. */
2288
+ dim: "2",
2289
+ /** `--faint` / `--faded` — bright black, the one ANSI grey that is a colour. */
2290
+ faint: "90",
2291
+ /** Not a colour: emphasis, used with whatever ink is already in force. */
2292
+ bold: "1",
2293
+ /** `--signal` — yellow, the nearest ANSI seat for the brand's gold. */
2294
+ key: "33",
2295
+ /** `--signal`, brighter: the one value on a screen that must be found first. */
2296
+ keyBold: "33;1",
2297
+ /** `--good` — the console's affirmative. */
2298
+ good: "32",
2299
+ /** `--bad` — the console's negative. */
2300
+ bad: "31"
2301
+ };
2302
+ var SWATCH_SGR = [
2303
+ INK.dim,
2304
+ INK.faint,
2305
+ INK.plain,
2306
+ INK.key,
2307
+ INK.keyBold,
2308
+ INK.good,
2309
+ INK.bad
2310
+ ];
2311
+ var ESC = "\x1B";
2312
+ var RESET = `${ESC}[0m`;
2313
+ function paint(segments, colour) {
2314
+ return segments.map((s) => colour && s.sgr !== "" ? `${ESC}[${s.sgr}m${s.text}${RESET}` : s.text).join("");
2315
+ }
2316
+ function ink(name, text, colour) {
2317
+ const sgr = INK[name];
2318
+ return colour && sgr !== "" ? `${ESC}[${sgr}m${text}${RESET}` : text;
2319
+ }
2320
+ var GAP = 4;
2321
+ var FULL_LAYOUT = { art: MARK_FULL, label: 13, rule: 28, palette: true, minColumns: 76 };
2322
+ var NARROW_LAYOUT = { art: MARK_NARROW, label: 10, rule: 20, palette: false, minColumns: 48 };
2323
+ var DEFAULT_COLUMNS = 80;
2324
+ function usableColumns(environment) {
2325
+ const columns = environment.columns;
2326
+ return columns !== void 0 && columns > 0 ? columns : DEFAULT_COLUMNS;
2327
+ }
2328
+ function isSet(value) {
2329
+ return value !== void 0 && value !== "";
2330
+ }
2331
+ function plainTextRequested(env) {
2332
+ return isSet(env["NO_COLOR"]) || env["TERM"] === "dumb";
2333
+ }
2334
+ function decideCut(environment = {}) {
2335
+ const env = environment.env ?? {};
2336
+ if (environment.quiet === true)
2337
+ return "none";
2338
+ if (environment.isTTY !== true)
2339
+ return "line";
2340
+ if (plainTextRequested(env))
2341
+ return "line";
2342
+ const columns = usableColumns(environment);
2343
+ if (columns >= FULL_LAYOUT.minColumns)
2344
+ return "full";
2345
+ if (columns >= NARROW_LAYOUT.minColumns)
2346
+ return "narrow";
2347
+ return "line";
2348
+ }
2349
+ function decideColour(environment = {}) {
2350
+ const env = environment.env ?? {};
2351
+ const force = env["FORCE_COLOR"];
2352
+ if (isSet(force))
2353
+ return force !== "0";
2354
+ if (plainTextRequested(env))
2355
+ return false;
2356
+ return environment.isTTY === true;
2357
+ }
2358
+ function decideUnicode(environment = {}) {
2359
+ const env = environment.env ?? {};
2360
+ if ((environment.platform ?? "") !== "win32") {
2361
+ const locale = env["LC_ALL"] ?? env["LC_CTYPE"] ?? env["LANG"] ?? "";
2362
+ return locale === "" || /utf-?8/i.test(locale);
2363
+ }
2364
+ return isSet(env["WT_SESSION"]) || isSet(env["TERMINUS_SUBLIME"]) || env["ConEmuTask"] === "{cmd::Cmder}" || env["TERM_PROGRAM"] === "vscode";
2365
+ }
2366
+ function padEnd(text, width) {
2367
+ return text + " ".repeat(Math.max(0, width - text.length));
2368
+ }
2369
+ function clip(text, width) {
2370
+ if (width < 8 || text.length <= width)
2371
+ return text;
2372
+ return `${text.slice(0, width - 3)}...`;
2373
+ }
2374
+ function paletteRow(swatch) {
2375
+ const row = [];
2376
+ SWATCH_SGR.forEach((sgr, i) => {
2377
+ if (i > 0)
2378
+ row.push({ text: " ", sgr: INK.plain });
2379
+ row.push({ text: swatch.repeat(2), sgr });
2380
+ });
2381
+ return row;
2382
+ }
2383
+ function factColumn(content, layout, glyphs, text, valueWidth) {
2384
+ const rows = [];
2385
+ const head = text(content.head);
2386
+ const at = head.indexOf("@");
2387
+ rows.push(at < 0 ? [{ text: head, sgr: INK.keyBold }] : [
2388
+ { text: head.slice(0, at), sgr: INK.keyBold },
2389
+ { text: "@", sgr: INK.dim },
2390
+ { text: head.slice(at + 1), sgr: INK.bold }
2391
+ ]);
2392
+ rows.push([{ text: glyphs.rule.repeat(layout.rule), sgr: INK.dim }]);
2393
+ const narrow = layout.art === MARK_NARROW;
2394
+ for (const fact2 of content.facts) {
2395
+ const raw = narrow ? fact2.narrow : fact2.value;
2396
+ if (raw === void 0)
2397
+ continue;
2398
+ rows.push([
2399
+ { text: padEnd(`${text(fact2.label)}:`, layout.label), sgr: INK.key },
2400
+ { text: clip(text(raw), valueWidth), sgr: INK.plain }
2401
+ ]);
2402
+ }
2403
+ if (layout.palette) {
2404
+ rows.push([]);
2405
+ rows.push(paletteRow(glyphs.swatch));
2406
+ }
2407
+ return rows;
2408
+ }
2409
+ var MARGIN = 2;
2410
+ function lastSentenceStart(line) {
2411
+ const at = line.lastIndexOf(". ");
2412
+ return at < 0 ? 0 : at + 2;
2413
+ }
2414
+ function taglineRow(line, first, last) {
2415
+ const indent = { text: " ", sgr: INK.plain };
2416
+ if (first)
2417
+ return [indent, { text: line, sgr: INK.keyBold }];
2418
+ if (!last)
2419
+ return [indent, { text: line, sgr: INK.dim }];
2420
+ const at = lastSentenceStart(line);
2421
+ if (at === 0)
2422
+ return [indent, { text: line, sgr: INK.key }];
2423
+ return [
2424
+ indent,
2425
+ { text: line.slice(0, at), sgr: INK.dim },
2426
+ { text: line.slice(at), sgr: INK.key }
2427
+ ];
2428
+ }
2429
+ function renderBlock(content, layout, environment) {
2430
+ const unicode = decideUnicode(environment);
2431
+ const colour = decideColour(environment);
2432
+ const glyphs = unicode ? UNICODE_GLYPHS : ASCII_GLYPHS;
2433
+ const text = (raw) => unicode ? raw : toAscii(raw);
2434
+ const art = layout.art;
2435
+ const valueWidth = usableColumns(environment) - (art.width + GAP + layout.label);
2436
+ const right = factColumn(content, layout, glyphs, text, valueWidth);
2437
+ const top = Math.max(0, Math.floor((art.lines.length - right.length) / 2));
2438
+ const height = Math.max(art.lines.length, right.length + top);
2439
+ const lines = Array.from({ length: MARGIN }, () => []);
2440
+ for (let i = 0; i < height; i++) {
2441
+ const artLine = art.lines[i] ?? "";
2442
+ const segments = [];
2443
+ if (artLine.length > 0) {
2444
+ segments.push({ text: artLine, sgr: i < art.discRows ? INK.plain : INK.dim });
2445
+ }
2446
+ const rightRow = right[i - top];
2447
+ if (rightRow !== void 0 && rightRow.length > 0) {
2448
+ segments.push({ text: " ".repeat(Math.max(0, art.width + GAP - artLine.length)), sgr: INK.plain });
2449
+ segments.push(...rightRow);
2450
+ }
2451
+ lines.push(segments);
2452
+ }
2453
+ const tagline = layout.art === MARK_NARROW ? content.tagNarrow ?? content.tag : content.tag;
2454
+ lines.push([]);
2455
+ tagline.forEach((line, i) => {
2456
+ lines.push(taglineRow(text(line), i === 0, i === tagline.length - 1));
2457
+ });
2458
+ for (let i = 0; i < MARGIN; i++)
2459
+ lines.push([]);
2460
+ return lines.map((segments) => paint(segments, colour)).join("\n");
2461
+ }
2462
+ function renderLine(content, environment) {
2463
+ const unicode = decideUnicode(environment);
2464
+ const dot = unicode ? UNICODE_GLYPHS.dot : ASCII_GLYPHS.dot;
2465
+ const named = content.version === "" ? "Halfcycle" : `Halfcycle v${content.version}`;
2466
+ const raw = `${named} ${dot} ${content.url}`;
2467
+ return unicode ? raw : toAscii(raw);
2468
+ }
2469
+ function renderBanner(content, environment = {}) {
2470
+ switch (decideCut(environment)) {
2471
+ case "none":
2472
+ return "";
2473
+ case "line":
2474
+ return renderLine(content, environment);
2475
+ case "narrow":
2476
+ return renderBlock(content, NARROW_LAYOUT, environment);
2477
+ case "full":
2478
+ return renderBlock(content, FULL_LAYOUT, environment);
2479
+ }
2480
+ }
2481
+ function printBanner(sink, content, environment = {}) {
2482
+ const text = renderBanner(content, environment);
2483
+ if (text === "")
2484
+ return;
2485
+ sink.write(`${text}
2486
+ `);
2487
+ }
2488
+ function currentEnvironment(quiet2) {
2489
+ return {
2490
+ columns: process.stdout.columns,
2491
+ isTTY: process.stdout.isTTY === true,
2492
+ quiet: quiet2,
2493
+ env: process.env,
2494
+ platform: process.platform
2495
+ };
2496
+ }
2497
+
2498
+ // dist/confirm-identity.js
2227
2499
  var IDENTITY_TIMEOUT_MS = 5e3;
2228
2500
  var IDENTITY_DECLINED = "identity-declined";
2229
2501
  async function describeAccount(serviceUrl, credential, fetchImpl = fetch) {
@@ -2245,14 +2517,14 @@ async function describeAccount(serviceUrl, credential, fetchImpl = fetch) {
2245
2517
  clearTimeout(timer);
2246
2518
  }
2247
2519
  }
2248
- function accountLabel(identity, storedAccountId) {
2520
+ function accountLabel(identity, storedAccountId, verbose2 = false) {
2249
2521
  const accountId = identity?.accountId ?? storedAccountId;
2250
2522
  if (identity?.email !== void 0 && identity.email.trim() !== "") {
2251
- return accountId !== void 0 ? `${identity.email} (account ${accountId})` : identity.email;
2523
+ return verbose2 && accountId !== void 0 ? `${identity.email} (account ${accountId})` : identity.email;
2252
2524
  }
2253
2525
  if (accountId !== void 0)
2254
2526
  return `account ${accountId} (no email on record for it)`;
2255
- return "the account this credential belongs to, which this plane did not name";
2527
+ return "the account this credential belongs to, which this service did not name";
2256
2528
  }
2257
2529
  function onlyDate(iso) {
2258
2530
  if (iso === void 0)
@@ -2263,10 +2535,10 @@ function onlyDate(iso) {
2263
2535
  function actClause(act) {
2264
2536
  return act.kind === "create" ? `create a NEW project for that account, and install into ${act.targetRepo}` : `join the project this repository is pinned to (${act.engagementId}) as that account, and install into ${act.targetRepo}`;
2265
2537
  }
2266
- async function defaultAsk(question) {
2538
+ async function defaultAsk(question2) {
2267
2539
  const rl = createInterface({ input: process.stdin, output: process.stdout });
2268
2540
  try {
2269
- return await rl.question(question);
2541
+ return await rl.question(question2);
2270
2542
  } finally {
2271
2543
  rl.close();
2272
2544
  process.stdin.pause();
@@ -2284,7 +2556,10 @@ function actingAs(credential, identity) {
2284
2556
  const accountId = identity?.accountId ?? credential.accountId;
2285
2557
  return accountId === void 0 ? credential : { ...credential, accountId };
2286
2558
  }
2287
- function armScript(credential, act, label, home) {
2559
+ function question(colour) {
2560
+ 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: `;
2561
+ }
2562
+ function armScript(credential, act, label, plainLabel, home) {
2288
2563
  switch (credential.source) {
2289
2564
  case "browser-handoff":
2290
2565
  return {
@@ -2310,7 +2585,7 @@ function armScript(credential, act, label, home) {
2310
2585
  noTerminalNote: "",
2311
2586
  switchOpening: `[halfcycle] Signing you in through your browser \u2014 this install will use that account instead.
2312
2587
  `,
2313
- stopRemedy: `Stopped before anything was created \u2014 nothing was created and nothing was written. HALFCYCLE_TOKEN in this environment is the credential for ${label}, 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.`,
2588
+ stopRemedy: `Stopped before anything was created \u2014 nothing was created and nothing was written. 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.`,
2314
2589
  // The switch is real for THIS run (the new credential is what gets used) and it
2315
2590
  // does not persist: `obtainCredential` reads the variable first, so the next run
2316
2591
  // is the same variable again. Said here rather than discovered tomorrow.
@@ -2336,7 +2611,7 @@ function armScript(credential, act, label, home) {
2336
2611
  `,
2337
2612
  switchOpening: `[halfcycle] Forgetting this machine's saved sign-in and signing you in through your browser.
2338
2613
  `,
2339
- stopRemedy: `Stopped before anything was created \u2014 nothing was created and nothing was written. This machine's saved sign-in is for ${label}. 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.`,
2614
+ stopRemedy: `Stopped before anything was created \u2014 nothing was created and nothing was written. 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.`,
2340
2615
  afterSwitch: ""
2341
2616
  };
2342
2617
  }
@@ -2344,10 +2619,12 @@ function armScript(credential, act, label, home) {
2344
2619
  }
2345
2620
  async function confirmActingIdentity(serviceUrl, credential, act, deps = {}) {
2346
2621
  const write = deps.write ?? ((text) => process.stdout.write(text));
2622
+ const colour = deps.colour ?? decideColour({ isTTY: process.stdout.isTTY === true, env: deps.env ?? process.env });
2347
2623
  const identity = await describeAccount(serviceUrl, credential.credential, deps.fetchImpl);
2348
- const label = accountLabel(identity, credential.accountId);
2624
+ const plainLabel = accountLabel(identity, credential.accountId, deps.verbose === true);
2625
+ const label = ink("keyBold", plainLabel, colour);
2349
2626
  const acting = actingAs(credential, identity);
2350
- const script = armScript(credential, act, label, deps.home);
2627
+ const script = armScript(credential, act, label, plainLabel, deps.home);
2351
2628
  write(script.preamble);
2352
2629
  if (!script.asks)
2353
2630
  return acting;
@@ -2363,14 +2640,15 @@ async function confirmActingIdentity(serviceUrl, credential, act, deps = {}) {
2363
2640
  return acting;
2364
2641
  }
2365
2642
  const ask = deps.ask ?? defaultAsk;
2366
- const decision = readDecision(await ask(`[halfcycle] Continue as that account? [y] yes [s] sign in as someone else [anything else] stop: `));
2643
+ write("\n");
2644
+ const decision = readDecision(await ask(question(colour)));
2367
2645
  if (decision === "proceed")
2368
2646
  return acting;
2369
2647
  if (decision === "switch-account") {
2370
2648
  write(script.switchOpening);
2371
2649
  const replacement = await switchAccount(serviceUrl, deps);
2372
2650
  const replacementIdentity = await describeAccount(serviceUrl, replacement.credential, deps.fetchImpl);
2373
- write(`[halfcycle] Signed in as ${accountLabel(replacementIdentity, replacement.accountId)}. This install will ${actClause(act)}.
2651
+ write(`[halfcycle] Signed in as ${ink("keyBold", accountLabel(replacementIdentity, replacement.accountId, deps.verbose === true), colour)}. This install will ${actClause(act)}.
2374
2652
  `);
2375
2653
  if (script.afterSwitch !== "")
2376
2654
  write(script.afterSwitch);
@@ -2882,7 +3160,7 @@ async function openPhase(credential, phase, entry) {
2882
3160
  body: JSON.stringify({ phase, entry })
2883
3161
  });
2884
3162
  } catch (err) {
2885
- throw new Error(`[halfcycle] Could not reach the Halfcycle control plane at ${url}: ${err instanceof Error ? err.message : String(err)}. No phase was opened.`);
3163
+ throw new Error(`[halfcycle] Could not reach the Halfcycle service at ${url}: ${err instanceof Error ? err.message : String(err)}. No phase was opened.`);
2886
3164
  }
2887
3165
  if (!res.ok) {
2888
3166
  const body2 = await res.json().catch(() => null);
@@ -2935,7 +3213,7 @@ async function closePhase(credential, phase, outcome, close) {
2935
3213
  body: JSON.stringify({ phase, acceptanceOutcome: outcome, close })
2936
3214
  });
2937
3215
  } catch (err) {
2938
- throw new Error(`[halfcycle] Could not reach the Halfcycle control plane at ${url}: ${err instanceof Error ? err.message : String(err)}. The phase was not closed.`);
3216
+ throw new Error(`[halfcycle] Could not reach the Halfcycle service at ${url}: ${err instanceof Error ? err.message : String(err)}. The phase was not closed.`);
2939
3217
  }
2940
3218
  if (!res.ok) {
2941
3219
  const body2 = await res.json().catch(() => null);
@@ -3066,234 +3344,6 @@ var PLACEHOLDERS = {
3066
3344
  "--evidence": '"\u2026"'
3067
3345
  };
3068
3346
 
3069
- // dist/banner.js
3070
- var MARK_FULL = {
3071
- width: 32,
3072
- discRows: 7,
3073
- lines: [
3074
- " :+oshhhhso+:",
3075
- " :sdMMMMMMMMMMMMds:",
3076
- " :hMMMMMMMMMMMMMMMMMMh:",
3077
- " oMMMMMMMMMMMMMMMMMMMMMMo",
3078
- " oMMMMMMMMMMMMMMMMMMMMMMMMo",
3079
- " NMMMMMMMMMMMMMMMMMMMMMMMMN",
3080
- " :ssssssssssssssssssssssssss:",
3081
- " +dddddddddddddddddddddddddd+",
3082
- " Nd dN",
3083
- " oMy yMo",
3084
- " oMh: :hMo",
3085
- " :hMy- -yMh:",
3086
- " :sdNys++--++syNds:",
3087
- " :+oshhhhso+:"
3088
- ]
3089
- };
3090
- var MARK_NARROW = {
3091
- width: 24,
3092
- discRows: 5,
3093
- lines: [
3094
- " -ohdMMMMdho-",
3095
- " +dMMMMMMMMMMMMd+",
3096
- " yMMMMMMMMMMMMMMMMy",
3097
- " sMMMMMMMMMMMMMMMMMMs",
3098
- " yhhhhhhhhhhhhhhhhhhy",
3099
- " NdhhhhhhhhhhhhhhhhdN",
3100
- " sd ds",
3101
- " yd: :dy",
3102
- " +dy+ +yd+",
3103
- " -ohhhhhhhho-"
3104
- ]
3105
- };
3106
- var UNICODE_GLYPHS = {
3107
- rule: "\u2500",
3108
- swatch: "\u2584",
3109
- dot: "\xB7",
3110
- dash: "\u2014"
3111
- };
3112
- var ASCII_GLYPHS = {
3113
- rule: "-",
3114
- swatch: "#",
3115
- dot: "|",
3116
- dash: "-"
3117
- };
3118
- function toAscii(text) {
3119
- let out = text;
3120
- for (const key of Object.keys(UNICODE_GLYPHS)) {
3121
- out = out.split(UNICODE_GLYPHS[key]).join(ASCII_GLYPHS[key]);
3122
- }
3123
- return out.replace(/[^ -~]/g, "?");
3124
- }
3125
- var INK = {
3126
- plain: "",
3127
- dim: "2",
3128
- bold: "1",
3129
- /** Yellow — the nearest ANSI seat for the brand's gold, used for labels and the URL. */
3130
- key: "33",
3131
- keyBold: "33;1"
3132
- };
3133
- var SWATCH_SGR = ["90", "31", "32", "33", "34", "35", "36", "37"];
3134
- var ESC = "\x1B";
3135
- var RESET = `${ESC}[0m`;
3136
- function paint(segments, colour) {
3137
- return segments.map((s) => colour && s.sgr !== "" ? `${ESC}[${s.sgr}m${s.text}${RESET}` : s.text).join("");
3138
- }
3139
- var GAP = 4;
3140
- var FULL_LAYOUT = { art: MARK_FULL, label: 13, rule: 28, palette: true, minColumns: 76 };
3141
- var NARROW_LAYOUT = { art: MARK_NARROW, label: 10, rule: 20, palette: false, minColumns: 48 };
3142
- var DEFAULT_COLUMNS = 80;
3143
- function usableColumns(environment) {
3144
- const columns = environment.columns;
3145
- return columns !== void 0 && columns > 0 ? columns : DEFAULT_COLUMNS;
3146
- }
3147
- function isSet(value) {
3148
- return value !== void 0 && value !== "";
3149
- }
3150
- function plainTextRequested(env) {
3151
- return isSet(env["NO_COLOR"]) || env["TERM"] === "dumb";
3152
- }
3153
- function decideCut(environment = {}) {
3154
- const env = environment.env ?? {};
3155
- if (environment.quiet === true)
3156
- return "none";
3157
- if (environment.isTTY !== true)
3158
- return "line";
3159
- if (plainTextRequested(env))
3160
- return "line";
3161
- const columns = usableColumns(environment);
3162
- if (columns >= FULL_LAYOUT.minColumns)
3163
- return "full";
3164
- if (columns >= NARROW_LAYOUT.minColumns)
3165
- return "narrow";
3166
- return "line";
3167
- }
3168
- function decideColour(environment = {}) {
3169
- const env = environment.env ?? {};
3170
- const force = env["FORCE_COLOR"];
3171
- if (isSet(force))
3172
- return force !== "0";
3173
- if (plainTextRequested(env))
3174
- return false;
3175
- return environment.isTTY === true;
3176
- }
3177
- function decideUnicode(environment = {}) {
3178
- const env = environment.env ?? {};
3179
- if ((environment.platform ?? "") !== "win32") {
3180
- const locale = env["LC_ALL"] ?? env["LC_CTYPE"] ?? env["LANG"] ?? "";
3181
- return locale === "" || /utf-?8/i.test(locale);
3182
- }
3183
- return isSet(env["WT_SESSION"]) || isSet(env["TERMINUS_SUBLIME"]) || env["ConEmuTask"] === "{cmd::Cmder}" || env["TERM_PROGRAM"] === "vscode";
3184
- }
3185
- function padEnd(text, width) {
3186
- return text + " ".repeat(Math.max(0, width - text.length));
3187
- }
3188
- function clip(text, width) {
3189
- if (width < 8 || text.length <= width)
3190
- return text;
3191
- return `${text.slice(0, width - 3)}...`;
3192
- }
3193
- function paletteRow(swatch) {
3194
- const row = [];
3195
- SWATCH_SGR.forEach((sgr, i) => {
3196
- if (i > 0)
3197
- row.push({ text: " ", sgr: INK.plain });
3198
- row.push({ text: swatch.repeat(2), sgr });
3199
- });
3200
- return row;
3201
- }
3202
- function factColumn(content, layout, glyphs, text, valueWidth) {
3203
- const rows = [];
3204
- const head = text(content.head);
3205
- const at = head.indexOf("@");
3206
- rows.push(at < 0 ? [{ text: head, sgr: INK.keyBold }] : [
3207
- { text: head.slice(0, at), sgr: INK.keyBold },
3208
- { text: "@", sgr: INK.dim },
3209
- { text: head.slice(at + 1), sgr: INK.bold }
3210
- ]);
3211
- rows.push([{ text: glyphs.rule.repeat(layout.rule), sgr: INK.dim }]);
3212
- const narrow = layout.art === MARK_NARROW;
3213
- for (const fact2 of content.facts) {
3214
- const raw = narrow ? fact2.narrow : fact2.value;
3215
- if (raw === void 0)
3216
- continue;
3217
- rows.push([
3218
- { text: padEnd(`${text(fact2.label)}:`, layout.label), sgr: INK.key },
3219
- { text: clip(text(raw), valueWidth), sgr: INK.plain }
3220
- ]);
3221
- }
3222
- if (layout.palette) {
3223
- rows.push([]);
3224
- rows.push(paletteRow(glyphs.swatch));
3225
- }
3226
- return rows;
3227
- }
3228
- function renderBlock(content, layout, environment) {
3229
- const unicode = decideUnicode(environment);
3230
- const colour = decideColour(environment);
3231
- const glyphs = unicode ? UNICODE_GLYPHS : ASCII_GLYPHS;
3232
- const text = (raw) => unicode ? raw : toAscii(raw);
3233
- const art = layout.art;
3234
- const valueWidth = usableColumns(environment) - (art.width + GAP + layout.label);
3235
- const right = factColumn(content, layout, glyphs, text, valueWidth);
3236
- const top = Math.max(0, Math.floor((art.lines.length - right.length) / 2));
3237
- const height = Math.max(art.lines.length, right.length + top);
3238
- const lines = [];
3239
- for (let i = 0; i < height; i++) {
3240
- const artLine = art.lines[i] ?? "";
3241
- const segments = [];
3242
- if (artLine.length > 0) {
3243
- segments.push({ text: artLine, sgr: i < art.discRows ? INK.plain : INK.dim });
3244
- }
3245
- const rightRow = right[i - top];
3246
- if (rightRow !== void 0 && rightRow.length > 0) {
3247
- segments.push({ text: " ".repeat(Math.max(0, art.width + GAP - artLine.length)), sgr: INK.plain });
3248
- segments.push(...rightRow);
3249
- }
3250
- lines.push(segments);
3251
- }
3252
- const tagline = layout.art === MARK_NARROW ? content.tagNarrow ?? content.tag : content.tag;
3253
- lines.push([]);
3254
- tagline.forEach((line, i) => {
3255
- lines.push([{ text: ` ${text(line)}`, sgr: i === 0 ? INK.bold : INK.dim }]);
3256
- });
3257
- lines.push([]);
3258
- lines.push([{ text: ` ${text(content.url)}`, sgr: INK.key }]);
3259
- return lines.map((segments) => paint(segments, colour)).join("\n");
3260
- }
3261
- function renderLine(content, environment) {
3262
- const unicode = decideUnicode(environment);
3263
- const dot = unicode ? UNICODE_GLYPHS.dot : ASCII_GLYPHS.dot;
3264
- const named = content.version === "" ? "Halfcycle" : `Halfcycle v${content.version}`;
3265
- const raw = `${named} ${dot} ${content.url}`;
3266
- return unicode ? raw : toAscii(raw);
3267
- }
3268
- function renderBanner(content, environment = {}) {
3269
- switch (decideCut(environment)) {
3270
- case "none":
3271
- return "";
3272
- case "line":
3273
- return renderLine(content, environment);
3274
- case "narrow":
3275
- return renderBlock(content, NARROW_LAYOUT, environment);
3276
- case "full":
3277
- return renderBlock(content, FULL_LAYOUT, environment);
3278
- }
3279
- }
3280
- function printBanner(sink, content, environment = {}) {
3281
- const text = renderBanner(content, environment);
3282
- if (text === "")
3283
- return;
3284
- sink.write(`${text}
3285
- `);
3286
- }
3287
- function currentEnvironment(quiet2) {
3288
- return {
3289
- columns: process.stdout.columns,
3290
- isTTY: process.stdout.isTTY === true,
3291
- quiet: quiet2,
3292
- env: process.env,
3293
- platform: process.platform
3294
- };
3295
- }
3296
-
3297
3347
  // dist/banner-facts.js
3298
3348
  import { execFileSync as execFileSync3 } from "node:child_process";
3299
3349
  import { readFileSync as readFileSync8 } from "node:fs";
@@ -3352,7 +3402,7 @@ var REAL_PROBES = {
3352
3402
  return void 0;
3353
3403
  }
3354
3404
  },
3355
- planeHost: (env) => {
3405
+ websiteHost: (env) => {
3356
3406
  try {
3357
3407
  const resolved = resolveControlOrigin(env);
3358
3408
  return resolved.source === "default" ? BRAND_URL : new URL(resolved.origin).host;
@@ -3380,7 +3430,11 @@ function openingBannerContent(projectDir, env = process.env, probes = REAL_PROBE
3380
3430
  fact("Claude Code", probes.claudeCodeVersion()),
3381
3431
  fact("Project", project, name),
3382
3432
  fact("Status", status, installed === void 0 ? "new" : "installed"),
3383
- fact("Plane", probes.planeHost(env))
3433
+ // `Website`, NOT `Plane` (W-3, #868). "Plane" is our internal word for the control
3434
+ // plane; the developer reading this has never heard it, and raw method vocabulary
3435
+ // reaching a client surface is a recorded finding class in this product. The row
3436
+ // answers *which Halfcycle is this*, and an address is the answer they recognise.
3437
+ fact("Website", probes.websiteHost(env))
3384
3438
  ].filter((f) => f !== void 0);
3385
3439
  return {
3386
3440
  head: `halfcycle@${name}`,
@@ -3509,7 +3563,7 @@ ${USAGE}`);
3509
3563
  const controlOrigin = resolveControlOrigin(process.env);
3510
3564
  const serviceUrl = controlOrigin.origin;
3511
3565
  if (verbose || controlOrigin.source !== "default") {
3512
- process.stdout.write(`[halfcycle] Halfcycle plane: ${controlOriginNote(controlOrigin)}
3566
+ process.stdout.write(`[halfcycle] Halfcycle service: ${controlOriginNote(controlOrigin)}
3513
3567
  `);
3514
3568
  }
3515
3569
  const pinned = readPinnedEngagement(targetRepo);
@@ -3536,7 +3590,7 @@ ${USAGE}`);
3536
3590
  `);
3537
3591
  }
3538
3592
  } else if (requestedId !== void 0) {
3539
- const joined = await joinPinnedEngagement(serviceUrl, requestedId, targetRepo, { assumeYes });
3593
+ const joined = await joinPinnedEngagement(serviceUrl, requestedId, targetRepo, { assumeYes, verbose });
3540
3594
  engagementId = joined.engagementId;
3541
3595
  actingAccountId = joined.actingAccountId;
3542
3596
  credential = {
@@ -3555,7 +3609,7 @@ ${USAGE}`);
3555
3609
  `);
3556
3610
  }
3557
3611
  } else {
3558
- const created = await createOwnedEngagement(serviceUrl, targetRepo, { assumeYes });
3612
+ const created = await createOwnedEngagement(serviceUrl, targetRepo, { assumeYes, verbose });
3559
3613
  engagementId = created.engagementId;
3560
3614
  actingAccountId = created.actingAccountId;
3561
3615
  credential = {
@@ -3637,7 +3691,7 @@ ${USAGE}`);
3637
3691
  const identity = await describeAccount(credential.serviceUrl, credential.token);
3638
3692
  const written = result.writtenPaths.length;
3639
3693
  const merged = result.mergedPaths.length;
3640
- process.stdout.write(`[halfcycle] Signed in as ${accountLabel(identity, actingAccountId)}
3694
+ process.stdout.write(`[halfcycle] Signed in as ${accountLabel(identity, actingAccountId, verbose)}
3641
3695
  `);
3642
3696
  process.stdout.write(`[halfcycle] Found: ${foundLine}
3643
3697
  `);