halfcycle 0.3.13 → 0.3.15

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,286 @@ 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
+ /** `--console-accent` (`#19d3ff`) — the console's own accent, cyan being its ANSI seat. */
2298
+ accent: "36",
2299
+ /** `--good` — the console's affirmative. */
2300
+ good: "32",
2301
+ /** `--bad` — the console's negative. */
2302
+ bad: "31"
2303
+ };
2304
+ var SWATCH_SGR = [
2305
+ INK.dim,
2306
+ INK.faint,
2307
+ INK.key,
2308
+ INK.keyBold,
2309
+ INK.accent,
2310
+ INK.good,
2311
+ INK.bad
2312
+ ];
2313
+ var ESC = "\x1B";
2314
+ var RESET = `${ESC}[0m`;
2315
+ function paint(segments, colour) {
2316
+ return segments.map((s) => colour && s.sgr !== "" ? `${ESC}[${s.sgr}m${s.text}${RESET}` : s.text).join("");
2317
+ }
2318
+ function ink(name, text, colour) {
2319
+ const sgr = INK[name];
2320
+ return colour && sgr !== "" ? `${ESC}[${sgr}m${text}${RESET}` : text;
2321
+ }
2322
+ var GAP = 4;
2323
+ var FULL_LAYOUT = { art: MARK_FULL, label: 13, rule: 28, palette: true, minColumns: 76 };
2324
+ var NARROW_LAYOUT = { art: MARK_NARROW, label: 10, rule: 20, palette: false, minColumns: 48 };
2325
+ var DEFAULT_COLUMNS = 80;
2326
+ function usableColumns(environment) {
2327
+ const columns = environment.columns;
2328
+ return columns !== void 0 && columns > 0 ? columns : DEFAULT_COLUMNS;
2329
+ }
2330
+ function isSet(value) {
2331
+ return value !== void 0 && value !== "";
2332
+ }
2333
+ function plainTextRequested(env) {
2334
+ return isSet(env["NO_COLOR"]) || env["TERM"] === "dumb";
2335
+ }
2336
+ function decideCut(environment = {}) {
2337
+ const env = environment.env ?? {};
2338
+ if (environment.quiet === true)
2339
+ return "none";
2340
+ if (environment.isTTY !== true)
2341
+ return "line";
2342
+ if (plainTextRequested(env))
2343
+ return "line";
2344
+ const columns = usableColumns(environment);
2345
+ if (columns >= FULL_LAYOUT.minColumns)
2346
+ return "full";
2347
+ if (columns >= NARROW_LAYOUT.minColumns)
2348
+ return "narrow";
2349
+ return "line";
2350
+ }
2351
+ function decideColour(environment = {}) {
2352
+ const env = environment.env ?? {};
2353
+ const force = env["FORCE_COLOR"];
2354
+ if (isSet(force))
2355
+ return force !== "0";
2356
+ if (plainTextRequested(env))
2357
+ return false;
2358
+ return environment.isTTY === true;
2359
+ }
2360
+ function decideUnicode(environment = {}) {
2361
+ const env = environment.env ?? {};
2362
+ if ((environment.platform ?? "") !== "win32") {
2363
+ const locale = env["LC_ALL"] ?? env["LC_CTYPE"] ?? env["LANG"] ?? "";
2364
+ return locale === "" || /utf-?8/i.test(locale);
2365
+ }
2366
+ return isSet(env["WT_SESSION"]) || isSet(env["TERMINUS_SUBLIME"]) || env["ConEmuTask"] === "{cmd::Cmder}" || env["TERM_PROGRAM"] === "vscode";
2367
+ }
2368
+ function padEnd(text, width) {
2369
+ return text + " ".repeat(Math.max(0, width - text.length));
2370
+ }
2371
+ function clip(text, width) {
2372
+ if (width < 8 || text.length <= width)
2373
+ return text;
2374
+ return `${text.slice(0, width - 3)}...`;
2375
+ }
2376
+ function paletteRow(swatch) {
2377
+ const row = [];
2378
+ SWATCH_SGR.forEach((sgr, i) => {
2379
+ if (i > 0)
2380
+ row.push({ text: " ", sgr: INK.plain });
2381
+ row.push({ text: swatch.repeat(2), sgr });
2382
+ });
2383
+ return row;
2384
+ }
2385
+ function factColumn(content, layout, glyphs, text, valueWidth) {
2386
+ const rows = [];
2387
+ const head = text(content.head);
2388
+ const at = head.indexOf("@");
2389
+ rows.push(at < 0 ? [{ text: head, sgr: INK.keyBold }] : [
2390
+ { text: head.slice(0, at), sgr: INK.keyBold },
2391
+ { text: "@", sgr: INK.dim },
2392
+ { text: head.slice(at + 1), sgr: INK.bold }
2393
+ ]);
2394
+ rows.push([{ text: glyphs.rule.repeat(layout.rule), sgr: INK.dim }]);
2395
+ const narrow = layout.art === MARK_NARROW;
2396
+ for (const fact2 of content.facts) {
2397
+ const raw = narrow ? fact2.narrow : fact2.value;
2398
+ if (raw === void 0)
2399
+ continue;
2400
+ rows.push([
2401
+ { text: padEnd(`${text(fact2.label)}:`, layout.label), sgr: INK.key },
2402
+ { text: clip(text(raw), valueWidth), sgr: INK.plain }
2403
+ ]);
2404
+ }
2405
+ if (layout.palette) {
2406
+ rows.push([]);
2407
+ rows.push(paletteRow(glyphs.swatch));
2408
+ }
2409
+ return rows;
2410
+ }
2411
+ var MARGIN = 2;
2412
+ function lastSentenceStart(line) {
2413
+ const at = line.lastIndexOf(". ");
2414
+ return at < 0 ? 0 : at + 2;
2415
+ }
2416
+ function taglineRow(line, first, last) {
2417
+ const indent = { text: " ", sgr: INK.plain };
2418
+ if (first)
2419
+ return [indent, { text: line, sgr: INK.keyBold }];
2420
+ if (!last)
2421
+ return [indent, { text: line, sgr: INK.dim }];
2422
+ const at = lastSentenceStart(line);
2423
+ if (at === 0)
2424
+ return [indent, { text: line, sgr: INK.key }];
2425
+ return [
2426
+ indent,
2427
+ { text: line.slice(0, at), sgr: INK.dim },
2428
+ { text: line.slice(at), sgr: INK.key }
2429
+ ];
2430
+ }
2431
+ function renderBlock(content, layout, environment) {
2432
+ const unicode = decideUnicode(environment);
2433
+ const colour = decideColour(environment);
2434
+ const glyphs = unicode ? UNICODE_GLYPHS : ASCII_GLYPHS;
2435
+ const text = (raw) => unicode ? raw : toAscii(raw);
2436
+ const art = layout.art;
2437
+ const valueWidth = usableColumns(environment) - (art.width + GAP + layout.label);
2438
+ const right = factColumn(content, layout, glyphs, text, valueWidth);
2439
+ const top = Math.max(0, Math.floor((art.lines.length - right.length) / 2));
2440
+ const height = Math.max(art.lines.length, right.length + top);
2441
+ const lines = Array.from({ length: MARGIN }, () => []);
2442
+ for (let i = 0; i < height; i++) {
2443
+ const artLine = art.lines[i] ?? "";
2444
+ const segments = [];
2445
+ if (artLine.length > 0) {
2446
+ segments.push({ text: artLine, sgr: i < art.discRows ? INK.plain : INK.dim });
2447
+ }
2448
+ const rightRow = right[i - top];
2449
+ if (rightRow !== void 0 && rightRow.length > 0) {
2450
+ segments.push({ text: " ".repeat(Math.max(0, art.width + GAP - artLine.length)), sgr: INK.plain });
2451
+ segments.push(...rightRow);
2452
+ }
2453
+ lines.push(segments);
2454
+ }
2455
+ const tagline = layout.art === MARK_NARROW ? content.tagNarrow ?? content.tag : content.tag;
2456
+ lines.push([]);
2457
+ tagline.forEach((line, i) => {
2458
+ lines.push(taglineRow(text(line), i === 0, i === tagline.length - 1));
2459
+ });
2460
+ for (let i = 0; i < MARGIN; i++)
2461
+ lines.push([]);
2462
+ return lines.map((segments) => paint(segments, colour)).join("\n");
2463
+ }
2464
+ function renderLine(content, environment) {
2465
+ const unicode = decideUnicode(environment);
2466
+ const dot = unicode ? UNICODE_GLYPHS.dot : ASCII_GLYPHS.dot;
2467
+ const named = content.version === "" ? "Halfcycle" : `Halfcycle v${content.version}`;
2468
+ const raw = `${named} ${dot} ${content.url}`;
2469
+ return unicode ? raw : toAscii(raw);
2470
+ }
2471
+ function renderBanner(content, environment = {}) {
2472
+ switch (decideCut(environment)) {
2473
+ case "none":
2474
+ return "";
2475
+ case "line":
2476
+ return renderLine(content, environment);
2477
+ case "narrow":
2478
+ return renderBlock(content, NARROW_LAYOUT, environment);
2479
+ case "full":
2480
+ return renderBlock(content, FULL_LAYOUT, environment);
2481
+ }
2482
+ }
2483
+ function printBanner(sink, content, environment = {}) {
2484
+ const text = renderBanner(content, environment);
2485
+ if (text === "")
2486
+ return;
2487
+ sink.write(`${text}
2488
+ `);
2489
+ }
2490
+ function currentEnvironment(quiet2) {
2491
+ return {
2492
+ columns: process.stdout.columns,
2493
+ isTTY: process.stdout.isTTY === true,
2494
+ quiet: quiet2,
2495
+ env: process.env,
2496
+ platform: process.platform
2497
+ };
2498
+ }
2499
+
2500
+ // dist/confirm-identity.js
2227
2501
  var IDENTITY_TIMEOUT_MS = 5e3;
