claude-use 11.60.2 → 11.61.0

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/cli.cjs CHANGED
@@ -25451,9 +25451,17 @@ function resolveStatusline(settings2) {
25451
25451
  if (forContext.warnAt >= forContext.criticalAt) {
25452
25452
  throw new RangeError(`statusline context.warnAt (${String(forContext.warnAt)}) must be below context.criticalAt (${String(forContext.criticalAt)}), each following bands while it is unset`);
25453
25453
  }
25454
+ const merged = new Set(resolved.layout.merge.flat());
25455
+ const unmerged = resolved.layout.rightAlign.filter((row) => !merged.has(row));
25456
+ if (unmerged.length > 0) {
25457
+ throw new RangeError(`statusline layout.rightAlign names ${unmerged.join(", ")}, which no group in layout.merge contains`);
25458
+ }
25459
+ if (resolved.layout.forceMaxWidth && resolved.layout.maxWidth === 0) {
25460
+ throw new RangeError("statusline layout.forceMaxWidth needs layout.maxWidth to be set");
25461
+ }
25454
25462
  return resolved;
25455
25463
  }
25456
- var STATUSLINE_HEADER_SEGMENTS, STATUSLINE_ROWS, STATUSLINE_SESSION_TIME_PARTS, PROMPT_CACHE_TTL_SECONDS, STATUSLINE_COLOUR_NAMES, DEFAULT_HEADER_SEGMENTS, DEFAULT_ROWS, STATUSLINE_PATH_LEVELS, STATUSLINE_USAGE_VALUES, STATUSLINE_TIME_FORMATS, STATUSLINE_HOUR_CYCLES, MAX_PERCENT, MAX_STATUSLINE_PALETTE_INDEX, MAX_STATUSLINE_LABEL_COLUMNS, MAX_BAR_CELLS, MAX_ACCOUNT_USER_CHARS, MAX_DISPLAY_TEXT_CHARS, LABEL_RE2, OPTIONAL_LABEL_RE, colourValue, barCharacter, STATUSLINE_CONTEXT_VALUES, STATUSLINE_CONTEXT_ESTIMATES, AUTOCOMPACT_RESERVE_FRACTION, AUTOCOMPACT_RESERVE_RAMP_START, AUTOCOMPACT_RESERVE_RAMP_END, labelText, overrideText, wholePercent, thresholdPercent, SHAPES, UNSET_NOTE, StatuslineSchema, ResolvedStatuslineSchema, STATUSLINE_DEFAULTS, STATUSLINE_ENV;
25464
+ var STATUSLINE_HEADER_SEGMENTS, STATUSLINE_ROWS, STATUSLINE_SESSION_TIME_PARTS, PROMPT_CACHE_TTL_SECONDS, STATUSLINE_COLOUR_NAMES, DEFAULT_HEADER_SEGMENTS, DEFAULT_ROWS, STATUSLINE_PATH_LEVELS, STATUSLINE_USAGE_VALUES, STATUSLINE_TIME_FORMATS, STATUSLINE_HOUR_CYCLES, MAX_PERCENT, MAX_STATUSLINE_PALETTE_INDEX, MAX_STATUSLINE_LABEL_COLUMNS, MIN_STATUSLINE_COLUMNS, MAX_STATUSLINE_COLUMNS, MAX_BAR_CELLS, MAX_ACCOUNT_USER_CHARS, MAX_DISPLAY_TEXT_CHARS, LABEL_RE2, OPTIONAL_LABEL_RE, mergeGroups, colourValue, barCharacter, STATUSLINE_CONTEXT_VALUES, STATUSLINE_CONTEXT_ESTIMATES, AUTOCOMPACT_RESERVE_FRACTION, AUTOCOMPACT_RESERVE_RAMP_START, AUTOCOMPACT_RESERVE_RAMP_END, labelText, overrideText, wholePercent, thresholdPercent, SHAPES, UNSET_NOTE, StatuslineSchema, ResolvedStatuslineSchema, STATUSLINE_DEFAULTS, STATUSLINE_ENV;
25457
25465
  var init_statusline = __esm({
25458
25466
  "src/config/statusline.ts"() {
25459
25467
  "use strict";
@@ -25494,11 +25502,17 @@ var init_statusline = __esm({
25494
25502
  MAX_PERCENT = 100;
25495
25503
  MAX_STATUSLINE_PALETTE_INDEX = 255;
25496
25504
  MAX_STATUSLINE_LABEL_COLUMNS = 12;
25505
+ MIN_STATUSLINE_COLUMNS = 20;
25506
+ MAX_STATUSLINE_COLUMNS = 1e3;
25497
25507
  MAX_BAR_CELLS = 40;
25498
25508
  MAX_ACCOUNT_USER_CHARS = 254;
25499
25509
  MAX_DISPLAY_TEXT_CHARS = 80;
25500
25510
  LABEL_RE2 = /^[^\p{C}\p{Z}]+(?: [^\p{C}\p{Z}]+)*$/u;
25501
25511
  OPTIONAL_LABEL_RE = /^(?:[^\p{C}\p{Z}]+(?: [^\p{C}\p{Z}]+)*)?$/u;
25512
+ mergeGroups = external_exports.array(distinctList(external_exports.enum(STATUSLINE_ROWS)).min(2)).refine((groups) => {
25513
+ const rows = groups.flat();
25514
+ return new Set(rows).size === rows.length;
25515
+ }, "must not name a row in more than one group");
25502
25516
  colourValue = external_exports.union([external_exports.enum(STATUSLINE_COLOUR_NAMES), external_exports.number().int().min(0).max(MAX_STATUSLINE_PALETTE_INDEX), external_exports.string().regex(/^#[0-9a-fA-F]{6}$/, "must be a colour name, a palette index from 0 to 255 or #rrggbb")]);
25503
25517
  barCharacter = external_exports.string().refine(isSingleVisibleGrapheme, "must be a single visible character that takes one terminal column");
25504
25518
  STATUSLINE_CONTEXT_VALUES = ["both", "percent", "tokens", "remaining"];
@@ -25513,7 +25527,14 @@ var init_statusline = __esm({
25513
25527
  SHAPES = {
25514
25528
  layout: {
25515
25529
  header: distinctList(external_exports.enum(STATUSLINE_HEADER_SEGMENTS)).describe("The header segments to draw, in drawing order; a segment left out is hidden, and an empty list hides the header."),
25516
- rows: distinctList(external_exports.enum(STATUSLINE_ROWS)).describe("The rows to draw below the header, in drawing order; a row left out is hidden, and an empty list hides every row.")
25530
+ rows: distinctList(external_exports.enum(STATUSLINE_ROWS)).describe("The rows to draw below the header, in drawing order; a row left out is hidden, and an empty list hides every row."),
25531
+ mode: external_exports.enum(["expanded", "compact"]).describe("How the statusline is laid out: expanded draws the header and then the rows, each on lines of its own as layout.merge allows, and compact draws the header segments and every row's content on one line, starting a new line only where the line would be wider than the width. layout.mergeBars, layout.merge, layout.rightAlign and layout.rule have no effect in compact."),
25532
+ rule: external_exports.enum(["none", "belowHeader"]).describe(`Whether a rule is drawn across the line between the header and the rows beneath it, as wide as the header's widest line (at least ${String(MIN_STATUSLINE_COLUMNS)} columns, at most the width); it is drawn only when both the header and the rows have something to show, and not in the compact layout.`),
25533
+ mergeBars: external_exports.boolean().describe("Whether bar rows that sit next to each other in layout.rows share a line, and one grid, when they fit (the context and quota bars do by default); when false each bar row is on a line of its own, its bars still aligned to its neighbours'. A row of text is never joined by this setting, whichever form the quota row takes: layout.merge does that."),
25534
+ merge: mergeGroups.describe("Groups of rows that share a line when they sit next to each other in layout.rows and fit on one line together, and are drawn on lines of their own when they do not; each group names at least two rows, and no row belongs to two groups. A group may mix bar rows and rows of text, which are joined with the separator when every member is a single line. Bar rows outside a group follow layout.mergeBars. A group in which no row has anything to show draws nothing: one with a row of text in it still ends a run of bars sharing a line, as an empty row of text does, and one of bar rows alone is left out, so the bars either side of it share as if it were not there."),
25535
+ rightAlign: distinctList(external_exports.enum(STATUSLINE_ROWS)).describe("Rows that start the right-aligned part of a merged line: the first of them to appear on a line, and the rows after it, are pushed to the right edge, with the gap padded by spaces. A row named here must belong to a group in layout.merge, and it has no effect when it is the first row of its line."),
25536
+ maxWidth: external_exports.number().int().min(0).max(MAX_STATUSLINE_COLUMNS).refine((columns) => columns === 0 || columns >= MIN_STATUSLINE_COLUMNS, `must be 0, or at least ${String(MIN_STATUSLINE_COLUMNS)} columns`).describe(`The exact width in columns lines are drawn to when the client reports none and the COLUMNS environment variable names none, in place of the default width it would assume for the terminal; unlike a terminal's width, it is not reduced by the columns Claude Code keeps back for itself. It is 0, to leave it unset, or from ${String(MIN_STATUSLINE_COLUMNS)} to ${String(MAX_STATUSLINE_COLUMNS)}; a value outside that is refused, not clamped.`),
25537
+ forceMaxWidth: external_exports.boolean().describe("Whether maxWidth is the exact width lines are drawn to for whatever the client reports, even when the terminal is narrower or wider; it needs maxWidth to be set.")
25517
25538
  },
25518
25539
  model: {
25519
25540
  format: external_exports.enum(["full", "compact", "short"]).describe("How the model's name is written: full is the name as the client gives it, compact drops a context-window suffix such as (1M context), and short also drops a leading Claude."),
@@ -25659,7 +25680,7 @@ var init_statusline = __esm({
25659
25680
  jj: external_exports.strictObject(SHAPES.jj)
25660
25681
  });
25661
25682
  STATUSLINE_DEFAULTS = {
25662
- layout: { header: [...DEFAULT_HEADER_SEGMENTS], rows: [...DEFAULT_ROWS] },
25683
+ layout: { header: [...DEFAULT_HEADER_SEGMENTS], rows: [...DEFAULT_ROWS], mode: "expanded", rule: "none", mergeBars: true, merge: [], rightAlign: [], maxWidth: 0, forceMaxWidth: false },
25663
25684
  model: { format: "full", override: "", source: "client" },
25664
25685
  provider: { name: "" },
25665
25686
  account: { parts: ["user", "organisation", "plan"], userLength: 0 },
@@ -32600,6 +32621,9 @@ function keyOf(path70, declared) {
32600
32621
  if (leaf instanceof external_exports.ZodArray && leaf.element instanceof external_exports.ZodEnum) {
32601
32622
  return { ...common, kind: "list", choices: Object.values(leaf.element.enum).map(String) };
32602
32623
  }
32624
+ if (leaf instanceof external_exports.ZodArray && leaf.element instanceof external_exports.ZodArray && leaf.element.element instanceof external_exports.ZodEnum) {
32625
+ return { ...common, kind: "groups", choices: Object.values(leaf.element.element.enum).map(String) };
32626
+ }
32603
32627
  throw new TypeError(`statusline setting ${path70.join(".")} has a kind the key listing does not describe`);
32604
32628
  }
32605
32629
  function walk(shape, prefix) {
@@ -32610,7 +32634,7 @@ function statuslineKeys() {
32610
32634
  }
32611
32635
  function formatStatuslineKeys(keys) {
32612
32636
  return keys.map((key) => {
32613
- const takes = key.kind === "choice" || key.kind === "list" || key.kind === "colour" ? `${key.kind} of ${key.choices.join(", ")}` : key.kind;
32637
+ const takes = key.kind === "choice" || key.kind === "list" || key.kind === "groups" || key.kind === "colour" ? `${key.kind} of ${key.choices.join(", ")}` : key.kind;
32614
32638
  return `${key.path} (${takes}; ${key.optional ? "no default; none unsets it over shallower layers" : `default ${JSON.stringify(key.defaultValue)}`})
32615
32639
  ${key.description}`;
32616
32640
  }).join("\n");
@@ -32662,6 +32686,8 @@ function parseStatuslineAssignment(text5) {
32662
32686
  return { path: path70, value: /^\d+$/.test(raw) ? Number(raw) : raw };
32663
32687
  case "list":
32664
32688
  return { path: path70, value: raw === "" ? [] : raw.split(",") };
32689
+ case "groups":
32690
+ return { path: path70, value: raw === "" ? [] : raw.split(";").map((group) => group.split(",")) };
32665
32691
  default:
32666
32692
  return key.kind;
32667
32693
  }
@@ -36952,7 +36978,7 @@ var init_package = __esm({
36952
36978
  "package.json"() {
36953
36979
  package_default = {
36954
36980
  name: "claude-use",
36955
- version: "11.60.2",
36981
+ version: "11.61.0",
36956
36982
  description: "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
36957
36983
  license: "MIT",
36958
36984
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -116251,7 +116277,7 @@ function registerProfileCommand(program2, deps) {
116251
116277
  withExamples(
116252
116278
  profile.command("set <name>").description(
116253
116279
  "Update a configuration profile's categories, entries, extends list, description or launch settings. With no options on a terminal, walks through its categories."
116254
- ).option("--category <category=bool>", "Share (true) or hide (false) a whole category (repeatable).", collectBoolPair).option("--entry <path=bool>", "Share (true) or hide (false) one <category>/<path> entry (repeatable).", collectBoolPair).option("--extends <profile>", "Replace the extends list with these profiles (repeatable, in order).", collectRepeated).option("--no-extends", "Clear the extends list.").option("--description <text>", "Replace the description.").option("--no-description", "Clear the description.").option("--launch-skip-permissions", "Launches under this profile skip permission prompts.").option("--no-launch-skip-permissions", "Launches under this profile keep permission prompts.").option("--launch-remote-control", "Launches under this profile enable Remote Control.").option("--no-launch-remote-control", "Launches under this profile leave Remote Control off.").option("--launch-headroom", "Launches under this profile route through headroom.").option("--no-launch-headroom", "Launches under this profile route direct.").option("--launch-track-usage", "Launches under this profile route through the front door so their requests and quota are recorded, with no headroom needed.").option("--no-launch-track-usage", "Launches under this profile are not recorded unless a provider or headroom routes them anyway.").option("--launch-provider <provider>", "Launches under this profile route through this provider.").option("--no-launch-provider", "Clear this profile's provider selection.").option("--launch-claude-version <version>", "Launches under this profile run exactly this installed Claude Code version (such as 2.1.220); a version that is not installed fails the launch.").option("--no-launch-claude-version", "Clear this profile's Claude Code version pin.").option("--launch-delay <duration>", "Launches under this profile wait this long (such as 5s) after the launcher's own output before starting claude, so the output can be read.", parseDurationLiteral).option("--no-launch-delay", "Clear this profile's launch delay.").option("--launch-pause", "Launches under this profile wait for a key press after the launcher's own output before starting claude.").option("--no-launch-pause", "Launches under this profile start claude without waiting for a key press.").option("--launch-explain", "Launches under this profile that pick from a pool list every member the pick ranked and why, in the launcher's report.").option("--no-launch-explain", "Launches under this profile name only the member a pool pick chose.").option("--launch-proxy-bypass <host>", "Launches under this profile keep this host off the front door by adding it to NO_PROXY, so its traffic goes direct (repeatable; replaces the profile's list).", collectRepeated).option("--no-launch-proxy-bypass", "Clear this profile's list of hosts kept off the front door.").option("--statusline <setting=value>", "Set one statusline setting for launches under this profile (repeatable; `agent-shim statusline keys` lists them; a list is comma-separated, and an empty value is the empty list).", collectRepeated).option("--unset-statusline <setting>", "Remove one statusline setting from this profile so the shallower layers or the default apply again (repeatable).", collectRepeated).option("--json", "Print the result as JSON.").action(async (name, allOptions) => {
116280
+ ).option("--category <category=bool>", "Share (true) or hide (false) a whole category (repeatable).", collectBoolPair).option("--entry <path=bool>", "Share (true) or hide (false) one <category>/<path> entry (repeatable).", collectBoolPair).option("--extends <profile>", "Replace the extends list with these profiles (repeatable, in order).", collectRepeated).option("--no-extends", "Clear the extends list.").option("--description <text>", "Replace the description.").option("--no-description", "Clear the description.").option("--launch-skip-permissions", "Launches under this profile skip permission prompts.").option("--no-launch-skip-permissions", "Launches under this profile keep permission prompts.").option("--launch-remote-control", "Launches under this profile enable Remote Control.").option("--no-launch-remote-control", "Launches under this profile leave Remote Control off.").option("--launch-headroom", "Launches under this profile route through headroom.").option("--no-launch-headroom", "Launches under this profile route direct.").option("--launch-track-usage", "Launches under this profile route through the front door so their requests and quota are recorded, with no headroom needed.").option("--no-launch-track-usage", "Launches under this profile are not recorded unless a provider or headroom routes them anyway.").option("--launch-provider <provider>", "Launches under this profile route through this provider.").option("--no-launch-provider", "Clear this profile's provider selection.").option("--launch-claude-version <version>", "Launches under this profile run exactly this installed Claude Code version (such as 2.1.220); a version that is not installed fails the launch.").option("--no-launch-claude-version", "Clear this profile's Claude Code version pin.").option("--launch-delay <duration>", "Launches under this profile wait this long (such as 5s) after the launcher's own output before starting claude, so the output can be read.", parseDurationLiteral).option("--no-launch-delay", "Clear this profile's launch delay.").option("--launch-pause", "Launches under this profile wait for a key press after the launcher's own output before starting claude.").option("--no-launch-pause", "Launches under this profile start claude without waiting for a key press.").option("--launch-explain", "Launches under this profile that pick from a pool list every member the pick ranked and why, in the launcher's report.").option("--no-launch-explain", "Launches under this profile name only the member a pool pick chose.").option("--launch-proxy-bypass <host>", "Launches under this profile keep this host off the front door by adding it to NO_PROXY, so its traffic goes direct (repeatable; replaces the profile's list).", collectRepeated).option("--no-launch-proxy-bypass", "Clear this profile's list of hosts kept off the front door.").option("--statusline <setting=value>", "Set one statusline setting for launches under this profile (repeatable; `agent-shim statusline keys` lists them; a list is comma-separated, a list of groups such as layout.merge is groups separated by semicolons with commas inside each group, and an empty value is the empty list).", collectRepeated).option("--unset-statusline <setting>", "Remove one statusline setting from this profile so the shallower layers or the default apply again (repeatable).", collectRepeated).option("--json", "Print the result as JSON.").action(async (name, allOptions) => {
116255
116281
  const { json: json2, ...options } = allOptions;
116256
116282
  requireProfileExists(paths, name);
116257
116283
  if (Object.entries(allOptions).every(([key, value2]) => key === "json" || value2 === void 0)) {
@@ -138264,106 +138290,6 @@ function realStatuslinePorts(env, stateDirectory) {
138264
138290
  // src/statusline/render.ts
138265
138291
  init_statusline();
138266
138292
 
138267
- // src/statusline/ansi.ts
138268
- init_string_width();
138269
- init_graphemes();
138270
- var ESCAPE_CHAR_CODE = 27;
138271
- var ESCAPE = String.fromCharCode(ESCAPE_CHAR_CODE);
138272
- var BELL = "\\u0007";
138273
- var ESCAPE_SEQUENCE_PATTERN = [`${ESCAPE}\\[[0-?]*[ -/]*[@-~]`, `${ESCAPE}\\][^${BELL}${ESCAPE}]*(?:${BELL}|${ESCAPE}\\\\)?`, `${ESCAPE}[@-Z\\\\-_]`].join("|");
138274
- var SPLIT_ON_ESCAPES = new RegExp(`(${ESCAPE_SEQUENCE_PATTERN})`);
138275
- function visibleLength(text5) {
138276
- return stringWidth(text5);
138277
- }
138278
- function truncateToColumns(text5, columns) {
138279
- let used = 0;
138280
- let kept = "";
138281
- text5.split(SPLIT_ON_ESCAPES).forEach((part, index) => {
138282
- if (index % 2 === 1) {
138283
- kept += part;
138284
- return;
138285
- }
138286
- for (const grapheme of graphemesOf(part)) {
138287
- used += visibleLength(grapheme);
138288
- if (used > columns) {
138289
- return;
138290
- }
138291
- kept += grapheme;
138292
- }
138293
- });
138294
- return kept;
138295
- }
138296
-
138297
- // src/statusline/barGrid.ts
138298
- var BRACKET_COLUMNS = 2;
138299
- function chunk(items, size) {
138300
- const chunks = [];
138301
- for (let start = 0; start < items.length; start += size) {
138302
- chunks.push(items.slice(start, start + size));
138303
- }
138304
- return chunks;
138305
- }
138306
- function lastColumnOf(index, rowLength, columns) {
138307
- return index === rowLength - 1 ? columns - 1 : index;
138308
- }
138309
- function measureGrid(rows, separatorLength, baseBarCells) {
138310
- const columns = Math.max(...rows.map((row) => row.length));
138311
- const prefixWidths = Array.from({ length: columns }, () => 0);
138312
- const suffixWidths = Array.from({ length: columns }, () => 0);
138313
- for (const row of rows) {
138314
- row.forEach((cell, index) => {
138315
- const last = lastColumnOf(index, row.length, columns);
138316
- prefixWidths[index] = Math.max(prefixWidths[index] ?? 0, visibleLength(cell.prefix));
138317
- suffixWidths[last] = Math.max(suffixWidths[last] ?? 0, visibleLength(cell.suffix));
138318
- });
138319
- }
138320
- const fixed = [...prefixWidths, ...suffixWidths].reduce((total, width) => total + width, 0) + columns * (baseBarCells + BRACKET_COLUMNS);
138321
- return { rows, columns, prefixWidths, suffixWidths, baseWidth: fixed + separatorLength * (columns - 1) };
138322
- }
138323
- function padTo(text5, width) {
138324
- return text5 + " ".repeat(Math.max(0, width - visibleLength(text5)));
138325
- }
138326
- function renderGrid(grid, maxWidth, separator, baseBarCells) {
138327
- const separatorLength = visibleLength(separator);
138328
- const spare = Math.max(0, maxWidth - grid.baseWidth);
138329
- const barCells = Array.from({ length: grid.columns }, (_, column) => baseBarCells + Math.floor(spare / grid.columns) + (column < spare % grid.columns ? 1 : 0));
138330
- const blockWidths = barCells.map((cells) => cells + BRACKET_COLUMNS);
138331
- return grid.rows.map(
138332
- (row) => row.map((cell, index) => {
138333
- const last = lastColumnOf(index, row.length, grid.columns);
138334
- let blockWidth = blockWidths[index] ?? 0;
138335
- for (let column = index + 1; column <= last; column += 1) {
138336
- blockWidth += separatorLength + (grid.suffixWidths[column - 1] ?? 0) + (grid.prefixWidths[column] ?? 0) + (blockWidths[column] ?? 0);
138337
- }
138338
- const prefix = padTo(cell.prefix, grid.prefixWidths[index] ?? 0);
138339
- const bar = cell.draw(blockWidth - BRACKET_COLUMNS);
138340
- const suffix = index === row.length - 1 ? cell.suffix : padTo(cell.suffix, grid.suffixWidths[last] ?? 0);
138341
- return `${prefix}${bar}${suffix}`;
138342
- }).join(separator)
138343
- );
138344
- }
138345
- function layoutBarGrid(groups, maxWidth, separator, baseBarCells) {
138346
- const nonEmpty = groups.filter((group) => group.length > 0);
138347
- if (nonEmpty.length === 0) {
138348
- return [];
138349
- }
138350
- const separatorLength = visibleLength(separator);
138351
- const widest = Math.max(...nonEmpty.map((group) => group.length));
138352
- const layoutFor = (perRow) => measureGrid(nonEmpty.flatMap((group) => chunk(group, perRow)), separatorLength, baseBarCells);
138353
- const candidates = [measureGrid([nonEmpty.flat()], separatorLength, baseBarCells)];
138354
- for (let perRow = widest; perRow > 1; perRow -= 1) {
138355
- candidates.push(layoutFor(perRow));
138356
- }
138357
- const fitting = candidates.find((grid) => grid.baseWidth <= maxWidth);
138358
- if (fitting !== void 0) {
138359
- return renderGrid(fitting, maxWidth, separator, baseBarCells);
138360
- }
138361
- if (nonEmpty.length > 1) {
138362
- return nonEmpty.flatMap((group) => layoutBarGrid([group], maxWidth, separator, baseBarCells));
138363
- }
138364
- return renderGrid(layoutFor(1), maxWidth, separator, baseBarCells);
138365
- }
138366
-
138367
138293
  // src/statusline/elements/header/account.ts
138368
138294
  init_graphemes();
138369
138295
  function tierSuffixOf(tier) {
@@ -138433,6 +138359,38 @@ function accountSegment({ summary, settings: settings2 }) {
138433
138359
 
138434
138360
  // src/statusline/sanitise.ts
138435
138361
  init_statusline();
138362
+
138363
+ // src/statusline/ansi.ts
138364
+ init_string_width();
138365
+ init_graphemes();
138366
+ var ESCAPE_CHAR_CODE = 27;
138367
+ var ESCAPE = String.fromCharCode(ESCAPE_CHAR_CODE);
138368
+ var BELL = "\\u0007";
138369
+ var ESCAPE_SEQUENCE_PATTERN = [`${ESCAPE}\\[[0-?]*[ -/]*[@-~]`, `${ESCAPE}\\][^${BELL}${ESCAPE}]*(?:${BELL}|${ESCAPE}\\\\)?`, `${ESCAPE}[@-Z\\\\-_]`].join("|");
138370
+ var SPLIT_ON_ESCAPES = new RegExp(`(${ESCAPE_SEQUENCE_PATTERN})`);
138371
+ function visibleLength(text5) {
138372
+ return stringWidth(text5);
138373
+ }
138374
+ function truncateToColumns(text5, columns) {
138375
+ let used = 0;
138376
+ let kept = "";
138377
+ text5.split(SPLIT_ON_ESCAPES).forEach((part, index) => {
138378
+ if (index % 2 === 1) {
138379
+ kept += part;
138380
+ return;
138381
+ }
138382
+ for (const grapheme of graphemesOf(part)) {
138383
+ used += visibleLength(grapheme);
138384
+ if (used > columns) {
138385
+ return;
138386
+ }
138387
+ kept += grapheme;
138388
+ }
138389
+ });
138390
+ return kept;
138391
+ }
138392
+
138393
+ // src/statusline/sanitise.ts
138436
138394
  init_blank();
138437
138395
  init_graphemes();
138438
138396
  var ESCAPE_SEQUENCES = new RegExp(ESCAPE_SEQUENCE_PATTERN, "g");
@@ -138693,7 +138651,6 @@ var SECONDS_PER_DAY = 86400;
138693
138651
  var THOUSAND = 1e3;
138694
138652
  var MILLION = 1e6;
138695
138653
  var TEN = 10;
138696
- var TWENTY = 20;
138697
138654
  var FULL_PERCENT3 = 100;
138698
138655
  var ONE_PERCENT = 1;
138699
138656
  function withColor(color, text5) {
@@ -140096,6 +140053,176 @@ function readTranscriptIdentity(ports, transcriptPath) {
140096
140053
  return floor === 0 ? identityOfLine(decoder.decode(joined2(carry))) ?? NOT_FOUND : NOT_FOUND;
140097
140054
  }
140098
140055
 
140056
+ // src/statusline/barGrid.ts
140057
+ var BRACKET_COLUMNS = 2;
140058
+ function chunk(items, size) {
140059
+ const chunks = [];
140060
+ for (let start = 0; start < items.length; start += size) {
140061
+ chunks.push(items.slice(start, start + size));
140062
+ }
140063
+ return chunks;
140064
+ }
140065
+ function lastColumnOf(index, rowLength, columns) {
140066
+ return index === rowLength - 1 ? columns - 1 : index;
140067
+ }
140068
+ function measureGrid(rows, separatorLength, baseBarCells) {
140069
+ const columns = Math.max(...rows.map((row) => row.length));
140070
+ const prefixWidths = Array.from({ length: columns }, () => 0);
140071
+ const suffixWidths = Array.from({ length: columns }, () => 0);
140072
+ for (const row of rows) {
140073
+ row.forEach((cell, index) => {
140074
+ const last = lastColumnOf(index, row.length, columns);
140075
+ prefixWidths[index] = Math.max(prefixWidths[index] ?? 0, visibleLength(cell.prefix));
140076
+ suffixWidths[last] = Math.max(suffixWidths[last] ?? 0, visibleLength(cell.suffix));
140077
+ });
140078
+ }
140079
+ const fixed = [...prefixWidths, ...suffixWidths].reduce((total, width) => total + width, 0) + columns * (baseBarCells + BRACKET_COLUMNS);
140080
+ return { rows, columns, prefixWidths, suffixWidths, baseWidth: fixed + separatorLength * (columns - 1) };
140081
+ }
140082
+ function padTo(text5, width) {
140083
+ return text5 + " ".repeat(Math.max(0, width - visibleLength(text5)));
140084
+ }
140085
+ function renderGrid(grid, maxWidth, separator, baseBarCells) {
140086
+ const separatorLength = visibleLength(separator);
140087
+ const spare = Math.max(0, maxWidth - grid.baseWidth);
140088
+ const barCells = Array.from({ length: grid.columns }, (_, column) => baseBarCells + Math.floor(spare / grid.columns) + (column < spare % grid.columns ? 1 : 0));
140089
+ const blockWidths = barCells.map((cells) => cells + BRACKET_COLUMNS);
140090
+ return grid.rows.map(
140091
+ (row) => row.map((cell, index) => {
140092
+ const last = lastColumnOf(index, row.length, grid.columns);
140093
+ let blockWidth = blockWidths[index] ?? 0;
140094
+ for (let column = index + 1; column <= last; column += 1) {
140095
+ blockWidth += separatorLength + (grid.suffixWidths[column - 1] ?? 0) + (grid.prefixWidths[column] ?? 0) + (blockWidths[column] ?? 0);
140096
+ }
140097
+ const prefix = padTo(cell.prefix, grid.prefixWidths[index] ?? 0);
140098
+ const bar = cell.draw(blockWidth - BRACKET_COLUMNS);
140099
+ const suffix = index === row.length - 1 ? cell.suffix : padTo(cell.suffix, grid.suffixWidths[last] ?? 0);
140100
+ return `${prefix}${bar}${suffix}`;
140101
+ }).join(separator)
140102
+ );
140103
+ }
140104
+ function drawInline(group, separator, baseBarCells) {
140105
+ return group.map((cell) => `${cell.prefix}${cell.draw(baseBarCells)}${cell.suffix}`).join(separator);
140106
+ }
140107
+ function layoutBarGrid(units, maxWidth, separator, baseBarCells) {
140108
+ const nonEmpty = units.map((unit) => unit.filter((group) => group.length > 0)).filter((unit) => unit.length > 0);
140109
+ if (nonEmpty.length === 0) {
140110
+ return [];
140111
+ }
140112
+ const separatorLength = visibleLength(separator);
140113
+ const groups = nonEmpty.flat();
140114
+ const candidates = [measureGrid(nonEmpty.map((unit) => unit.flat()), separatorLength, baseBarCells)];
140115
+ const chunked = (perRow) => measureGrid(groups.flatMap((group) => chunk(group, perRow)), separatorLength, baseBarCells);
140116
+ const fewest = nonEmpty.length > 1 ? 1 : 2;
140117
+ for (let perRow = Math.max(...groups.map((group) => group.length)); perRow >= fewest; perRow -= 1) {
140118
+ candidates.push(chunked(perRow));
140119
+ }
140120
+ const fitting = candidates.find((grid) => grid.baseWidth <= maxWidth);
140121
+ if (fitting !== void 0) {
140122
+ return renderGrid(fitting, maxWidth, separator, baseBarCells);
140123
+ }
140124
+ if (groups.length > 1) {
140125
+ return groups.flatMap((group) => layoutBarGrid([[group]], maxWidth, separator, baseBarCells));
140126
+ }
140127
+ return renderGrid(chunked(1), maxWidth, separator, baseBarCells);
140128
+ }
140129
+
140130
+ // src/statusline/rowsLayout.ts
140131
+ var BREAK = { kind: "lines", lines: [] };
140132
+ function isEmpty(output2) {
140133
+ return output2.kind === "bars" ? output2.cells.length === 0 : output2.lines.length === 0;
140134
+ }
140135
+ function oneLineOf(output2, barCells) {
140136
+ if (output2.kind === "bars") {
140137
+ return drawInline(output2.cells, SEPARATOR2, barCells);
140138
+ }
140139
+ return output2.lines.length === 1 ? output2.lines[0] : void 0;
140140
+ }
140141
+ function alignRight(lines, rightAlign, width) {
140142
+ const split = lines.findIndex(({ name }) => rightAlign.has(name));
140143
+ if (split < 1) {
140144
+ return void 0;
140145
+ }
140146
+ const left = lines.slice(0, split).map(({ text: text5 }) => text5).join(SEPARATOR2);
140147
+ const right = lines.slice(split).map(({ text: text5 }) => text5).join(SEPARATOR2);
140148
+ return `${left}${" ".repeat(width - visibleLength(left) - visibleLength(right))}${right}`;
140149
+ }
140150
+ function itemOf(row) {
140151
+ return row.output.kind === "bars" ? { kind: "bars", unit: [row.output.cells] } : { kind: "lines", lines: row.output.lines };
140152
+ }
140153
+ function mergedItems(rows, settings2, width) {
140154
+ const rightAlign = new Set(settings2.rightAlign);
140155
+ const aligned = rows.some((row, index) => index > 0 && rightAlign.has(row.name));
140156
+ if (!aligned && rows.every((row) => row.output.kind === "bars")) {
140157
+ return [{ kind: "bars", unit: rows.flatMap((row) => row.output.kind === "bars" ? [row.output.cells] : []) }];
140158
+ }
140159
+ const lines = rows.map((row) => ({ name: row.name, text: oneLineOf(row.output, settings2.barCells) }));
140160
+ const texts = lines.flatMap(({ name, text: text5 }) => text5 === void 0 ? [] : [{ name, text: text5 }]);
140161
+ if (texts.length === rows.length) {
140162
+ const joined3 = texts.map(({ text: text5 }) => text5).join(SEPARATOR2);
140163
+ if (visibleLength(joined3) <= width) {
140164
+ return [{ kind: "lines", lines: [alignRight(texts, rightAlign, width) ?? joined3] }];
140165
+ }
140166
+ }
140167
+ return rows.map(itemOf);
140168
+ }
140169
+ var BARS_RUN = -1;
140170
+ function runsOf(rows, settings2) {
140171
+ const groupOf2 = new Map(settings2.merge.flatMap((group, index) => group.map((name) => [name, index])));
140172
+ const keyOf3 = (row) => groupOf2.get(row.name) ?? (settings2.mergeBars && row.output.kind === "bars" ? BARS_RUN : void 0);
140173
+ const runs = [];
140174
+ const add = (key, row) => {
140175
+ const last = runs.at(-1);
140176
+ if (last !== void 0 && key !== void 0 && last.key === key) {
140177
+ last.rows.push(row);
140178
+ } else {
140179
+ runs.push({ key, rows: [row] });
140180
+ }
140181
+ };
140182
+ for (const row of rows) {
140183
+ add(keyOf3(row), row);
140184
+ }
140185
+ const silent = (run) => run.rows.every((row) => isEmpty(row.output) && row.output.kind === "bars");
140186
+ const kept = [];
140187
+ for (const run of runs.filter((candidate) => !silent(candidate))) {
140188
+ const last = kept.at(-1);
140189
+ if (last !== void 0 && run.key === BARS_RUN && last.key === BARS_RUN) {
140190
+ last.rows.push(...run.rows);
140191
+ } else {
140192
+ kept.push(run);
140193
+ }
140194
+ }
140195
+ return kept.map((run) => run.rows);
140196
+ }
140197
+ function layoutRows(rows, settings2, width) {
140198
+ const items = runsOf(rows, settings2).flatMap((run) => {
140199
+ const present = run.filter((row) => !isEmpty(row.output));
140200
+ if (present.length === 0) {
140201
+ return [BREAK];
140202
+ }
140203
+ return present.length > 1 ? mergedItems(present, settings2, width) : present.map(itemOf);
140204
+ });
140205
+ const lines = [];
140206
+ let units = [];
140207
+ const flushBars = () => {
140208
+ lines.push(...layoutBarGrid(units, width, SEPARATOR2, settings2.barCells));
140209
+ units = [];
140210
+ };
140211
+ for (const item of items) {
140212
+ if (item.kind === "bars") {
140213
+ units.push(item.unit);
140214
+ } else {
140215
+ flushBars();
140216
+ lines.push(...item.lines);
140217
+ }
140218
+ }
140219
+ flushBars();
140220
+ return lines;
140221
+ }
140222
+ function compactUnits(rows, barCells) {
140223
+ return rows.flatMap(({ output: output2 }) => output2.kind === "bars" ? output2.cells.map((cell) => drawInline([cell], SEPARATOR2, barCells)) : [...output2.lines]);
140224
+ }
140225
+
140099
140226
  // src/statusline/transcript/reader.ts
140100
140227
  var import_node_crypto27 = require("node:crypto");
140101
140228
  init_zod();
@@ -140375,6 +140502,9 @@ function foldNewer(state, bytes, offset) {
140375
140502
 
140376
140503
  // src/statusline/render.ts
140377
140504
  var RESERVED_COLUMNS = 4;
140505
+ function lineWidthOf(terminalColumns) {
140506
+ return Math.max(MIN_STATUSLINE_COLUMNS, terminalColumns - RESERVED_COLUMNS);
140507
+ }
140378
140508
  function once2(read) {
140379
140509
  let kept;
140380
140510
  return () => {
@@ -140382,9 +140512,14 @@ function once2(read) {
140382
140512
  return kept.value;
140383
140513
  };
140384
140514
  }
140515
+ var RULE_CHARACTER = "\u2500";
140516
+ function ruleLine(headerLines, width, palette) {
140517
+ const widest = Math.max(MIN_STATUSLINE_COLUMNS, ...headerLines.map(visibleLength));
140518
+ return palette.paint("label", RULE_CHARACTER.repeat(Math.min(width, widest)));
140519
+ }
140385
140520
  function renderStatuslineBlock(input2) {
140386
140521
  const { terminalWidth, settings: settings2 = STATUSLINE_DEFAULTS, elements = ELEMENTS } = input2;
140387
- const width = Math.max(TWENTY, terminalWidth - RESERVED_COLUMNS);
140522
+ const width = input2.lineWidth ?? lineWidthOf(terminalWidth);
140388
140523
  const context = {
140389
140524
  summary: input2.summary,
140390
140525
  view: input2.view,
@@ -140406,26 +140541,14 @@ function renderStatuslineBlock(input2) {
140406
140541
  return text5 === void 0 ? [] : { name, text: context.palette.paint(HEADER_ROLES[name], text5) };
140407
140542
  });
140408
140543
  const header = headerUnits(parts, settings2.git.branchOverflow);
140409
- const lines = [];
140410
- if (header.length > 0) {
140411
- lines.push(layoutHeader(header, width, SEPARATOR2));
140412
- }
140413
- let barGroups = [];
140414
- const flushBars = () => {
140415
- lines.push(...layoutBarGrid(barGroups, width, SEPARATOR2, settings2.bar.width));
140416
- barGroups = [];
140417
- };
140418
- for (const name of settings2.layout.rows) {
140419
- const output2 = elements.rows[name](context);
140420
- if (output2.kind === "bars") {
140421
- barGroups.push(output2.cells);
140422
- } else {
140423
- flushBars();
140424
- lines.push(...output2.lines);
140425
- }
140544
+ const drawn = settings2.layout.rows.map((name) => ({ name, output: elements.rows[name](context) }));
140545
+ if (settings2.layout.mode === "compact") {
140546
+ return packLines([...header, ...compactUnits(drawn, settings2.bar.width)], width, SEPARATOR2).map((line) => line.join(SEPARATOR2)).join("\n");
140426
140547
  }
140427
- flushBars();
140428
- return lines.join("\n");
140548
+ const headerLines = header.length > 0 ? layoutHeader(header, width, SEPARATOR2).split("\n") : [];
140549
+ const rowLines = layoutRows(drawn, { mergeBars: settings2.layout.mergeBars, merge: settings2.layout.merge, rightAlign: settings2.layout.rightAlign, barCells: settings2.bar.width }, width);
140550
+ const rule = settings2.layout.rule === "belowHeader" && headerLines.length > 0 && rowLines.length > 0 ? [ruleLine(headerLines, width, context.palette)] : [];
140551
+ return [...headerLines, ...rule, ...rowLines].join("\n");
140429
140552
  }
140430
140553
 
140431
140554
  // src/statusline/commands.ts
@@ -140433,13 +140556,22 @@ init_keys();
140433
140556
  init_settings2();
140434
140557
 
140435
140558
  // src/statusline/width.ts
140559
+ init_statusline();
140436
140560
  var DEFAULT_COLUMNS = 80;
140437
- function terminalWidthOf(payload, env) {
140561
+ function widthsOf(payload, env, layout) {
140562
+ const configured = { terminal: layout.maxWidth, line: layout.maxWidth };
140563
+ if (layout.forceMaxWidth) {
140564
+ return configured;
140565
+ }
140566
+ const reported = (terminal) => ({ terminal, line: lineWidthOf(terminal) });
140438
140567
  if (typeof payload === "object" && payload !== null && "terminal_width" in payload && typeof payload.terminal_width === "number" && Number.isFinite(payload.terminal_width) && payload.terminal_width > 0) {
140439
- return Math.floor(payload.terminal_width);
140568
+ return reported(Math.floor(payload.terminal_width));
140440
140569
  }
140441
140570
  const columns = Number.parseInt(env.COLUMNS ?? "", 10);
140442
- return Number.isFinite(columns) && columns > 0 ? columns : DEFAULT_COLUMNS;
140571
+ if (Number.isFinite(columns) && columns > 0) {
140572
+ return reported(columns);
140573
+ }
140574
+ return layout.maxWidth >= MIN_STATUSLINE_COLUMNS ? configured : reported(DEFAULT_COLUMNS);
140443
140575
  }
140444
140576
 
140445
140577
  // src/statusline/commands.ts
@@ -140548,7 +140680,8 @@ function registerStatuslineCommand(program2, deps) {
140548
140680
  }
140549
140681
  }
140550
140682
  const view = { ...viewOf(clientPayload), ...stackedUnderDefault ? { modelDisplayName: void 0 } : {} };
140551
- console.log(renderStatuslineBlock({ summary, view, payload: clientPayload, terminalWidth: terminalWidthOf(payload, process.env), nowMs, settings: settings2, ports: realStatuslinePorts(process.env, deps.paths.statuslineDir) }));
140683
+ const widths = widthsOf(payload, process.env, settings2.layout);
140684
+ console.log(renderStatuslineBlock({ summary, view, payload: clientPayload, terminalWidth: widths.terminal, lineWidth: widths.line, nowMs, settings: settings2, ports: realStatuslinePorts(process.env, deps.paths.statuslineDir) }));
140552
140685
  }),
140553
140686
  ["$ agent-shim statusline # a client's statusline setting names this; it renders or delegates"]
140554
140687
  );
package/dist/index.cjs CHANGED
@@ -289,9 +289,17 @@ function resolveStatusline(settings) {
289
289
  if (forContext.warnAt >= forContext.criticalAt) {
290
290
  throw new RangeError(`statusline context.warnAt (${String(forContext.warnAt)}) must be below context.criticalAt (${String(forContext.criticalAt)}), each following bands while it is unset`);
291
291
  }
292
+ const merged = new Set(resolved.layout.merge.flat());
293
+ const unmerged = resolved.layout.rightAlign.filter((row) => !merged.has(row));
294
+ if (unmerged.length > 0) {
295
+ throw new RangeError(`statusline layout.rightAlign names ${unmerged.join(", ")}, which no group in layout.merge contains`);
296
+ }
297
+ if (resolved.layout.forceMaxWidth && resolved.layout.maxWidth === 0) {
298
+ throw new RangeError("statusline layout.forceMaxWidth needs layout.maxWidth to be set");
299
+ }
292
300
  return resolved;
293
301
  }
294
- var import_string_width2, import_zod2, STATUSLINE_HEADER_SEGMENTS, STATUSLINE_ROWS, STATUSLINE_SESSION_TIME_PARTS, PROMPT_CACHE_TTL_SECONDS, STATUSLINE_COLOUR_NAMES, DEFAULT_HEADER_SEGMENTS, DEFAULT_ROWS, STATUSLINE_PATH_LEVELS, STATUSLINE_USAGE_VALUES, STATUSLINE_TIME_FORMATS, STATUSLINE_HOUR_CYCLES, MAX_PERCENT, MAX_STATUSLINE_PALETTE_INDEX, MAX_STATUSLINE_LABEL_COLUMNS, MAX_BAR_CELLS, MAX_ACCOUNT_USER_CHARS, MAX_DISPLAY_TEXT_CHARS, LABEL_RE2, OPTIONAL_LABEL_RE, colourValue, barCharacter, STATUSLINE_CONTEXT_VALUES, STATUSLINE_CONTEXT_ESTIMATES, AUTOCOMPACT_RESERVE_FRACTION, AUTOCOMPACT_RESERVE_RAMP_START, AUTOCOMPACT_RESERVE_RAMP_END, labelText, overrideText, wholePercent, thresholdPercent, SHAPES, UNSET_NOTE, StatuslineSchema, ResolvedStatuslineSchema, STATUSLINE_DEFAULTS, STATUSLINE_ENV;
302
+ var import_string_width2, import_zod2, STATUSLINE_HEADER_SEGMENTS, STATUSLINE_ROWS, STATUSLINE_SESSION_TIME_PARTS, PROMPT_CACHE_TTL_SECONDS, STATUSLINE_COLOUR_NAMES, DEFAULT_HEADER_SEGMENTS, DEFAULT_ROWS, STATUSLINE_PATH_LEVELS, STATUSLINE_USAGE_VALUES, STATUSLINE_TIME_FORMATS, STATUSLINE_HOUR_CYCLES, MAX_PERCENT, MAX_STATUSLINE_PALETTE_INDEX, MAX_STATUSLINE_LABEL_COLUMNS, MIN_STATUSLINE_COLUMNS, MAX_STATUSLINE_COLUMNS, MAX_BAR_CELLS, MAX_ACCOUNT_USER_CHARS, MAX_DISPLAY_TEXT_CHARS, LABEL_RE2, OPTIONAL_LABEL_RE, mergeGroups, colourValue, barCharacter, STATUSLINE_CONTEXT_VALUES, STATUSLINE_CONTEXT_ESTIMATES, AUTOCOMPACT_RESERVE_FRACTION, AUTOCOMPACT_RESERVE_RAMP_START, AUTOCOMPACT_RESERVE_RAMP_END, labelText, overrideText, wholePercent, thresholdPercent, SHAPES, UNSET_NOTE, StatuslineSchema, ResolvedStatuslineSchema, STATUSLINE_DEFAULTS, STATUSLINE_ENV;
295
303
  var init_statusline = __esm({
296
304
  "src/config/statusline.ts"() {
297
305
  "use strict";
@@ -332,11 +340,17 @@ var init_statusline = __esm({
332
340
  MAX_PERCENT = 100;
333
341
  MAX_STATUSLINE_PALETTE_INDEX = 255;
334
342
  MAX_STATUSLINE_LABEL_COLUMNS = 12;
343
+ MIN_STATUSLINE_COLUMNS = 20;
344
+ MAX_STATUSLINE_COLUMNS = 1e3;
335
345
  MAX_BAR_CELLS = 40;
336
346
  MAX_ACCOUNT_USER_CHARS = 254;
337
347
  MAX_DISPLAY_TEXT_CHARS = 80;
338
348
  LABEL_RE2 = /^[^\p{C}\p{Z}]+(?: [^\p{C}\p{Z}]+)*$/u;
339
349
  OPTIONAL_LABEL_RE = /^(?:[^\p{C}\p{Z}]+(?: [^\p{C}\p{Z}]+)*)?$/u;
350
+ mergeGroups = import_zod2.z.array(distinctList(import_zod2.z.enum(STATUSLINE_ROWS)).min(2)).refine((groups) => {
351
+ const rows = groups.flat();
352
+ return new Set(rows).size === rows.length;
353
+ }, "must not name a row in more than one group");
340
354
  colourValue = import_zod2.z.union([import_zod2.z.enum(STATUSLINE_COLOUR_NAMES), import_zod2.z.number().int().min(0).max(MAX_STATUSLINE_PALETTE_INDEX), import_zod2.z.string().regex(/^#[0-9a-fA-F]{6}$/, "must be a colour name, a palette index from 0 to 255 or #rrggbb")]);
341
355
  barCharacter = import_zod2.z.string().refine(isSingleVisibleGrapheme, "must be a single visible character that takes one terminal column");
342
356
  STATUSLINE_CONTEXT_VALUES = ["both", "percent", "tokens", "remaining"];
@@ -351,7 +365,14 @@ var init_statusline = __esm({
351
365
  SHAPES = {
352
366
  layout: {
353
367
  header: distinctList(import_zod2.z.enum(STATUSLINE_HEADER_SEGMENTS)).describe("The header segments to draw, in drawing order; a segment left out is hidden, and an empty list hides the header."),
354
- rows: distinctList(import_zod2.z.enum(STATUSLINE_ROWS)).describe("The rows to draw below the header, in drawing order; a row left out is hidden, and an empty list hides every row.")
368
+ rows: distinctList(import_zod2.z.enum(STATUSLINE_ROWS)).describe("The rows to draw below the header, in drawing order; a row left out is hidden, and an empty list hides every row."),
369
+ mode: import_zod2.z.enum(["expanded", "compact"]).describe("How the statusline is laid out: expanded draws the header and then the rows, each on lines of its own as layout.merge allows, and compact draws the header segments and every row's content on one line, starting a new line only where the line would be wider than the width. layout.mergeBars, layout.merge, layout.rightAlign and layout.rule have no effect in compact."),
370
+ rule: import_zod2.z.enum(["none", "belowHeader"]).describe(`Whether a rule is drawn across the line between the header and the rows beneath it, as wide as the header's widest line (at least ${String(MIN_STATUSLINE_COLUMNS)} columns, at most the width); it is drawn only when both the header and the rows have something to show, and not in the compact layout.`),
371
+ mergeBars: import_zod2.z.boolean().describe("Whether bar rows that sit next to each other in layout.rows share a line, and one grid, when they fit (the context and quota bars do by default); when false each bar row is on a line of its own, its bars still aligned to its neighbours'. A row of text is never joined by this setting, whichever form the quota row takes: layout.merge does that."),
372
+ merge: mergeGroups.describe("Groups of rows that share a line when they sit next to each other in layout.rows and fit on one line together, and are drawn on lines of their own when they do not; each group names at least two rows, and no row belongs to two groups. A group may mix bar rows and rows of text, which are joined with the separator when every member is a single line. Bar rows outside a group follow layout.mergeBars. A group in which no row has anything to show draws nothing: one with a row of text in it still ends a run of bars sharing a line, as an empty row of text does, and one of bar rows alone is left out, so the bars either side of it share as if it were not there."),
373
+ rightAlign: distinctList(import_zod2.z.enum(STATUSLINE_ROWS)).describe("Rows that start the right-aligned part of a merged line: the first of them to appear on a line, and the rows after it, are pushed to the right edge, with the gap padded by spaces. A row named here must belong to a group in layout.merge, and it has no effect when it is the first row of its line."),
374
+ maxWidth: import_zod2.z.number().int().min(0).max(MAX_STATUSLINE_COLUMNS).refine((columns) => columns === 0 || columns >= MIN_STATUSLINE_COLUMNS, `must be 0, or at least ${String(MIN_STATUSLINE_COLUMNS)} columns`).describe(`The exact width in columns lines are drawn to when the client reports none and the COLUMNS environment variable names none, in place of the default width it would assume for the terminal; unlike a terminal's width, it is not reduced by the columns Claude Code keeps back for itself. It is 0, to leave it unset, or from ${String(MIN_STATUSLINE_COLUMNS)} to ${String(MAX_STATUSLINE_COLUMNS)}; a value outside that is refused, not clamped.`),
375
+ forceMaxWidth: import_zod2.z.boolean().describe("Whether maxWidth is the exact width lines are drawn to for whatever the client reports, even when the terminal is narrower or wider; it needs maxWidth to be set.")
355
376
  },
356
377
  model: {
357
378
  format: import_zod2.z.enum(["full", "compact", "short"]).describe("How the model's name is written: full is the name as the client gives it, compact drops a context-window suffix such as (1M context), and short also drops a leading Claude."),
@@ -497,7 +518,7 @@ var init_statusline = __esm({
497
518
  jj: import_zod2.z.strictObject(SHAPES.jj)
498
519
  });
499
520
  STATUSLINE_DEFAULTS = {
500
- layout: { header: [...DEFAULT_HEADER_SEGMENTS], rows: [...DEFAULT_ROWS] },
521
+ layout: { header: [...DEFAULT_HEADER_SEGMENTS], rows: [...DEFAULT_ROWS], mode: "expanded", rule: "none", mergeBars: true, merge: [], rightAlign: [], maxWidth: 0, forceMaxWidth: false },
501
522
  model: { format: "full", override: "", source: "client" },
502
523
  provider: { name: "" },
503
524
  account: { parts: ["user", "organisation", "plan"], userLength: 0 },
@@ -4577,7 +4598,7 @@ var init_package = __esm({
4577
4598
  "package.json"() {
4578
4599
  package_default = {
4579
4600
  name: "claude-use",
4580
- version: "11.60.2",
4601
+ version: "11.61.0",
4581
4602
  description: "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
4582
4603
  license: "MIT",
4583
4604
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -7815,6 +7836,9 @@ function keyOf(path41, declared) {
7815
7836
  if (leaf instanceof import_zod16.z.ZodArray && leaf.element instanceof import_zod16.z.ZodEnum) {
7816
7837
  return { ...common, kind: "list", choices: Object.values(leaf.element.enum).map(String) };
7817
7838
  }
7839
+ if (leaf instanceof import_zod16.z.ZodArray && leaf.element instanceof import_zod16.z.ZodArray && leaf.element.element instanceof import_zod16.z.ZodEnum) {
7840
+ return { ...common, kind: "groups", choices: Object.values(leaf.element.element.enum).map(String) };
7841
+ }
7818
7842
  throw new TypeError(`statusline setting ${path41.join(".")} has a kind the key listing does not describe`);
7819
7843
  }
7820
7844
  function walk(shape, prefix) {