claude-use 11.60.2 → 11.62.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/index.cjs CHANGED
@@ -250,6 +250,15 @@ var init_graphemes = __esm({
250
250
  }
251
251
  });
252
252
 
253
+ // src/statusline/messages/languages.ts
254
+ var STATUSLINE_LANGUAGES;
255
+ var init_languages = __esm({
256
+ "src/statusline/messages/languages.ts"() {
257
+ "use strict";
258
+ STATUSLINE_LANGUAGES = ["en", "zh-Hans", "zh-Hant"];
259
+ }
260
+ });
261
+
253
262
  // src/config/statusline.ts
254
263
  function distinctList(member) {
255
264
  return import_zod2.z.array(member).refine((items) => new Set(items).size === items.length, "must not repeat an entry");
@@ -260,10 +269,13 @@ function isSingleVisibleGrapheme(text) {
260
269
  function unsettable(setting) {
261
270
  return setting.unwrap().nullable().optional().describe(`${setting.description ?? ""} ${UNSET_NOTE}`);
262
271
  }
272
+ function canonicalLanguage(tag) {
273
+ return tag === "zh" || tag === "zh-TW" ? LANGUAGE_ALIASES[tag] : tag;
274
+ }
263
275
  function isPlainObject(value) {
264
276
  return typeof value === "object" && value !== null && !Array.isArray(value);
265
277
  }
266
- function mergeStatuslineSettings(base, over) {
278
+ function mergeKeys(base, over) {
267
279
  if (!isPlainObject(over)) {
268
280
  return over === void 0 ? base : over;
269
281
  }
@@ -272,7 +284,7 @@ function mergeStatuslineSettings(base, over) {
272
284
  if (value === null) {
273
285
  Reflect.deleteProperty(merged, key);
274
286
  } else {
275
- merged[key] = mergeStatuslineSettings(merged[key], value);
287
+ merged[key] = mergeKeys(merged[key], value);
276
288
  }
277
289
  }
278
290
  return merged;
@@ -280,6 +292,13 @@ function mergeStatuslineSettings(base, over) {
280
292
  function contextBands({ bands, context }) {
281
293
  return { warnAt: context.warnAt ?? bands.warnAt, criticalAt: context.criticalAt ?? bands.criticalAt };
282
294
  }
295
+ function isPresetName(value) {
296
+ return STATUSLINE_PRESETS.some((name) => name === value);
297
+ }
298
+ function mergeStatuslineSettings(base, over) {
299
+ const preset = isPlainObject(over) ? over.preset : void 0;
300
+ return mergeKeys(isPresetName(preset) ? mergeKeys(base, PRESET_BUNDLES[preset]) : base, over);
301
+ }
283
302
  function resolveStatusline(settings) {
284
303
  const resolved = ResolvedStatuslineSchema.parse(mergeStatuslineSettings(STATUSLINE_DEFAULTS, settings));
285
304
  if (resolved.bands.warnAt >= resolved.bands.criticalAt) {
@@ -289,9 +308,17 @@ function resolveStatusline(settings) {
289
308
  if (forContext.warnAt >= forContext.criticalAt) {
290
309
  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
310
  }
311
+ const merged = new Set(resolved.layout.merge.flat());
312
+ const unmerged = resolved.layout.rightAlign.filter((row) => !merged.has(row));
313
+ if (unmerged.length > 0) {
314
+ throw new RangeError(`statusline layout.rightAlign names ${unmerged.join(", ")}, which no group in layout.merge contains`);
315
+ }
316
+ if (resolved.layout.forceMaxWidth && resolved.layout.maxWidth === 0) {
317
+ throw new RangeError("statusline layout.forceMaxWidth needs layout.maxWidth to be set");
318
+ }
292
319
  return resolved;
293
320
  }
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;
321
+ 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_PRESETS, GIT_WITH_DIRTY_MARKER, JJ_OFF, PRESET_BUNDLES, 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, labelWithinColumns, labelWithinColumnsMessage, labelShowsSomething, labelShowsSomethingMessage, labelIsPlainText, labelIsPlainTextMessage, windowLabel, overrideText, wholePercent, thresholdPercent, SHAPES, UNSET_NOTE, LANGUAGE_DESCRIPTION, LANGUAGE_TAGS, LANGUAGE_ALIASES, TOP_LEVEL, StatuslineSchema, ResolvedStatuslineSchema, STATUSLINE_DEFAULTS, STATUSLINE_ENV;
295
322
  var init_statusline = __esm({
296
323
  "src/config/statusline.ts"() {
297
324
  "use strict";
@@ -299,6 +326,7 @@ var init_statusline = __esm({
299
326
  import_zod2 = require("zod");
300
327
  init_blank();
301
328
  init_graphemes();
329
+ init_languages();
302
330
  STATUSLINE_HEADER_SEGMENTS = ["model", "provider", "route", "account", "identity", "headroom", "project", "vcs", "effort", "advisor", "version", "outputStyle", "cost", "dailyCost", "sessionName", "duration", "speed"];
303
331
  STATUSLINE_ROWS = ["context", "quota", "addedDirs", "gitFiles", "memory", "environment", "sessionTime", "promptCache", "compactions", "tokens"];
304
332
  STATUSLINE_SESSION_TIME_PARTS = ["started", "lastReply"];
@@ -325,6 +353,21 @@ var init_statusline = __esm({
325
353
  ];
326
354
  DEFAULT_HEADER_SEGMENTS = ["model", "provider", "route", "account", "identity", "headroom"];
327
355
  DEFAULT_ROWS = ["context", "quota"];
356
+ STATUSLINE_PRESETS = ["standard", "full", "essential", "minimal"];
357
+ GIT_WITH_DIRTY_MARKER = { enabled: true, showDirty: true, showAheadBehind: false, showLineDiff: false };
358
+ JJ_OFF = { enabled: false, showDirty: true, showConflicts: true };
359
+ PRESET_BUNDLES = {
360
+ standard: { layout: { header: DEFAULT_HEADER_SEGMENTS, rows: DEFAULT_ROWS }, git: GIT_WITH_DIRTY_MARKER, jj: JJ_OFF, usage: { resetLabel: false }, context: { breakdown: "never" } },
361
+ full: {
362
+ layout: { header: ["model", "project", "vcs", "effort", "advisor", "version", "outputStyle", "cost", "sessionName", "duration"], rows: ["context", "quota", "addedDirs", "memory", "environment", "promptCache", "compactions", "tokens"] },
363
+ git: GIT_WITH_DIRTY_MARKER,
364
+ jj: { enabled: true, showDirty: true, showConflicts: true },
365
+ usage: { resetLabel: true },
366
+ context: { breakdown: "critical" }
367
+ },
368
+ essential: { layout: { header: ["model", "project", "vcs", "duration"], rows: ["context", "addedDirs"] }, git: GIT_WITH_DIRTY_MARKER, jj: JJ_OFF, usage: { resetLabel: true }, context: { breakdown: "never" } },
369
+ minimal: { layout: { header: ["model", "project", "vcs"], rows: ["context", "addedDirs"] }, git: GIT_WITH_DIRTY_MARKER, jj: JJ_OFF, usage: { resetLabel: true }, context: { breakdown: "never" } }
370
+ };
328
371
  STATUSLINE_PATH_LEVELS = ["1", "2", "3", "full"];
329
372
  STATUSLINE_USAGE_VALUES = ["used", "remaining"];
330
373
  STATUSLINE_TIME_FORMATS = ["relative", "absolute", "both", "elapsed", "elapsedAndAbsolute"];
@@ -332,11 +375,17 @@ var init_statusline = __esm({
332
375
  MAX_PERCENT = 100;
333
376
  MAX_STATUSLINE_PALETTE_INDEX = 255;
334
377
  MAX_STATUSLINE_LABEL_COLUMNS = 12;
378
+ MIN_STATUSLINE_COLUMNS = 20;
379
+ MAX_STATUSLINE_COLUMNS = 1e3;
335
380
  MAX_BAR_CELLS = 40;
336
381
  MAX_ACCOUNT_USER_CHARS = 254;
337
382
  MAX_DISPLAY_TEXT_CHARS = 80;
338
383
  LABEL_RE2 = /^[^\p{C}\p{Z}]+(?: [^\p{C}\p{Z}]+)*$/u;
339
384
  OPTIONAL_LABEL_RE = /^(?:[^\p{C}\p{Z}]+(?: [^\p{C}\p{Z}]+)*)?$/u;
385
+ mergeGroups = import_zod2.z.array(distinctList(import_zod2.z.enum(STATUSLINE_ROWS)).min(2)).refine((groups) => {
386
+ const rows = groups.flat();
387
+ return new Set(rows).size === rows.length;
388
+ }, "must not name a row in more than one group");
340
389
  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
390
  barCharacter = import_zod2.z.string().refine(isSingleVisibleGrapheme, "must be a single visible character that takes one terminal column");
342
391
  STATUSLINE_CONTEXT_VALUES = ["both", "percent", "tokens", "remaining"];
@@ -344,14 +393,27 @@ var init_statusline = __esm({
344
393
  AUTOCOMPACT_RESERVE_FRACTION = 0.165;
345
394
  AUTOCOMPACT_RESERVE_RAMP_START = 0.05;
346
395
  AUTOCOMPACT_RESERVE_RAMP_END = 0.5;
347
- labelText = import_zod2.z.string().refine((text) => (0, import_string_width2.default)(text) <= MAX_STATUSLINE_LABEL_COLUMNS, `must take no more than ${String(MAX_STATUSLINE_LABEL_COLUMNS)} terminal columns`).refine((text) => !drawsNothing(text), "must show something: a label of only combining marks or blank characters draws nothing").regex(LABEL_RE2, "must be visible text without control characters");
396
+ labelWithinColumns = (text) => (0, import_string_width2.default)(text) <= MAX_STATUSLINE_LABEL_COLUMNS;
397
+ labelWithinColumnsMessage = `must take no more than ${String(MAX_STATUSLINE_LABEL_COLUMNS)} terminal columns`;
398
+ labelShowsSomething = (text) => !drawsNothing(text);
399
+ labelShowsSomethingMessage = "must show something: a label of only combining marks or blank characters draws nothing";
400
+ labelIsPlainText = (text) => LABEL_RE2.test(text);
401
+ labelIsPlainTextMessage = "must be visible text without control characters";
402
+ windowLabel = import_zod2.z.string().refine((text) => text === "" || labelWithinColumns(text), labelWithinColumnsMessage).refine((text) => text === "" || labelShowsSomething(text), labelShowsSomethingMessage).refine((text) => text === "" || labelIsPlainText(text), labelIsPlainTextMessage);
348
403
  overrideText = import_zod2.z.string().max(MAX_DISPLAY_TEXT_CHARS).regex(OPTIONAL_LABEL_RE, "must be visible text without control characters");
349
404
  wholePercent = import_zod2.z.number().int().min(1).max(MAX_PERCENT);
350
405
  thresholdPercent = import_zod2.z.number().int().min(0).max(MAX_PERCENT);
351
406
  SHAPES = {
352
407
  layout: {
353
408
  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.")
409
+ 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."),
410
+ 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."),
411
+ 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.`),
412
+ 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."),
413
+ 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."),
414
+ 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."),
415
+ 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.`),
416
+ 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
417
  },
356
418
  model: {
357
419
  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."),
@@ -385,8 +447,8 @@ var init_statusline = __esm({
385
447
  criticalAt: wholePercent.describe("The whole percentage of use at which a bar turns red.")
386
448
  },
387
449
  labels: {
388
- fiveHour: labelText.describe("The name of the five-hour subscription window."),
389
- sevenDay: labelText.describe("The name of the weekly subscription window.")
450
+ fiveHour: windowLabel.describe("The name of the five-hour subscription window; empty uses the language's own name for it."),
451
+ sevenDay: windowLabel.describe("The name of the weekly subscription window; empty uses the language's own name for it.")
390
452
  },
391
453
  bar: {
392
454
  width: import_zod2.z.number().int().min(1).max(MAX_BAR_CELLS).describe("A bar's width in cells before the line's spare width is shared out among the bars."),
@@ -452,7 +514,16 @@ var init_statusline = __esm({
452
514
  }
453
515
  };
454
516
  UNSET_NOTE = "A layer may write null to return it to unset when a shallower layer sets it.";
517
+ LANGUAGE_DESCRIPTION = "The language of the words the statusline writes (labels, units of waiting such as `ago`, the names of effort levels): en, zh-Hans for Chinese in simplified characters, and zh-Hant for traditional; zh and zh-TW are read as zh-Hans and zh-Hant, and any other tag is refused. Durations, token counts, the dollar sign and clock times are written the same in every language, and a clock time follows the system locale and the clock settings.";
518
+ LANGUAGE_TAGS = [...STATUSLINE_LANGUAGES, "zh", "zh-TW"];
519
+ LANGUAGE_ALIASES = { zh: "zh-Hans", "zh-TW": "zh-Hant" };
520
+ TOP_LEVEL = {
521
+ language: import_zod2.z.enum(STATUSLINE_LANGUAGES).describe(LANGUAGE_DESCRIPTION),
522
+ preset: import_zod2.z.enum(STATUSLINE_PRESETS).describe("A named bundle of settings (layout.header, layout.rows, git.enabled, git.showDirty, git.showAheadBehind, git.showLineDiff, jj.enabled, jj.showDirty, jj.showConflicts, usage.resetLabel and context.breakdown) that this layer sets beneath the keys it names itself and above everything the shallower layers set: standard is the statusline's own; full, essential and minimal are claude-hud's presets. Each draws the model, project and version control segments and the context row; full adds the effort, advisor, version, output style, cost, session name and duration segments and the quota, added directories, memory, environment, prompt cache, compactions and session tokens rows, with the token breakdown at high use, essential adds the duration and the added directories row, and minimal adds the added directories row alone.")
523
+ };
455
524
  StatuslineSchema = import_zod2.z.strictObject({
525
+ language: import_zod2.z.enum(LANGUAGE_TAGS).transform(canonicalLanguage).describe(LANGUAGE_DESCRIPTION).optional(),
526
+ preset: TOP_LEVEL.preset.optional(),
456
527
  layout: import_zod2.z.strictObject(SHAPES.layout).partial().optional(),
457
528
  model: import_zod2.z.strictObject(SHAPES.model).partial().optional(),
458
529
  provider: import_zod2.z.strictObject(SHAPES.provider).partial().optional(),
@@ -475,6 +546,8 @@ var init_statusline = __esm({
475
546
  jj: import_zod2.z.strictObject(SHAPES.jj).partial().optional()
476
547
  });
477
548
  ResolvedStatuslineSchema = import_zod2.z.strictObject({
549
+ language: TOP_LEVEL.language,
550
+ preset: TOP_LEVEL.preset,
478
551
  layout: import_zod2.z.strictObject(SHAPES.layout),
479
552
  model: import_zod2.z.strictObject(SHAPES.model),
480
553
  provider: import_zod2.z.strictObject(SHAPES.provider),
@@ -497,7 +570,9 @@ var init_statusline = __esm({
497
570
  jj: import_zod2.z.strictObject(SHAPES.jj)
498
571
  });
499
572
  STATUSLINE_DEFAULTS = {
500
- layout: { header: [...DEFAULT_HEADER_SEGMENTS], rows: [...DEFAULT_ROWS] },
573
+ language: "en",
574
+ preset: "standard",
575
+ layout: { header: [...DEFAULT_HEADER_SEGMENTS], rows: [...DEFAULT_ROWS], mode: "expanded", rule: "none", mergeBars: true, merge: [], rightAlign: [], maxWidth: 0, forceMaxWidth: false },
501
576
  model: { format: "full", override: "", source: "client" },
502
577
  provider: { name: "" },
503
578
  account: { parts: ["user", "organisation", "plan"], userLength: 0 },
@@ -505,7 +580,7 @@ var init_statusline = __esm({
505
580
  advisor: { override: "" },
506
581
  context: { bar: "shown", value: "both", estimate: "never", breakdown: "never" },
507
582
  bands: { warnAt: 70, criticalAt: 90 },
508
- labels: { fiveHour: "5h", sevenDay: "Wk" },
583
+ labels: { fiveHour: "", sevenDay: "" },
509
584
  bar: { width: 10, filled: "\u2588", empty: "\u2591" },
510
585
  usage: { value: "used", bar: true, compact: false, resetLabel: false, modelScoped: false, timeFormat: "relative", threshold: 0, sevenDayThreshold: 0 },
511
586
  clock: { hourCycle: "auto", seconds: false },
@@ -4577,7 +4652,7 @@ var init_package = __esm({
4577
4652
  "package.json"() {
4578
4653
  package_default = {
4579
4654
  name: "claude-use",
4580
- version: "11.60.2",
4655
+ version: "11.62.0",
4581
4656
  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
4657
  license: "MIT",
4583
4658
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -7815,6 +7890,9 @@ function keyOf(path41, declared) {
7815
7890
  if (leaf instanceof import_zod16.z.ZodArray && leaf.element instanceof import_zod16.z.ZodEnum) {
7816
7891
  return { ...common, kind: "list", choices: Object.values(leaf.element.enum).map(String) };
7817
7892
  }
7893
+ if (leaf instanceof import_zod16.z.ZodArray && leaf.element instanceof import_zod16.z.ZodArray && leaf.element.element instanceof import_zod16.z.ZodEnum) {
7894
+ return { ...common, kind: "groups", choices: Object.values(leaf.element.element.enum).map(String) };
7895
+ }
7818
7896
  throw new TypeError(`statusline setting ${path41.join(".")} has a kind the key listing does not describe`);
7819
7897
  }
7820
7898
  function walk(shape, prefix) {
package/dist/index.mjs CHANGED
@@ -228,6 +228,15 @@ var init_graphemes = __esm({
228
228
  }
229
229
  });
230
230
 
231
+ // src/statusline/messages/languages.ts
232
+ var STATUSLINE_LANGUAGES;
233
+ var init_languages = __esm({
234
+ "src/statusline/messages/languages.ts"() {
235
+ "use strict";
236
+ STATUSLINE_LANGUAGES = ["en", "zh-Hans", "zh-Hant"];
237
+ }
238
+ });
239
+
231
240
  // src/config/statusline.ts
232
241
  import stringWidth2 from "string-width";
233
242
  import { z as z2 } from "zod";
@@ -240,10 +249,13 @@ function isSingleVisibleGrapheme(text) {
240
249
  function unsettable(setting) {
241
250
  return setting.unwrap().nullable().optional().describe(`${setting.description ?? ""} ${UNSET_NOTE}`);
242
251
  }
252
+ function canonicalLanguage(tag) {
253
+ return tag === "zh" || tag === "zh-TW" ? LANGUAGE_ALIASES[tag] : tag;
254
+ }
243
255
  function isPlainObject(value) {
244
256
  return typeof value === "object" && value !== null && !Array.isArray(value);
245
257
  }
246
- function mergeStatuslineSettings(base, over) {
258
+ function mergeKeys(base, over) {
247
259
  if (!isPlainObject(over)) {
248
260
  return over === void 0 ? base : over;
249
261
  }
@@ -252,7 +264,7 @@ function mergeStatuslineSettings(base, over) {
252
264
  if (value === null) {
253
265
  Reflect.deleteProperty(merged, key);
254
266
  } else {
255
- merged[key] = mergeStatuslineSettings(merged[key], value);
267
+ merged[key] = mergeKeys(merged[key], value);
256
268
  }
257
269
  }
258
270
  return merged;
@@ -260,6 +272,13 @@ function mergeStatuslineSettings(base, over) {
260
272
  function contextBands({ bands, context }) {
261
273
  return { warnAt: context.warnAt ?? bands.warnAt, criticalAt: context.criticalAt ?? bands.criticalAt };
262
274
  }
275
+ function isPresetName(value) {
276
+ return STATUSLINE_PRESETS.some((name) => name === value);
277
+ }
278
+ function mergeStatuslineSettings(base, over) {
279
+ const preset = isPlainObject(over) ? over.preset : void 0;
280
+ return mergeKeys(isPresetName(preset) ? mergeKeys(base, PRESET_BUNDLES[preset]) : base, over);
281
+ }
263
282
  function resolveStatusline(settings) {
264
283
  const resolved = ResolvedStatuslineSchema.parse(mergeStatuslineSettings(STATUSLINE_DEFAULTS, settings));
265
284
  if (resolved.bands.warnAt >= resolved.bands.criticalAt) {
@@ -269,14 +288,23 @@ function resolveStatusline(settings) {
269
288
  if (forContext.warnAt >= forContext.criticalAt) {
270
289
  throw new RangeError(`statusline context.warnAt (${String(forContext.warnAt)}) must be below context.criticalAt (${String(forContext.criticalAt)}), each following bands while it is unset`);
271
290
  }
291
+ const merged = new Set(resolved.layout.merge.flat());
292
+ const unmerged = resolved.layout.rightAlign.filter((row) => !merged.has(row));
293
+ if (unmerged.length > 0) {
294
+ throw new RangeError(`statusline layout.rightAlign names ${unmerged.join(", ")}, which no group in layout.merge contains`);
295
+ }
296
+ if (resolved.layout.forceMaxWidth && resolved.layout.maxWidth === 0) {
297
+ throw new RangeError("statusline layout.forceMaxWidth needs layout.maxWidth to be set");
298
+ }
272
299
  return resolved;
273
300
  }
274
- 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;
301
+ var STATUSLINE_HEADER_SEGMENTS, STATUSLINE_ROWS, STATUSLINE_SESSION_TIME_PARTS, PROMPT_CACHE_TTL_SECONDS, STATUSLINE_COLOUR_NAMES, DEFAULT_HEADER_SEGMENTS, DEFAULT_ROWS, STATUSLINE_PRESETS, GIT_WITH_DIRTY_MARKER, JJ_OFF, PRESET_BUNDLES, 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, labelWithinColumns, labelWithinColumnsMessage, labelShowsSomething, labelShowsSomethingMessage, labelIsPlainText, labelIsPlainTextMessage, windowLabel, overrideText, wholePercent, thresholdPercent, SHAPES, UNSET_NOTE, LANGUAGE_DESCRIPTION, LANGUAGE_TAGS, LANGUAGE_ALIASES, TOP_LEVEL, StatuslineSchema, ResolvedStatuslineSchema, STATUSLINE_DEFAULTS, STATUSLINE_ENV;
275
302
  var init_statusline = __esm({
276
303
  "src/config/statusline.ts"() {
277
304
  "use strict";
278
305
  init_blank();
279
306
  init_graphemes();
307
+ init_languages();
280
308
  STATUSLINE_HEADER_SEGMENTS = ["model", "provider", "route", "account", "identity", "headroom", "project", "vcs", "effort", "advisor", "version", "outputStyle", "cost", "dailyCost", "sessionName", "duration", "speed"];
281
309
  STATUSLINE_ROWS = ["context", "quota", "addedDirs", "gitFiles", "memory", "environment", "sessionTime", "promptCache", "compactions", "tokens"];
282
310
  STATUSLINE_SESSION_TIME_PARTS = ["started", "lastReply"];
@@ -303,6 +331,21 @@ var init_statusline = __esm({
303
331
  ];
304
332
  DEFAULT_HEADER_SEGMENTS = ["model", "provider", "route", "account", "identity", "headroom"];
305
333
  DEFAULT_ROWS = ["context", "quota"];
334
+ STATUSLINE_PRESETS = ["standard", "full", "essential", "minimal"];
335
+ GIT_WITH_DIRTY_MARKER = { enabled: true, showDirty: true, showAheadBehind: false, showLineDiff: false };
336
+ JJ_OFF = { enabled: false, showDirty: true, showConflicts: true };
337
+ PRESET_BUNDLES = {
338
+ standard: { layout: { header: DEFAULT_HEADER_SEGMENTS, rows: DEFAULT_ROWS }, git: GIT_WITH_DIRTY_MARKER, jj: JJ_OFF, usage: { resetLabel: false }, context: { breakdown: "never" } },
339
+ full: {
340
+ layout: { header: ["model", "project", "vcs", "effort", "advisor", "version", "outputStyle", "cost", "sessionName", "duration"], rows: ["context", "quota", "addedDirs", "memory", "environment", "promptCache", "compactions", "tokens"] },
341
+ git: GIT_WITH_DIRTY_MARKER,
342
+ jj: { enabled: true, showDirty: true, showConflicts: true },
343
+ usage: { resetLabel: true },
344
+ context: { breakdown: "critical" }
345
+ },
346
+ essential: { layout: { header: ["model", "project", "vcs", "duration"], rows: ["context", "addedDirs"] }, git: GIT_WITH_DIRTY_MARKER, jj: JJ_OFF, usage: { resetLabel: true }, context: { breakdown: "never" } },
347
+ minimal: { layout: { header: ["model", "project", "vcs"], rows: ["context", "addedDirs"] }, git: GIT_WITH_DIRTY_MARKER, jj: JJ_OFF, usage: { resetLabel: true }, context: { breakdown: "never" } }
348
+ };
306
349
  STATUSLINE_PATH_LEVELS = ["1", "2", "3", "full"];
307
350
  STATUSLINE_USAGE_VALUES = ["used", "remaining"];
308
351
  STATUSLINE_TIME_FORMATS = ["relative", "absolute", "both", "elapsed", "elapsedAndAbsolute"];
@@ -310,11 +353,17 @@ var init_statusline = __esm({
310
353
  MAX_PERCENT = 100;
311
354
  MAX_STATUSLINE_PALETTE_INDEX = 255;
312
355
  MAX_STATUSLINE_LABEL_COLUMNS = 12;
356
+ MIN_STATUSLINE_COLUMNS = 20;
357
+ MAX_STATUSLINE_COLUMNS = 1e3;
313
358
  MAX_BAR_CELLS = 40;
314
359
  MAX_ACCOUNT_USER_CHARS = 254;
315
360
  MAX_DISPLAY_TEXT_CHARS = 80;
316
361
  LABEL_RE2 = /^[^\p{C}\p{Z}]+(?: [^\p{C}\p{Z}]+)*$/u;
317
362
  OPTIONAL_LABEL_RE = /^(?:[^\p{C}\p{Z}]+(?: [^\p{C}\p{Z}]+)*)?$/u;
363
+ mergeGroups = z2.array(distinctList(z2.enum(STATUSLINE_ROWS)).min(2)).refine((groups) => {
364
+ const rows = groups.flat();
365
+ return new Set(rows).size === rows.length;
366
+ }, "must not name a row in more than one group");
318
367
  colourValue = z2.union([z2.enum(STATUSLINE_COLOUR_NAMES), z2.number().int().min(0).max(MAX_STATUSLINE_PALETTE_INDEX), z2.string().regex(/^#[0-9a-fA-F]{6}$/, "must be a colour name, a palette index from 0 to 255 or #rrggbb")]);
319
368
  barCharacter = z2.string().refine(isSingleVisibleGrapheme, "must be a single visible character that takes one terminal column");
320
369
  STATUSLINE_CONTEXT_VALUES = ["both", "percent", "tokens", "remaining"];
@@ -322,14 +371,27 @@ var init_statusline = __esm({
322
371
  AUTOCOMPACT_RESERVE_FRACTION = 0.165;
323
372
  AUTOCOMPACT_RESERVE_RAMP_START = 0.05;
324
373
  AUTOCOMPACT_RESERVE_RAMP_END = 0.5;
325
- labelText = z2.string().refine((text) => stringWidth2(text) <= MAX_STATUSLINE_LABEL_COLUMNS, `must take no more than ${String(MAX_STATUSLINE_LABEL_COLUMNS)} terminal columns`).refine((text) => !drawsNothing(text), "must show something: a label of only combining marks or blank characters draws nothing").regex(LABEL_RE2, "must be visible text without control characters");
374
+ labelWithinColumns = (text) => stringWidth2(text) <= MAX_STATUSLINE_LABEL_COLUMNS;
375
+ labelWithinColumnsMessage = `must take no more than ${String(MAX_STATUSLINE_LABEL_COLUMNS)} terminal columns`;
376
+ labelShowsSomething = (text) => !drawsNothing(text);
377
+ labelShowsSomethingMessage = "must show something: a label of only combining marks or blank characters draws nothing";
378
+ labelIsPlainText = (text) => LABEL_RE2.test(text);
379
+ labelIsPlainTextMessage = "must be visible text without control characters";
380
+ windowLabel = z2.string().refine((text) => text === "" || labelWithinColumns(text), labelWithinColumnsMessage).refine((text) => text === "" || labelShowsSomething(text), labelShowsSomethingMessage).refine((text) => text === "" || labelIsPlainText(text), labelIsPlainTextMessage);
326
381
  overrideText = z2.string().max(MAX_DISPLAY_TEXT_CHARS).regex(OPTIONAL_LABEL_RE, "must be visible text without control characters");
327
382
  wholePercent = z2.number().int().min(1).max(MAX_PERCENT);
328
383
  thresholdPercent = z2.number().int().min(0).max(MAX_PERCENT);
329
384
  SHAPES = {
330
385
  layout: {
331
386
  header: distinctList(z2.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."),
332
- rows: distinctList(z2.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.")
387
+ rows: distinctList(z2.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."),
388
+ mode: z2.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."),
389
+ rule: z2.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.`),
390
+ mergeBars: z2.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."),
391
+ 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."),
392
+ rightAlign: distinctList(z2.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."),
393
+ maxWidth: z2.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.`),
394
+ forceMaxWidth: z2.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.")
333
395
  },
334
396
  model: {
335
397
  format: z2.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."),
@@ -363,8 +425,8 @@ var init_statusline = __esm({
363
425
  criticalAt: wholePercent.describe("The whole percentage of use at which a bar turns red.")
364
426
  },
365
427
  labels: {
366
- fiveHour: labelText.describe("The name of the five-hour subscription window."),
367
- sevenDay: labelText.describe("The name of the weekly subscription window.")
428
+ fiveHour: windowLabel.describe("The name of the five-hour subscription window; empty uses the language's own name for it."),
429
+ sevenDay: windowLabel.describe("The name of the weekly subscription window; empty uses the language's own name for it.")
368
430
  },
369
431
  bar: {
370
432
  width: z2.number().int().min(1).max(MAX_BAR_CELLS).describe("A bar's width in cells before the line's spare width is shared out among the bars."),
@@ -430,7 +492,16 @@ var init_statusline = __esm({
430
492
  }
431
493
  };
432
494
  UNSET_NOTE = "A layer may write null to return it to unset when a shallower layer sets it.";
495
+ LANGUAGE_DESCRIPTION = "The language of the words the statusline writes (labels, units of waiting such as `ago`, the names of effort levels): en, zh-Hans for Chinese in simplified characters, and zh-Hant for traditional; zh and zh-TW are read as zh-Hans and zh-Hant, and any other tag is refused. Durations, token counts, the dollar sign and clock times are written the same in every language, and a clock time follows the system locale and the clock settings.";
496
+ LANGUAGE_TAGS = [...STATUSLINE_LANGUAGES, "zh", "zh-TW"];
497
+ LANGUAGE_ALIASES = { zh: "zh-Hans", "zh-TW": "zh-Hant" };
498
+ TOP_LEVEL = {
499
+ language: z2.enum(STATUSLINE_LANGUAGES).describe(LANGUAGE_DESCRIPTION),
500
+ preset: z2.enum(STATUSLINE_PRESETS).describe("A named bundle of settings (layout.header, layout.rows, git.enabled, git.showDirty, git.showAheadBehind, git.showLineDiff, jj.enabled, jj.showDirty, jj.showConflicts, usage.resetLabel and context.breakdown) that this layer sets beneath the keys it names itself and above everything the shallower layers set: standard is the statusline's own; full, essential and minimal are claude-hud's presets. Each draws the model, project and version control segments and the context row; full adds the effort, advisor, version, output style, cost, session name and duration segments and the quota, added directories, memory, environment, prompt cache, compactions and session tokens rows, with the token breakdown at high use, essential adds the duration and the added directories row, and minimal adds the added directories row alone.")
501
+ };
433
502
  StatuslineSchema = z2.strictObject({
503
+ language: z2.enum(LANGUAGE_TAGS).transform(canonicalLanguage).describe(LANGUAGE_DESCRIPTION).optional(),
504
+ preset: TOP_LEVEL.preset.optional(),
434
505
  layout: z2.strictObject(SHAPES.layout).partial().optional(),
435
506
  model: z2.strictObject(SHAPES.model).partial().optional(),
436
507
  provider: z2.strictObject(SHAPES.provider).partial().optional(),
@@ -453,6 +524,8 @@ var init_statusline = __esm({
453
524
  jj: z2.strictObject(SHAPES.jj).partial().optional()
454
525
  });
455
526
  ResolvedStatuslineSchema = z2.strictObject({
527
+ language: TOP_LEVEL.language,
528
+ preset: TOP_LEVEL.preset,
456
529
  layout: z2.strictObject(SHAPES.layout),
457
530
  model: z2.strictObject(SHAPES.model),
458
531
  provider: z2.strictObject(SHAPES.provider),
@@ -475,7 +548,9 @@ var init_statusline = __esm({
475
548
  jj: z2.strictObject(SHAPES.jj)
476
549
  });
477
550
  STATUSLINE_DEFAULTS = {
478
- layout: { header: [...DEFAULT_HEADER_SEGMENTS], rows: [...DEFAULT_ROWS] },
551
+ language: "en",
552
+ preset: "standard",
553
+ layout: { header: [...DEFAULT_HEADER_SEGMENTS], rows: [...DEFAULT_ROWS], mode: "expanded", rule: "none", mergeBars: true, merge: [], rightAlign: [], maxWidth: 0, forceMaxWidth: false },
479
554
  model: { format: "full", override: "", source: "client" },
480
555
  provider: { name: "" },
481
556
  account: { parts: ["user", "organisation", "plan"], userLength: 0 },
@@ -483,7 +558,7 @@ var init_statusline = __esm({
483
558
  advisor: { override: "" },
484
559
  context: { bar: "shown", value: "both", estimate: "never", breakdown: "never" },
485
560
  bands: { warnAt: 70, criticalAt: 90 },
486
- labels: { fiveHour: "5h", sevenDay: "Wk" },
561
+ labels: { fiveHour: "", sevenDay: "" },
487
562
  bar: { width: 10, filled: "\u2588", empty: "\u2591" },
488
563
  usage: { value: "used", bar: true, compact: false, resetLabel: false, modelScoped: false, timeFormat: "relative", threshold: 0, sevenDayThreshold: 0 },
489
564
  clock: { hourCycle: "auto", seconds: false },
@@ -4553,7 +4628,7 @@ var init_package = __esm({
4553
4628
  "package.json"() {
4554
4629
  package_default = {
4555
4630
  name: "claude-use",
4556
- version: "11.60.2",
4631
+ version: "11.62.0",
4557
4632
  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.",
4558
4633
  license: "MIT",
4559
4634
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -7792,6 +7867,9 @@ function keyOf(path41, declared) {
7792
7867
  if (leaf instanceof z16.ZodArray && leaf.element instanceof z16.ZodEnum) {
7793
7868
  return { ...common, kind: "list", choices: Object.values(leaf.element.enum).map(String) };
7794
7869
  }
7870
+ if (leaf instanceof z16.ZodArray && leaf.element instanceof z16.ZodArray && leaf.element.element instanceof z16.ZodEnum) {
7871
+ return { ...common, kind: "groups", choices: Object.values(leaf.element.element.enum).map(String) };
7872
+ }
7795
7873
  throw new TypeError(`statusline setting ${path41.join(".")} has a kind the key listing does not describe`);
7796
7874
  }
7797
7875
  function walk(shape, prefix) {