@narumitw/pi-statusline 0.49.15 → 0.50.1

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.ts CHANGED
@@ -9,13 +9,12 @@ import {
9
9
  loadStatuslineSettingsForAgent,
10
10
  resolvePreset,
11
11
  settingsFilePath
12
- } from "./chunks/chunk-74QZGJ7L.js";
12
+ } from "./chunks/chunk-J2ZSGEDQ.js";
13
13
 
14
14
  // src/statusline.ts
15
15
  import { homedir } from "node:os";
16
- import {
17
- getAgentDir as getAgentDir2
18
- } from "@earendil-works/pi-coding-agent";
16
+ import { getAgentDir as getAgentDir2 } from "@earendil-works/pi-coding-agent";
17
+ import { getCapabilities } from "@earendil-works/pi-tui";
19
18
 
20
19
  // src/extension-status.ts
21
20
  import { existsSync, readFileSync } from "node:fs";
@@ -27,17 +26,21 @@ import { wrapTextWithAnsi } from "@earendil-works/pi-tui";
27
26
  import { visibleWidth } from "@earendil-works/pi-tui";
28
27
 
29
28
  // src/ansi.ts
30
- function ansiStyle(text, colors) {
29
+ function ansiStyle(text, colors, trueColor = true) {
31
30
  const codes = [
32
- colors.fg ? truecolorCode("38", colors.fg) : void 0,
33
- colors.bg ? truecolorCode("48", colors.bg) : void 0
31
+ colors.fg ? colorCode("38", colors.fg, trueColor) : void 0,
32
+ colors.bg ? colorCode("48", colors.bg, trueColor) : void 0
34
33
  ].filter((code) => code !== void 0);
35
34
  if (codes.length === 0) return text;
36
35
  return `\x1B[${codes.join(";")}m${text}\x1B[0m`;
37
36
  }
38
- function truecolorCode(prefix, hex) {
37
+ function colorCode(prefix, hex, trueColor) {
39
38
  const { red, green, blue } = hexToRgb(hex);
40
- return `${prefix};2;${red};${green};${blue}`;
39
+ return trueColor ? `${prefix};2;${red};${green};${blue}` : `${prefix};5;${rgbToAnsi256(red, green, blue)}`;
40
+ }
41
+ function rgbToAnsi256(red, green, blue) {
42
+ const channels = [red, green, blue].map((value) => Math.round(value / 255 * 5));
43
+ return 16 + 36 * (channels[0] ?? 0) + 6 * (channels[1] ?? 0) + (channels[2] ?? 0);
41
44
  }
42
45
  function hexToRgb(hex) {
43
46
  const normalized = hex.replace(/^#/, "");
@@ -49,9 +52,9 @@ function hexToRgb(hex) {
49
52
  }
50
53
 
51
54
  // src/powerline.ts
52
- function renderPowerlineStatusline(width, items, config) {
55
+ function renderPowerlineStatusline(width, items, config, trueColor = true) {
53
56
  if (items.length === 0 || width <= 0) return "";
54
- return splitLines(items).map((segments) => fitPowerlineSegments(segments, width, config)).join("\n");
57
+ return splitLines(items).map((segments) => fitPowerlineSegments(segments, width, config, trueColor)).join("\n");
55
58
  }
56
59
  function splitLines(items) {
57
60
  const lines = [[]];
@@ -76,11 +79,11 @@ var SEGMENT_RETENTION_PRIORITY = {
76
79
  turn: 20,
77
80
  brand: 10
78
81
  };
79
- function fitPowerlineSegments(segments, width, config) {
82
+ function fitPowerlineSegments(segments, width, config, trueColor) {
80
83
  if (segments.length === 0) return "";
81
84
  const fitted = [...segments];
82
85
  while (fitted.length > 1) {
83
- const rendered2 = joinPowerlineSegments(fitted, config);
86
+ const rendered2 = joinPowerlineSegments(fitted, config, trueColor);
84
87
  if (visibleWidth(rendered2) <= width) return rendered2;
85
88
  let removalIndex = 0;
86
89
  for (let index = 1; index < fitted.length; index += 1) {
@@ -92,23 +95,25 @@ function fitPowerlineSegments(segments, width, config) {
92
95
  }
93
96
  fitted.splice(removalIndex, 1);
94
97
  }
95
- const rendered = joinPowerlineSegments(fitted, config);
98
+ const rendered = joinPowerlineSegments(fitted, config, trueColor);
96
99
  return visibleWidth(rendered) <= width ? rendered : "";
97
100
  }
98
- function powerlineExtensionSeparator(_theme, palettePreset = "tokyo-night") {
99
- return ansiStyle(" \u2022 ", { fg: resolvePreset(palettePreset).extensionSeparator });
101
+ function powerlineExtensionSeparator(_theme, palettePreset = "tokyo-night", trueColor = true) {
102
+ return ansiStyle(" \u2022 ", { fg: resolvePreset(palettePreset).extensionSeparator }, trueColor);
100
103
  }
101
- function joinPowerlineSegments(segments, config) {
104
+ function joinPowerlineSegments(segments, config, trueColor) {
102
105
  const preset = resolvePreset(config.palettePreset);
103
106
  const blocks = contiguousBlocks(segments, preset, config.palettePreset, config.palette);
104
- let line = ansiStyle("\u2591\u2592\u2593", { fg: preset.lead });
107
+ let line = ansiStyle("\u2591\u2592\u2593", { fg: preset.lead }, trueColor);
105
108
  for (const [index, block] of blocks.entries()) {
106
109
  const previous = index === 0 ? void 0 : blocks[index - 1]?.colors;
107
- if (previous) line += ansiStyle("\uE0B4", { fg: previous.bg, bg: block.colors.bg });
108
- line += ansiStyle(formatBlockText(block, config), block.colors);
110
+ if (previous) {
111
+ line += ansiStyle("\uE0B4", { fg: previous.bg, bg: block.colors.bg }, trueColor);
112
+ }
113
+ line += ansiStyle(formatBlockText(block, config), block.colors, trueColor);
109
114
  }
110
115
  const lastBlock = blocks.at(-1);
111
- if (lastBlock) line += ansiStyle("\uE0B4", { fg: lastBlock.colors.bg });
116
+ if (lastBlock) line += ansiStyle("\uE0B4", { fg: lastBlock.colors.bg }, trueColor);
112
117
  return line;
113
118
  }
114
119
  function contiguousBlocks(segments, preset, palettePreset, configuredPalette) {
@@ -161,18 +166,14 @@ var COMPATIBLE_STATUS_ICON_KEYS = {
161
166
  pisync: "sync"
162
167
  };
163
168
  var EMPTY_EXTENSION_STATUS_ICON_ALIASES = /* @__PURE__ */ new Map();
164
- function extensionStatusSeparator(config, theme) {
165
- return powerlineExtensionSeparator(theme, config.palettePreset);
169
+ function extensionStatusSeparator(config, theme, trueColor) {
170
+ return powerlineExtensionSeparator(theme, config.palettePreset, trueColor);
166
171
  }
167
- function formatExtensionStatuses(statuses, theme, config, runtime, hiddenKeys = /* @__PURE__ */ new Set()) {
168
- const separator = extensionStatusSeparator(config, theme);
172
+ function formatExtensionStatuses(statuses, theme, config, runtime, hiddenKeys = /* @__PURE__ */ new Set(), trueColor = true) {
173
+ const separator = extensionStatusSeparator(config, theme, trueColor);
169
174
  const visibleStatuses = [
170
175
  ...formatDuplicateExtensionStatus(runtime, theme),
171
- ...[...statuses.entries()].filter(
172
- ([key, value]) => key !== STATUSLINE_KEY && !hiddenKeys.has(key) && value.trim().length > 0
173
- ).map(
174
- ([key, value]) => formatExtensionStatus(key, value, theme, config, runtime.extensionStatusIconAliases)
175
- )
176
+ ...[...statuses.entries()].filter(([key, value]) => key !== STATUSLINE_KEY && !hiddenKeys.has(key) && value.trim().length > 0).map(([key, value]) => formatExtensionStatus(key, value, theme, config, runtime.extensionStatusIconAliases))
176
177
  ].slice(0, 5);
177
178
  return visibleStatuses.join(separator);
178
179
  }
@@ -181,12 +182,7 @@ function formatExtensionStatus(key, value, theme, config, extensionStatusIconAli
181
182
  const text = simplifyExtensionStatusText(status.text);
182
183
  const color = extensionColor(key, value);
183
184
  const textColor = color === "warning" ? "warning" : "muted";
184
- const icon = extensionStatusIcon(
185
- key,
186
- status.icon,
187
- config.extensionStatusIcons,
188
- extensionStatusIconAliases
189
- );
185
+ const icon = extensionStatusIcon(key, status.icon, config.extensionStatusIcons, extensionStatusIconAliases);
190
186
  const renderedText = theme.fg(textColor, text);
191
187
  return icon ? `${theme.fg(color, icon)} ${renderedText}` : renderedText;
192
188
  }
@@ -277,9 +273,7 @@ function readInstalledExtensionPackages(cwd) {
277
273
  return packages;
278
274
  }
279
275
  function extensionSettingsFiles(cwd) {
280
- return [join(getAgentDir(), "settings.json"), join(cwd, ".pi", "settings.json")].filter(
281
- (file) => existsSync(file)
282
- );
276
+ return [join(getAgentDir(), "settings.json"), join(cwd, ".pi", "settings.json")].filter((file) => existsSync(file));
283
277
  }
284
278
  function findDuplicateExtensions(installedPackages) {
285
279
  const sourcesByPackage = /* @__PURE__ */ new Map();
@@ -293,23 +287,16 @@ function findDuplicateExtensions(installedPackages) {
293
287
  function buildExtensionStatusIconAliases(installedPackages) {
294
288
  const packageAliasesByStatusBase = /* @__PURE__ */ new Map();
295
289
  for (const extensionPackage of installedPackages) {
296
- const candidate = extensionStatusIconAliasCandidate(
297
- extensionPackage.packageName,
298
- extensionPackage.source
299
- );
290
+ const candidate = extensionStatusIconAliasCandidate(extensionPackage.packageName, extensionPackage.source);
300
291
  if (!candidate) continue;
301
292
  const aliasesByPackage = packageAliasesByStatusBase.get(candidate.statusBase) ?? /* @__PURE__ */ new Map();
302
293
  const existingAliases = aliasesByPackage.get(extensionPackage.packageName) ?? [];
303
- aliasesByPackage.set(
304
- extensionPackage.packageName,
305
- uniqueStrings([...existingAliases, ...candidate.aliases])
306
- );
294
+ aliasesByPackage.set(extensionPackage.packageName, uniqueStrings([...existingAliases, ...candidate.aliases]));
307
295
  packageAliasesByStatusBase.set(candidate.statusBase, aliasesByPackage);
308
296
  }
309
297
  const aliases = /* @__PURE__ */ new Map();
310
298
  for (const [statusBase, aliasesByPackage] of packageAliasesByStatusBase) {
311
- if (aliasesByPackage.size === 1)
312
- aliases.set(statusBase, [...aliasesByPackage.values()][0] ?? []);
299
+ if (aliasesByPackage.size === 1) aliases.set(statusBase, [...aliasesByPackage.values()][0] ?? []);
313
300
  }
314
301
  return aliases;
315
302
  }
@@ -375,11 +362,11 @@ import { isAbsolute as isAbsolute2 } from "node:path";
375
362
  var GIT_STATUS_TIMEOUT_MS = 3e3;
376
363
  async function readGitStatus(pi, cwd, signal) {
377
364
  const [statusResult, rootResult] = await Promise.all([
378
- pi.exec(
379
- "git",
380
- ["--no-optional-locks", "status", "--porcelain=v1", "--branch", "--untracked-files=normal"],
381
- { cwd, timeout: GIT_STATUS_TIMEOUT_MS, signal }
382
- ),
365
+ pi.exec("git", ["--no-optional-locks", "status", "--porcelain=v1", "--branch", "--untracked-files=normal"], {
366
+ cwd,
367
+ timeout: GIT_STATUS_TIMEOUT_MS,
368
+ signal
369
+ }),
383
370
  pi.exec("git", ["rev-parse", "--path-format=absolute", "--show-toplevel"], {
384
371
  cwd,
385
372
  timeout: GIT_STATUS_TIMEOUT_MS,
@@ -463,6 +450,7 @@ function formatCount(value) {
463
450
  }
464
451
 
465
452
  // src/render.ts
453
+ import { sliceByColumn, visibleWidth as visibleWidth2 } from "@earendil-works/pi-tui";
466
454
  import { sanitizeTerminalText as sanitizeTerminalText2 } from "@narumitw/pi-tui-kit/terminal-text";
467
455
 
468
456
  // src/directory.ts
@@ -523,7 +511,7 @@ function summarizeFooterUsage(entries) {
523
511
  totals.latestCacheHitRate = promptTokens > 0 ? cacheRead / promptTokens * 100 : void 0;
524
512
  } else if (entry.type === "message" && entry.message.role === "toolResult") {
525
513
  usage = entry.message.usage;
526
- } else if (entry.type === "compaction" || entry.type === "branch_summary") {
514
+ } else if (entry.type === "compaction" || entry.type === "branch_summary" || entry.type === "usage") {
527
515
  usage = entry.usage;
528
516
  }
529
517
  if (!usage) continue;
@@ -539,7 +527,7 @@ function summarizeFooterUsage(entries) {
539
527
  // src/render.ts
540
528
  var GITHUB_PR_KEY = "github-pr";
541
529
  var GITHUB_PR_STATUS_KEYS = /* @__PURE__ */ new Set([GITHUB_PR_KEY]);
542
- function renderStatusline(width, ctx, footerData, _theme, config, runtime) {
530
+ function renderStatusline(width, ctx, footerData, _theme, config, runtime, trueColor = true) {
543
531
  if (width <= 0) return "";
544
532
  const usageSummary = summarizeFooterUsage(ctx.sessionManager.getEntries());
545
533
  const rows = [
@@ -557,16 +545,14 @@ function renderStatusline(width, ctx, footerData, _theme, config, runtime) {
557
545
  if (rendered && rendered.text.length > 0) row.segments.push(rendered);
558
546
  }
559
547
  const segments = [];
560
- const renderedRows = rows.filter(
561
- (row) => row.configuredSegments === 0 || row.segments.length > 0
562
- );
548
+ const renderedRows = rows.filter((row) => row.configuredSegments === 0 || row.segments.length > 0);
563
549
  for (const [index, row] of renderedRows.entries()) {
564
550
  if (index > 0) segments.push({ name: LINE_BREAK_SEGMENT_NAME });
565
551
  segments.push(...row.segments);
566
552
  }
567
- return renderPowerlineStatusline(width, segments, config);
553
+ return renderPowerlineStatusline(width, segments, config, trueColor);
568
554
  }
569
- function renderExtensionStatusline(width, footerData, theme, config, runtime, mainLine) {
555
+ function renderExtensionStatusline(width, footerData, theme, config, runtime, mainLine, trueColor = true) {
570
556
  const statuses = footerData.getExtensionStatuses();
571
557
  const prContext = prContextFromStatuses(statuses);
572
558
  const rendersPrInline = prContext !== void 0 && mainLine.includes(prContext);
@@ -575,7 +561,8 @@ function renderExtensionStatusline(width, footerData, theme, config, runtime, ma
575
561
  theme,
576
562
  config,
577
563
  runtime,
578
- rendersPrInline ? GITHUB_PR_STATUS_KEYS : void 0
564
+ rendersPrInline ? GITHUB_PR_STATUS_KEYS : void 0,
565
+ trueColor
579
566
  );
580
567
  return wrapExtensionStatusline(status, width);
581
568
  }
@@ -596,23 +583,11 @@ function buildSegment(name, ctx, footerData, config, runtime, usageSummary) {
596
583
  return segment(name, model, config, "accent", "header");
597
584
  }
598
585
  case "thinking":
599
- return segment(
600
- name,
601
- runtime.thinkingLevel,
602
- config,
603
- thinkingColor(runtime.thinkingLevel),
604
- "header"
605
- );
586
+ return segment(name, runtime.thinkingLevel, config, thinkingColor(runtime.thinkingLevel), "header");
606
587
  case "branch": {
607
588
  const branch = footerData.getGitBranch();
608
589
  const pr = branch ? prContextFromStatuses(footerData.getExtensionStatuses()) : void 0;
609
- return segment(
610
- name,
611
- formatGitBranchValue(branch, runtime.gitStatus, pr),
612
- config,
613
- "accent",
614
- "git"
615
- );
590
+ return segment(name, formatGitBranchValue(branch, runtime.gitStatus, pr), config, "accent", "git");
616
591
  }
617
592
  case "cwd":
618
593
  return segment(
@@ -701,12 +676,36 @@ function contextColor(percent) {
701
676
  if (percent >= 70) return "warning";
702
677
  return "success";
703
678
  }
679
+ var MAX_UI_PROMPT_TITLE_CODE_POINTS = 256;
680
+ var MAX_UI_PROMPT_TITLE_WIDTH = 40;
681
+ function boundUIPromptTitleLength(title) {
682
+ let end = 0;
683
+ let ellipsisEnd = 0;
684
+ let codePoints = 0;
685
+ while (end < title.length && codePoints < MAX_UI_PROMPT_TITLE_CODE_POINTS) {
686
+ const codePoint = title.codePointAt(end) ?? 0;
687
+ end += codePoint > 65535 ? 2 : 1;
688
+ codePoints += 1;
689
+ if (codePoints < MAX_UI_PROMPT_TITLE_CODE_POINTS) ellipsisEnd = end;
690
+ }
691
+ return end < title.length ? `${title.slice(0, ellipsisEnd)}\u2026` : title;
692
+ }
693
+ function formatUIPromptTitle(title) {
694
+ const safeTitle = title ? sanitizeTerminalText2(title).trim() : "";
695
+ const boundedTitle = boundUIPromptTitleLength(safeTitle);
696
+ if (visibleWidth2(boundedTitle) <= MAX_UI_PROMPT_TITLE_WIDTH) return boundedTitle;
697
+ return `${sliceByColumn(boundedTitle, 0, MAX_UI_PROMPT_TITLE_WIDTH - 1, true)}\u2026`;
698
+ }
704
699
  function formatToolActivity(runtime) {
700
+ if (runtime.uiPrompt) {
701
+ const title = formatUIPromptTitle(runtime.uiPrompt.title);
702
+ return `\u2328 waiting for ${runtime.uiPrompt.kind}${title ? ` \xB7 ${title}` : ""}`;
703
+ }
705
704
  const active = [...runtime.activeTools.entries()];
706
705
  if (active.length > 0) {
707
706
  const [name, count] = active[0] ?? ["tool", 1];
708
707
  const suffix = count > 1 ? `\xD7${count}` : active.length > 1 ? `+${active.length - 1}` : "";
709
- return `\u2699 ${name}${suffix}`;
708
+ return `\u2699\uFE0F ${name}${suffix}`;
710
709
  }
711
710
  return runtime.isStreaming ? "\u{1F4AD} thinking" : void 0;
712
711
  }
@@ -721,10 +720,15 @@ function prLinkFromStatuses(statuses) {
721
720
  }
722
721
  function prContextFromStatuses(statuses) {
723
722
  const value = statuses.get(GITHUB_PR_KEY);
723
+ if (!value) return void 0;
724
724
  const link = prLinkFromStatuses(statuses);
725
- if (!value || !link) return void 0;
726
- const state = compactPrState(value.replace(link, ""));
727
- return state ? `${link} \xB7 ${state}` : void 0;
725
+ const reference = link ?? plainPrReference(value);
726
+ if (!reference) return void 0;
727
+ const state = compactPrState(link ? value.replace(link, "") : value);
728
+ return state ? `${reference} \xB7 ${state}` : void 0;
729
+ }
730
+ function plainPrReference(value) {
731
+ return /^PR\s+(#\d+):/u.exec(value)?.[1];
728
732
  }
729
733
  function compactPrState(value) {
730
734
  if (/:\s*merged\s*$/.test(value)) return "merged";
@@ -788,7 +792,7 @@ var EMPTY_EXTENSION_STATUS_ICON_ALIASES2 = /* @__PURE__ */ new Map();
788
792
  var statuslineCommandsPromise;
789
793
  function loadStatuslineCommands() {
790
794
  if (!statuslineCommandsPromise) {
791
- statuslineCommandsPromise = import("./chunks/commands-C2G2G7C6.js").catch((error) => {
795
+ statuslineCommandsPromise = import("./chunks/commands-PPT3AJTL.js").catch((error) => {
792
796
  statuslineCommandsPromise = void 0;
793
797
  throw error;
794
798
  });
@@ -927,11 +931,10 @@ function statusline(pi) {
927
931
  render(width) {
928
932
  if (!loaded) return [];
929
933
  const config = previewPalettePreset ? { ...loaded.config, palettePreset: previewPalettePreset } : loaded.config;
930
- const mainLine = renderStatusline(width, ctx, footerData, theme, config, runtime);
934
+ const trueColor = getCapabilities().trueColor;
935
+ const mainLine = renderStatusline(width, ctx, footerData, theme, config, runtime, trueColor);
931
936
  const lines = mainLine ? mainLine.split("\n") : [];
932
- lines.push(
933
- ...renderExtensionStatusline(width, footerData, theme, config, runtime, mainLine)
934
- );
937
+ lines.push(...renderExtensionStatusline(width, footerData, theme, config, runtime, mainLine, trueColor));
935
938
  return lines;
936
939
  }
937
940
  };
@@ -975,6 +978,7 @@ function statusline(pi) {
975
978
  runtime.turnCount = 0;
976
979
  runtime.activeTools.clear();
977
980
  runtime.isStreaming = false;
981
+ runtime.uiPrompt = void 0;
978
982
  loaded = loadStatuslineSettingsForAgent(agentDir);
979
983
  const settingsNotice = consumeStatuslineSettingsNotice();
980
984
  if (settingsNotice) ctx.ui.notify(settingsNotice, "warning");
@@ -1001,6 +1005,7 @@ function statusline(pi) {
1001
1005
  runtime.gitStatus = void 0;
1002
1006
  runtime.activeTools.clear();
1003
1007
  runtime.isStreaming = false;
1008
+ runtime.uiPrompt = void 0;
1004
1009
  runtime.duplicateExtensions = [];
1005
1010
  runtime.extensionStatusIconAliases = EMPTY_EXTENSION_STATUS_ICON_ALIASES2;
1006
1011
  ctx.ui.setFooter(void 0);
@@ -1029,6 +1034,16 @@ function statusline(pi) {
1029
1034
  runtime.activeTools.clear();
1030
1035
  refresh();
1031
1036
  });
1037
+ pi.on("ui_prompt_start", (event, ctx) => {
1038
+ if (!ownsRuntime(ctx)) return;
1039
+ runtime.uiPrompt = { kind: event.kind, title: event.title };
1040
+ refresh();
1041
+ });
1042
+ pi.on("ui_prompt_end", (_event, ctx) => {
1043
+ if (!ownsRuntime(ctx)) return;
1044
+ runtime.uiPrompt = void 0;
1045
+ refresh();
1046
+ });
1032
1047
  pi.on("turn_start", (_event, ctx) => {
1033
1048
  if (!ownsRuntime(ctx)) return;
1034
1049
  runtime.turnCount += 1;
@@ -1058,10 +1073,7 @@ function statusline(pi) {
1058
1073
  function formatSettingsDiagnostics(loaded) {
1059
1074
  const details = loaded.diagnostics.slice(0, 5).map((item) => item.message);
1060
1075
  const remaining = loaded.diagnostics.length - details.length;
1061
- return [
1062
- `pi-statusline settings: ${details.join("; ")}`,
1063
- ...remaining > 0 ? [`+${remaining} more`] : []
1064
- ].join(" ");
1076
+ return [`pi-statusline settings: ${details.join("; ")}`, ...remaining > 0 ? [`+${remaining} more`] : []].join(" ");
1065
1077
  }
1066
1078
  export {
1067
1079
  statusline as default