2228
2502
  var IDENTITY_DECLINED = "identity-declined";
2229
2503
  async function describeAccount(serviceUrl, credential, fetchImpl = fetch) {
@@ -2245,14 +2519,14 @@ async function describeAccount(serviceUrl, credential, fetchImpl = fetch) {
2245
2519
  clearTimeout(timer);
2246
2520
  }
2247
2521
  }
2248
- function accountLabel(identity, storedAccountId) {
2522
+ function accountLabel(identity, storedAccountId, verbose2 = false) {
2249
2523
  const accountId = identity?.accountId ?? storedAccountId;
2250
2524
  if (identity?.email !== void 0 && identity.email.trim() !== "") {
2251
- return accountId !== void 0 ? `${identity.email} (account ${accountId})` : identity.email;
2525
+ return verbose2 && accountId !== void 0 ? `${identity.email} (account ${accountId})` : identity.email;
2252
2526
  }
2253
2527
  if (accountId !== void 0)
2254
2528
  return `account ${accountId} (no email on record for it)`;
2255
- return "the account this credential belongs to, which this plane did not name";
2529
+ return "the account this credential belongs to, which this service did not name";
2256
2530
  }
2257
2531
  function onlyDate(iso) {
2258
2532
  if (iso === void 0)
@@ -2263,10 +2537,10 @@ function onlyDate(iso) {
2263
2537
  function actClause(act) {
2264
2538
  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
2539
  }
2266
- async function defaultAsk(question) {
2540
+ async function defaultAsk(question2) {
2267
2541
  const rl = createInterface({ input: process.stdin, output: process.stdout });
2268
2542
  try {
2269
- return await rl.question(question);
2543
+ return await rl.question(question2);
2270
2544
  } finally {
2271
2545
  rl.close();
2272
2546
  process.stdin.pause();
@@ -2284,7 +2558,10 @@ function actingAs(credential, identity) {
2284
2558
  const accountId = identity?.accountId ?? credential.accountId;
2285
2559
  return accountId === void 0 ? credential : { ...credential, accountId };
2286
2560
  }
2287
- function armScript(credential, act, label, home) {
2561
+ function question(colour) {
2562
+ 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: `;
2563
+ }
2564
+ function armScript(credential, act, label, plainLabel, home) {
2288
2565
  switch (credential.source) {
2289
2566
  case "browser-handoff":
2290
2567
  return {
@@ -2310,7 +2587,7 @@ function armScript(credential, act, label, home) {
2310
2587
  noTerminalNote: "",
2311
2588
  switchOpening: `[halfcycle] Signing you in through your browser \u2014 this install will use that account instead.
2312
2589
  `,
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.`,
2590
+ 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
2591
  // The switch is real for THIS run (the new credential is what gets used) and it
2315
2592
  // does not persist: `obtainCredential` reads the variable first, so the next run
2316
2593
  // is the same variable again. Said here rather than discovered tomorrow.
@@ -2336,7 +2613,7 @@ function armScript(credential, act, label, home) {
2336
2613
  `,
2337
2614
  switchOpening: `[halfcycle] Forgetting this machine's saved sign-in and signing you in through your browser.
2338
2615
  `,
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.`,
2616
+ 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
2617
  afterSwitch: ""
2341
2618
  };
2342
2619
  }
@@ -2344,10 +2621,12 @@ function armScript(credential, act, label, home) {
2344
2621
  }
2345
2622
  async function confirmActingIdentity(serviceUrl, credential, act, deps = {}) {
2346
2623
  const write = deps.write ?? ((text) => process.stdout.write(text));
2624
+ const colour = deps.colour ?? decideColour({ isTTY: process.stdout.isTTY === true, env: deps.env ?? process.env });
2347
2625
  const identity = await describeAccount(serviceUrl, credential.credential, deps.fetchImpl);
2348
- const label = accountLabel(identity, credential.accountId);
2626
+ const plainLabel = accountLabel(identity, credential.accountId, deps.verbose === true);
2627
+ const label = ink("keyBold", plainLabel, colour);
2349
2628
  const acting = actingAs(credential, identity);
2350
- const script = armScript(credential, act, label, deps.home);
2629
+ const script = armScript(credential, act, label, plainLabel, deps.home);
2351
2630
  write(script.preamble);
2352
2631
  if (!script.asks)
2353
2632
  return acting;
@@ -2363,14 +2642,15 @@ async function confirmActingIdentity(serviceUrl, credential, act, deps = {}) {
2363
2642
  return acting;
2364
2643
  }
2365
2644
  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: `));
2645
+ write("\n");
2646
+ const decision = readDecision(await ask(question(colour)));
2367
2647
  if (decision === "proceed")
2368
2648
  return acting;
2369
2649
  if (decision === "switch-account") {
2370
2650
  write(script.switchOpening);
2371
2651
  const replacement = await switchAccount(serviceUrl, deps);
2372
2652
  const replacementIdentity = await describeAccount(serviceUrl, replacement.credential, deps.fetchImpl);
2373
- write(`[halfcycle] Signed in as ${accountLabel(replacementIdentity, replacement.accountId)}. This install will ${actClause(act)}.
2653
+ write(`[halfcycle] Signed in as ${ink("keyBold", accountLabel(replacementIdentity, replacement.accountId, deps.verbose === true), colour)}. This install will ${actClause(act)}.
2374
2654
  `);
2375
2655
  if (script.afterSwitch !== "")
2376
2656
  write(script.afterSwitch);
@@ -2882,7 +3162,7 @@ async function openPhase(credential, phase, entry) {
2882
3162
  body: JSON.stringify({ phase, entry })
2883
3163
  });
2884
3164
  } 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.`);
3165
+ throw new Error(`[halfcycle] Could not reach the Halfcycle service at ${url}: ${err instanceof Error ? err.message : String(err)}. No phase was opened.`);
2886
3166
  }
2887
3167
  if (!res.ok) {
2888
3168
  const body2 = await res.json().catch(() => null);
@@ -2935,7 +3215,7 @@ async function closePhase(credential, phase, outcome, close) {
2935
3215
  body: JSON.stringify({ phase, acceptanceOutcome: outcome, close })
2936
3216
  });
2937
3217
  } 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.`);
3218
+ 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
3219
  }
2940
3220
  if (!res.ok) {
2941
3221
  const body2 = await res.json().catch(() => null);
@@ -3066,234 +3346,6 @@ var PLACEHOLDERS = {
3066
3346
  "--evidence": '"\u2026"'
3067
3347
  };
3068
3348
 
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
3349
  // dist/banner-facts.js
3298
3350
  import { execFileSync as execFileSync3 } from "node:child_process";
3299
3351
  import { readFileSync as readFileSync8 } from "node:fs";
@@ -3352,7 +3404,7 @@ var REAL_PROBES = {
3352
3404
  return void 0;
3353
3405
  }
3354
3406
  },
3355
- planeHost: (env) => {
3407
+ websiteHost: (env) => {
3356
3408
  try {
3357
3409
  const resolved = resolveControlOrigin(env);
3358
3410
  return resolved.source === "default" ? BRAND_URL : new URL(resolved.origin).host;
@@ -3380,7 +3432,11 @@ function openingBannerContent(projectDir, env = process.env, probes = REAL_PROBE
3380
3432
  fact("Claude Code", probes.claudeCodeVersion()),
3381
3433
  fact("Project", project, name),
3382
3434
  fact("Status", status, installed === void 0 ? "new" : "installed"),
3383
- fact("Plane", probes.planeHost(env))
3435
+ // `Website`, NOT `Plane` (W-3, #868). "Plane" is our internal word for the control
3436
+ // plane; the developer reading this has never heard it, and raw method vocabulary
3437
+ // reaching a client surface is a recorded finding class in this product. The row
3438
+ // answers *which Halfcycle is this*, and an address is the answer they recognise.
3439
+ fact("Website", probes.websiteHost(env))
3384
3440
  ].filter((f) => f !== void 0);
3385
3441
  return {
3386
3442
  head: `halfcycle@${name}`,
@@ -3509,7 +3565,7 @@ ${USAGE}`);
3509
3565
  const controlOrigin = resolveControlOrigin(process.env);
3510
3566
  const serviceUrl = controlOrigin.origin;
3511
3567
  if (verbose || controlOrigin.source !== "default") {
3512
- process.stdout.write(`[halfcycle] Halfcycle plane: ${controlOriginNote(controlOrigin)}
3568
+ process.stdout.write(`[halfcycle] Halfcycle service: ${controlOriginNote(controlOrigin)}
3513
3569
  `);
3514
3570
  }
3515
3571
  const pinned = readPinnedEngagement(targetRepo);
@@ -3536,7 +3592,7 @@ ${USAGE}`);
3536
3592
  `);
3537
3593
  }
3538
3594
  } else if (requestedId !== void 0) {
3539
- const joined = await joinPinnedEngagement(serviceUrl, requestedId, targetRepo, { assumeYes });
3595
+ const joined = await joinPinnedEngagement(serviceUrl, requestedId, targetRepo, { assumeYes, verbose });
3540
3596
  engagementId = joined.engagementId;
3541
3597
  actingAccountId = joined.actingAccountId;
3542
3598
  credential = {
@@ -3555,7 +3611,7 @@ ${USAGE}`);
3555
3611
  `);
3556
3612
  }
3557
3613
  } else {
3558
- const created = await createOwnedEngagement(serviceUrl, targetRepo, { assumeYes });
3614
+ const created = await createOwnedEngagement(serviceUrl, targetRepo, { assumeYes, verbose });
3559
3615
  engagementId = created.engagementId;
3560
3616
  actingAccountId = created.actingAccountId;
3561
3617
  credential = {
@@ -3637,7 +3693,7 @@ ${USAGE}`);
3637
3693
  const identity = await describeAccount(credential.serviceUrl, credential.token);
3638
3694
  const written = result.writtenPaths.length;
3639
3695
  const merged = result.mergedPaths.length;
3640
- process.stdout.write(`[halfcycle] Signed in as ${accountLabel(identity, actingAccountId)}
3696
+ process.stdout.write(`[halfcycle] Signed in as ${accountLabel(identity, actingAccountId, verbose)}
3641
3697
  `);
3642
3698
  process.stdout.write(`[halfcycle] Found: ${foundLine}
3643
3699
  `);