@herbertgao/pi-extensions 2026.8.3 → 2026.8.4

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.
Files changed (122) hide show
  1. package/README.md +41 -0
  2. package/examples/pi-footer.json +275 -0
  3. package/node_modules/pi-footer/CHANGELOG.md +145 -0
  4. package/node_modules/pi-footer/LICENSE +21 -0
  5. package/node_modules/pi-footer/README.md +354 -0
  6. package/node_modules/pi-footer/package.json +68 -0
  7. package/node_modules/pi-footer/src/cache.ts +106 -0
  8. package/node_modules/pi-footer/src/colors.ts +237 -0
  9. package/node_modules/pi-footer/src/config.ts +202 -0
  10. package/node_modules/pi-footer/src/event-widgets.ts +41 -0
  11. package/node_modules/pi-footer/src/extension-statuses.ts +102 -0
  12. package/node_modules/pi-footer/src/git.ts +135 -0
  13. package/node_modules/pi-footer/src/index.ts +268 -0
  14. package/node_modules/pi-footer/src/metrics.ts +141 -0
  15. package/node_modules/pi-footer/src/presets.ts +383 -0
  16. package/node_modules/pi-footer/src/render.ts +114 -0
  17. package/node_modules/pi-footer/src/separators.ts +52 -0
  18. package/node_modules/pi-footer/src/types.ts +122 -0
  19. package/node_modules/pi-footer/src/ui/color-level-confirm.ts +22 -0
  20. package/node_modules/pi-footer/src/ui/color-options.ts +195 -0
  21. package/node_modules/pi-footer/src/ui/config-lifecycle.ts +61 -0
  22. package/node_modules/pi-footer/src/ui/edit-colors.ts +13 -0
  23. package/node_modules/pi-footer/src/ui/events.ts +35 -0
  24. package/node_modules/pi-footer/src/ui/extension-status-picker.ts +60 -0
  25. package/node_modules/pi-footer/src/ui/extension-statuses.ts +73 -0
  26. package/node_modules/pi-footer/src/ui/fields.ts +252 -0
  27. package/node_modules/pi-footer/src/ui/global-menu.ts +126 -0
  28. package/node_modules/pi-footer/src/ui/helpers.ts +44 -0
  29. package/node_modules/pi-footer/src/ui/layout.ts +17 -0
  30. package/node_modules/pi-footer/src/ui/line-list.ts +19 -0
  31. package/node_modules/pi-footer/src/ui/model.ts +64 -0
  32. package/node_modules/pi-footer/src/ui/navigation.ts +29 -0
  33. package/node_modules/pi-footer/src/ui/option-edit.ts +93 -0
  34. package/node_modules/pi-footer/src/ui/overlay-render.ts +103 -0
  35. package/node_modules/pi-footer/src/ui/screen-context.ts +18 -0
  36. package/node_modules/pi-footer/src/ui/screen-controller.ts +27 -0
  37. package/node_modules/pi-footer/src/ui/screen-render.ts +32 -0
  38. package/node_modules/pi-footer/src/ui/screen-state.ts +20 -0
  39. package/node_modules/pi-footer/src/ui/screen.ts +243 -0
  40. package/node_modules/pi-footer/src/ui/screens/add-widget.ts +92 -0
  41. package/node_modules/pi-footer/src/ui/screens/confirm-exit.ts +52 -0
  42. package/node_modules/pi-footer/src/ui/screens/controller.ts +12 -0
  43. package/node_modules/pi-footer/src/ui/screens/edit-colors.ts +68 -0
  44. package/node_modules/pi-footer/src/ui/screens/edit-widget.ts +115 -0
  45. package/node_modules/pi-footer/src/ui/screens/extension-status-row.ts +61 -0
  46. package/node_modules/pi-footer/src/ui/screens/global.ts +75 -0
  47. package/node_modules/pi-footer/src/ui/screens/line-list.ts +108 -0
  48. package/node_modules/pi-footer/src/ui/screens/main.ts +99 -0
  49. package/node_modules/pi-footer/src/ui/screens/terminal.ts +121 -0
  50. package/node_modules/pi-footer/src/ui/screens/widget-list.ts +154 -0
  51. package/node_modules/pi-footer/src/ui/terminal-menu.ts +32 -0
  52. package/node_modules/pi-footer/src/ui/theme.ts +48 -0
  53. package/node_modules/pi-footer/src/ui/title-bar.ts +100 -0
  54. package/node_modules/pi-footer/src/ui/widget-actions.ts +76 -0
  55. package/node_modules/pi-footer/src/ui.ts +66 -0
  56. package/node_modules/pi-footer/src/widgets/context.ts +28 -0
  57. package/node_modules/pi-footer/src/widgets/core/active-tools.ts +17 -0
  58. package/node_modules/pi-footer/src/widgets/core/context-window.ts +24 -0
  59. package/node_modules/pi-footer/src/widgets/core/cwd-basename.ts +19 -0
  60. package/node_modules/pi-footer/src/widgets/core/cwd.ts +108 -0
  61. package/node_modules/pi-footer/src/widgets/core/event.ts +34 -0
  62. package/node_modules/pi-footer/src/widgets/core/external-status.ts +102 -0
  63. package/node_modules/pi-footer/src/widgets/core/model-provider.ts +20 -0
  64. package/node_modules/pi-footer/src/widgets/core/model.ts +35 -0
  65. package/node_modules/pi-footer/src/widgets/core/provider.ts +17 -0
  66. package/node_modules/pi-footer/src/widgets/core/session-name.ts +17 -0
  67. package/node_modules/pi-footer/src/widgets/core/text-verbosity.ts +17 -0
  68. package/node_modules/pi-footer/src/widgets/core/thinking-level.ts +17 -0
  69. package/node_modules/pi-footer/src/widgets/git/ahead-behind.ts +17 -0
  70. package/node_modules/pi-footer/src/widgets/git/branch.ts +81 -0
  71. package/node_modules/pi-footer/src/widgets/git/clean.ts +19 -0
  72. package/node_modules/pi-footer/src/widgets/git/deletions.ts +17 -0
  73. package/node_modules/pi-footer/src/widgets/git/diff.ts +38 -0
  74. package/node_modules/pi-footer/src/widgets/git/insertions.ts +17 -0
  75. package/node_modules/pi-footer/src/widgets/git/remote.ts +17 -0
  76. package/node_modules/pi-footer/src/widgets/git/root.ts +17 -0
  77. package/node_modules/pi-footer/src/widgets/git/sha.ts +17 -0
  78. package/node_modules/pi-footer/src/widgets/git/staged.ts +17 -0
  79. package/node_modules/pi-footer/src/widgets/git/status.ts +19 -0
  80. package/node_modules/pi-footer/src/widgets/git/unstaged.ts +17 -0
  81. package/node_modules/pi-footer/src/widgets/git/untracked.ts +17 -0
  82. package/node_modules/pi-footer/src/widgets/instance.ts +118 -0
  83. package/node_modules/pi-footer/src/widgets/layout/custom-text.ts +18 -0
  84. package/node_modules/pi-footer/src/widgets/layout/flex-separator.ts +32 -0
  85. package/node_modules/pi-footer/src/widgets/layout/separator.ts +57 -0
  86. package/node_modules/pi-footer/src/widgets/layout/spacer.ts +31 -0
  87. package/node_modules/pi-footer/src/widgets/options.ts +122 -0
  88. package/node_modules/pi-footer/src/widgets/project/runtime.ts +361 -0
  89. package/node_modules/pi-footer/src/widgets/registry.ts +223 -0
  90. package/node_modules/pi-footer/src/widgets/session/assistant-messages.ts +17 -0
  91. package/node_modules/pi-footer/src/widgets/session/compactions.ts +17 -0
  92. package/node_modules/pi-footer/src/widgets/session/elapsed.ts +18 -0
  93. package/node_modules/pi-footer/src/widgets/session/last-activity.ts +18 -0
  94. package/node_modules/pi-footer/src/widgets/session/messages.ts +19 -0
  95. package/node_modules/pi-footer/src/widgets/session/session-id.ts +17 -0
  96. package/node_modules/pi-footer/src/widgets/session/session-start.ts +18 -0
  97. package/node_modules/pi-footer/src/widgets/session/tool-results.ts +17 -0
  98. package/node_modules/pi-footer/src/widgets/session/total-messages.ts +19 -0
  99. package/node_modules/pi-footer/src/widgets/session/total-time.ts +18 -0
  100. package/node_modules/pi-footer/src/widgets/session/user-messages.ts +17 -0
  101. package/node_modules/pi-footer/src/widgets/store.ts +26 -0
  102. package/node_modules/pi-footer/src/widgets/tokens/cache-hit-rate.ts +55 -0
  103. package/node_modules/pi-footer/src/widgets/tokens/cache-read.ts +18 -0
  104. package/node_modules/pi-footer/src/widgets/tokens/cache-write.ts +18 -0
  105. package/node_modules/pi-footer/src/widgets/tokens/context-bar.ts +108 -0
  106. package/node_modules/pi-footer/src/widgets/tokens/context-length.ts +26 -0
  107. package/node_modules/pi-footer/src/widgets/tokens/context-remaining.ts +29 -0
  108. package/node_modules/pi-footer/src/widgets/tokens/context.ts +29 -0
  109. package/node_modules/pi-footer/src/widgets/tokens/cost.ts +51 -0
  110. package/node_modules/pi-footer/src/widgets/tokens/input-speed.ts +25 -0
  111. package/node_modules/pi-footer/src/widgets/tokens/input-tokens.ts +18 -0
  112. package/node_modules/pi-footer/src/widgets/tokens/output-speed.ts +25 -0
  113. package/node_modules/pi-footer/src/widgets/tokens/output-tokens.ts +18 -0
  114. package/node_modules/pi-footer/src/widgets/tokens/tokens.ts +20 -0
  115. package/node_modules/pi-footer/src/widgets/tokens/total-speed.ts +25 -0
  116. package/node_modules/pi-footer/src/widgets/tokens/total-tokens.ts +18 -0
  117. package/node_modules/pi-footer/src/widgets/types.ts +181 -0
  118. package/node_modules/pi-footer/src/widgets/utils/colors.ts +11 -0
  119. package/node_modules/pi-footer/src/widgets/utils/context.ts +112 -0
  120. package/node_modules/pi-footer/src/widgets/utils/session.ts +19 -0
  121. package/node_modules/pi-footer/src/widgets/utils/token-format.ts +78 -0
  122. package/package.json +7 -2
@@ -0,0 +1,17 @@
1
+ import { defineWidget } from "../types.js";
2
+
3
+ export const AssistantMessagesWidget = defineWidget({
4
+ type: "assistant-messages",
5
+ label: "Assistant Messages",
6
+ category: "Session",
7
+ description: "Assistant message count",
8
+ dependencies: ["metrics"],
9
+ baseOptions: ["raw", "hideWhenZero", "icon", "text"],
10
+ baseOptionDefaults: {},
11
+ properties: [],
12
+ icons: { emoji: "🤖", nerd: "󰚩", text: "assistant" },
13
+ defaultStyle: { fg: "white", bg: "default", bold: false },
14
+ render({ ctx, renderWidget }) {
15
+ return renderWidget(`${ctx.metrics.assistantMessages}`);
16
+ },
17
+ });
@@ -0,0 +1,17 @@
1
+ import { defineWidget } from "../types.js";
2
+
3
+ export const CompactionsWidget = defineWidget({
4
+ type: "compactions",
5
+ label: "Compactions",
6
+ category: "Session",
7
+ description: "Compaction summary count",
8
+ dependencies: ["metrics"],
9
+ baseOptions: ["raw", "icon"],
10
+ baseOptionDefaults: {},
11
+ properties: [],
12
+ icons: { emoji: "🗜️", nerd: "󰁨", text: "compactions" },
13
+ defaultStyle: { fg: "yellow", bg: "default", bold: false },
14
+ render({ ctx, renderWidget }) {
15
+ return renderWidget(`${ctx.metrics.compactions}`);
16
+ },
17
+ });
@@ -0,0 +1,18 @@
1
+ import { defineWidget } from "../types.js";
2
+ import { formatElapsed } from "../utils/session.js";
3
+
4
+ export const ElapsedWidget = defineWidget({
5
+ type: "elapsed",
6
+ label: "Transcript Span",
7
+ category: "Session",
8
+ description: "Time between first and last recorded session entry",
9
+ dependencies: ["metrics"],
10
+ baseOptions: ["raw", "icon"],
11
+ baseOptionDefaults: {},
12
+ properties: [],
13
+ icons: { emoji: "⏱️", nerd: "󱎫", text: "span" },
14
+ defaultStyle: { fg: "yellow", bg: "default", bold: false },
15
+ render({ ctx, renderWidget }) {
16
+ return renderWidget(formatElapsed(ctx.metrics.firstTimestampMs, ctx.metrics.lastTimestampMs));
17
+ },
18
+ });
@@ -0,0 +1,18 @@
1
+ import { defineWidget } from "../types.js";
2
+ import { formatTime } from "../utils/session.js";
3
+
4
+ export const LastActivityWidget = defineWidget({
5
+ type: "last-activity",
6
+ label: "Last Activity",
7
+ category: "Session",
8
+ description: "Most recent session entry time",
9
+ dependencies: ["metrics"],
10
+ baseOptions: ["raw", "hideWhenEmpty", "icon", "text"],
11
+ baseOptionDefaults: { text: "" },
12
+ properties: [],
13
+ icons: { emoji: "🕘", nerd: "󱑃", text: "last" },
14
+ defaultStyle: { fg: "yellow", bg: "default", bold: false },
15
+ render({ ctx, renderWidget }) {
16
+ return renderWidget(formatTime(ctx.metrics.lastTimestampMs));
17
+ },
18
+ });
@@ -0,0 +1,19 @@
1
+ import { defineWidget } from "../types.js";
2
+
3
+ export const MessagesWidget = defineWidget({
4
+ type: "messages",
5
+ label: "Message Counts",
6
+ category: "Session",
7
+ description: "User/assistant/tool message counts",
8
+ dependencies: ["metrics"],
9
+ baseOptions: ["raw", "icon"],
10
+ baseOptionDefaults: {},
11
+ properties: [],
12
+ icons: { emoji: "💬", nerd: "󰭻", text: "msg" },
13
+ defaultStyle: { fg: "cyan", bg: "default", bold: false },
14
+ render({ ctx, renderWidget }) {
15
+ return renderWidget(
16
+ `${ctx.metrics.userMessages}u/${ctx.metrics.assistantMessages}a/${ctx.metrics.toolResults}t`,
17
+ );
18
+ },
19
+ });
@@ -0,0 +1,17 @@
1
+ import { defineWidget } from "../types.js";
2
+
3
+ export const SessionIdWidget = defineWidget({
4
+ type: "session-id",
5
+ label: "Session ID",
6
+ category: "Session",
7
+ description: "Current pi session id",
8
+ dependencies: ["sessionId"],
9
+ baseOptions: ["raw", "hideWhenEmpty", "icon", "text"],
10
+ baseOptionDefaults: { text: "" },
11
+ properties: [],
12
+ icons: { emoji: "🆔", nerd: "󰈙", text: "session id" },
13
+ defaultStyle: { fg: "cyan", bg: "default", bold: false },
14
+ render({ ctx, renderWidget }) {
15
+ return renderWidget(ctx.sessionId);
16
+ },
17
+ });
@@ -0,0 +1,18 @@
1
+ import { defineWidget } from "../types.js";
2
+ import { formatTime } from "../utils/session.js";
3
+
4
+ export const SessionStartWidget = defineWidget({
5
+ type: "session-start",
6
+ label: "Session Start",
7
+ category: "Session",
8
+ description: "First session entry time",
9
+ dependencies: ["metrics"],
10
+ baseOptions: ["raw", "hideWhenEmpty", "icon", "text"],
11
+ baseOptionDefaults: { text: "" },
12
+ properties: [],
13
+ icons: { emoji: "🚀", nerd: "󱑂", text: "started" },
14
+ defaultStyle: { fg: "yellow", bg: "default", bold: false },
15
+ render({ ctx, renderWidget }) {
16
+ return renderWidget(formatTime(ctx.metrics.firstTimestampMs));
17
+ },
18
+ });
@@ -0,0 +1,17 @@
1
+ import { defineWidget } from "../types.js";
2
+
3
+ export const ToolResultsWidget = defineWidget({
4
+ type: "tool-results",
5
+ label: "Tool Results",
6
+ category: "Session",
7
+ description: "Tool result count",
8
+ dependencies: ["metrics"],
9
+ baseOptions: ["raw", "hideWhenZero", "icon", "text"],
10
+ baseOptionDefaults: {},
11
+ properties: [],
12
+ icons: { emoji: "🛠️", nerd: "󰒓", text: "tools" },
13
+ defaultStyle: { fg: "green", bg: "default", bold: false },
14
+ render({ ctx, renderWidget }) {
15
+ return renderWidget(`${ctx.metrics.toolResults}`);
16
+ },
17
+ });
@@ -0,0 +1,19 @@
1
+ import { defineWidget } from "../types.js";
2
+
3
+ export const TotalMessagesWidget = defineWidget({
4
+ type: "total-messages",
5
+ label: "Total Messages",
6
+ category: "Session",
7
+ description: "Total message count",
8
+ dependencies: ["metrics"],
9
+ baseOptions: ["raw", "hideWhenZero", "icon", "text"],
10
+ baseOptionDefaults: {},
11
+ properties: [],
12
+ icons: { emoji: "💬", nerd: "󰭻", text: "messages" },
13
+ defaultStyle: { fg: "cyan", bg: "default", bold: false },
14
+ render({ ctx, renderWidget }) {
15
+ return renderWidget(
16
+ `${ctx.metrics.userMessages + ctx.metrics.assistantMessages + ctx.metrics.toolResults}`,
17
+ );
18
+ },
19
+ });
@@ -0,0 +1,18 @@
1
+ import { defineWidget } from "../types.js";
2
+ import { formatElapsed } from "../utils/session.js";
3
+
4
+ export const TotalTimeWidget = defineWidget({
5
+ type: "total-time",
6
+ label: "Session Total Time",
7
+ category: "Session",
8
+ description: "Live wall-clock time since first session entry",
9
+ dependencies: ["metrics"],
10
+ baseOptions: ["raw", "icon"],
11
+ baseOptionDefaults: {},
12
+ properties: [],
13
+ icons: { emoji: "⏳", nerd: "󱎫", text: "total" },
14
+ defaultStyle: { fg: "yellow", bg: "default", bold: false },
15
+ render({ ctx, renderWidget }) {
16
+ return renderWidget(formatElapsed(ctx.metrics.firstTimestampMs, Date.now()));
17
+ },
18
+ });
@@ -0,0 +1,17 @@
1
+ import { defineWidget } from "../types.js";
2
+
3
+ export const UserMessagesWidget = defineWidget({
4
+ type: "user-messages",
5
+ label: "User Messages",
6
+ category: "Session",
7
+ description: "User message count",
8
+ dependencies: ["metrics"],
9
+ baseOptions: ["raw", "hideWhenZero", "icon", "text"],
10
+ baseOptionDefaults: {},
11
+ properties: [],
12
+ icons: { emoji: "👤", nerd: "", text: "user" },
13
+ defaultStyle: { fg: "blue", bg: "default", bold: false },
14
+ render({ ctx, renderWidget }) {
15
+ return renderWidget(`${ctx.metrics.userMessages}`);
16
+ },
17
+ });
@@ -0,0 +1,26 @@
1
+ import { cloneSettings } from "../config.js";
2
+ import type { StatuslineConfig, StatuslineSettings } from "../types.js";
3
+ import { registry } from "./registry.js";
4
+ import type { Widget } from "./types.js";
5
+
6
+ export class WidgetStore {
7
+ constructor(
8
+ public settings: StatuslineSettings,
9
+ public lines: Widget[][],
10
+ ) {}
11
+
12
+ static fromConfig(config: StatuslineConfig): WidgetStore {
13
+ const { lines, ...settings } = config;
14
+ return new WidgetStore(
15
+ cloneSettings(settings),
16
+ lines.map((line) => line.map((entry) => registry.hydrateWidget(entry))),
17
+ );
18
+ }
19
+
20
+ toConfig(): StatuslineConfig {
21
+ return {
22
+ ...cloneSettings(this.settings),
23
+ lines: this.lines.map((line) => line.map((widget) => widget.toEntry())),
24
+ };
25
+ }
26
+ }
@@ -0,0 +1,55 @@
1
+ import { defineWidget } from "../types.js";
2
+
3
+ export const CacheHitRateWidget = defineWidget({
4
+ type: "cache-hit-rate",
5
+ label: "Cache Hit Rate",
6
+ category: "Tokens",
7
+ description: "Session or latest turn cache hit percentage",
8
+ dependencies: ["metrics", "turnMetrics"],
9
+ baseOptions: ["raw", "hideWhenZero", "icon"],
10
+ baseOptionDefaults: {},
11
+ properties: [
12
+ {
13
+ id: "cacheHitSource",
14
+ label: "Source",
15
+ kind: "choice",
16
+ description: "Cache hit rate source",
17
+ default: "session",
18
+ options: {
19
+ choices: [
20
+ { id: "session", label: "Session", list: "Session" },
21
+ { id: "turn", label: "Last Turn", list: "Turn" },
22
+ ],
23
+ showInWidgets: true,
24
+ showInColors: true,
25
+ listProperty: "source",
26
+ },
27
+ },
28
+ {
29
+ id: "style",
30
+ label: "Style",
31
+ kind: "choice",
32
+ description: "Display style",
33
+ default: "default",
34
+ options: {
35
+ choices: [
36
+ { id: "default", label: "Default", list: "default" },
37
+ { id: "compact", label: "Compact", list: "compact" },
38
+ ],
39
+ showInWidgets: true,
40
+ showInColors: false,
41
+ listProperty: "style",
42
+ },
43
+ },
44
+ ],
45
+ icons: { emoji: "🎯", nerd: "󰓎", text: "cache hit" },
46
+ defaultStyle: { fg: "cyan", bg: "default", bold: false },
47
+ render({ ctx, options, renderWidget }) {
48
+ const source = options.cacheHitSource === "turn" ? ctx.turnMetrics : ctx.metrics;
49
+ const { inputTokens, cacheReadTokens, cacheWriteTokens } = source;
50
+ const promptTokens = inputTokens + cacheReadTokens + cacheWriteTokens;
51
+ const hitRate = promptTokens > 0 ? (cacheReadTokens / promptTokens) * 100 : 0;
52
+ if (hitRate === 0 && options.hideWhenZero) return undefined;
53
+ return renderWidget(`${hitRate.toFixed(options.style === "compact" ? 0 : 1)}%`);
54
+ },
55
+ });
@@ -0,0 +1,18 @@
1
+ import { defineWidget } from "../types.js";
2
+ import { formatTokenCount, tokenFormatStyleProperty } from "../utils/token-format.js";
3
+
4
+ export const CacheReadWidget = defineWidget({
5
+ type: "cache-read",
6
+ label: "Cache Read",
7
+ category: "Tokens",
8
+ description: "Cache read token total",
9
+ dependencies: ["metrics"],
10
+ baseOptions: ["raw", "hideWhenZero", "icon"],
11
+ baseOptionDefaults: {},
12
+ properties: [tokenFormatStyleProperty()],
13
+ icons: { emoji: "📖", nerd: "󰆼", text: "cache read" },
14
+ defaultStyle: { fg: "cyan", bg: "default", bold: false },
15
+ render({ ctx, options, renderWidget }) {
16
+ return renderWidget(formatTokenCount(ctx.metrics.cacheReadTokens, options.tokenFormatStyle));
17
+ },
18
+ });
@@ -0,0 +1,18 @@
1
+ import { defineWidget } from "../types.js";
2
+ import { formatTokenCount, tokenFormatStyleProperty } from "../utils/token-format.js";
3
+
4
+ export const CacheWriteWidget = defineWidget({
5
+ type: "cache-write",
6
+ label: "Cache Write",
7
+ category: "Tokens",
8
+ description: "Cache write token total",
9
+ dependencies: ["metrics"],
10
+ baseOptions: ["raw", "hideWhenZero", "icon"],
11
+ baseOptionDefaults: {},
12
+ properties: [tokenFormatStyleProperty()],
13
+ icons: { emoji: "✍️", nerd: "󰆼", text: "cache write" },
14
+ defaultStyle: { fg: "cyan", bg: "default", bold: false },
15
+ render({ ctx, options, renderWidget }) {
16
+ return renderWidget(formatTokenCount(ctx.metrics.cacheWriteTokens, options.tokenFormatStyle));
17
+ },
18
+ });
@@ -0,0 +1,108 @@
1
+ import { defineWidget } from "../types.js";
2
+ import { contextColorProperties, contextColors, contextPercent } from "../utils/context.js";
3
+ import {
4
+ formatTokenCount,
5
+ tokenFormatStyleProperty,
6
+ type TokenFormatStyle,
7
+ } from "../utils/token-format.js";
8
+
9
+ const CONTEXT_BAR_MODES = {
10
+ default: {
11
+ label: "Default bar",
12
+ list: "Default bar",
13
+ width: 32,
14
+ filled: "█",
15
+ empty: "░",
16
+ bracketed: true,
17
+ showUsage: true,
18
+ },
19
+ short: {
20
+ label: "Short bar",
21
+ list: "Short bar",
22
+ width: 10,
23
+ filled: "▓",
24
+ empty: "░",
25
+ bracketed: false,
26
+ showUsage: true,
27
+ },
28
+ "short-only": {
29
+ label: "Short bar only",
30
+ list: "Short bar only",
31
+ width: 10,
32
+ filled: "▓",
33
+ empty: "░",
34
+ bracketed: false,
35
+ showUsage: false,
36
+ },
37
+ medium: {
38
+ label: "Medium bar",
39
+ list: "Medium bar",
40
+ width: 16,
41
+ filled: "█",
42
+ empty: "░",
43
+ bracketed: true,
44
+ showUsage: true,
45
+ },
46
+ } as const;
47
+
48
+ type ContextBarMode = keyof typeof CONTEXT_BAR_MODES;
49
+
50
+ const CONTEXT_BAR_MODE_VALUES = Object.keys(CONTEXT_BAR_MODES) as ContextBarMode[];
51
+
52
+ const contextBarModeProperty = {
53
+ id: "contextBarMode",
54
+ label: "Display",
55
+ kind: "choice",
56
+ description: "Context bar display mode",
57
+ default: "default",
58
+ options: {
59
+ choices: CONTEXT_BAR_MODE_VALUES.map((mode) => ({
60
+ id: mode,
61
+ label: CONTEXT_BAR_MODES[mode].label,
62
+ list: CONTEXT_BAR_MODES[mode].list,
63
+ })),
64
+ showInWidgets: true,
65
+ showInColors: false,
66
+ listProperty: "display",
67
+ },
68
+ } as const;
69
+
70
+ export const ContextBarWidget = defineWidget({
71
+ type: "context-bar",
72
+ label: "Context Bar",
73
+ category: "Tokens",
74
+ description: "Progress bar for context usage",
75
+ dependencies: ["contextTokens", "contextMaxTokens"],
76
+ baseOptions: ["raw", "hideWhenEmpty", "icon", "text"],
77
+ baseOptionDefaults: { text: "" },
78
+ properties: [tokenFormatStyleProperty(), contextBarModeProperty, ...contextColorProperties()],
79
+ icons: { emoji: "📊", nerd: "󰍛", text: "Context:" },
80
+ defaultStyle: { fg: "blue", bg: "default", bold: false },
81
+ render({ ctx, options, renderWidget }) {
82
+ const { contextBarMode, tokenFormatStyle } = options;
83
+ return renderWidget(
84
+ contextBar(ctx.contextTokens, ctx.contextMaxTokens, contextBarMode, tokenFormatStyle),
85
+ {
86
+ ...contextColors(options, ctx.contextTokens, ctx.contextMaxTokens),
87
+ },
88
+ );
89
+ },
90
+ });
91
+
92
+ function contextBar(
93
+ tokens: number | undefined,
94
+ maxTokens: number | undefined,
95
+ contextBarMode: ContextBarMode,
96
+ tokenFormatStyle: TokenFormatStyle,
97
+ ) {
98
+ const percent = contextPercent(tokens, maxTokens);
99
+ if (tokens === undefined || maxTokens === undefined || percent === undefined) return "?";
100
+
101
+ const usage = `${formatTokenCount(tokens, tokenFormatStyle)}/${formatTokenCount(maxTokens, tokenFormatStyle)} (${Math.round(percent)}%)`;
102
+ const mode = CONTEXT_BAR_MODES[contextBarMode];
103
+ const filledWidth = Math.round((percent / 100) * mode.width);
104
+ const meter = `${mode.filled.repeat(filledWidth)}${mode.empty.repeat(Math.max(0, mode.width - filledWidth))}`;
105
+ const bar = mode.bracketed ? `[${meter}]` : meter;
106
+
107
+ return mode.showUsage ? `${bar} ${usage}` : bar;
108
+ }
@@ -0,0 +1,26 @@
1
+ import { defineWidget } from "../types.js";
2
+ import { contextColorProperties, contextColors } from "../utils/context.js";
3
+ import { formatTokenCount, tokenFormatStyleProperty } from "../utils/token-format.js";
4
+
5
+ export const ContextLengthWidget = defineWidget({
6
+ type: "context-length",
7
+ label: "Context Length",
8
+ category: "Tokens",
9
+ description: "Current context token count",
10
+ dependencies: ["contextTokens", "contextMaxTokens"],
11
+ baseOptions: ["raw", "hideWhenZero", "icon"],
12
+ baseOptionDefaults: {},
13
+ properties: [tokenFormatStyleProperty(), ...contextColorProperties()],
14
+ icons: { emoji: "📏", nerd: "󰍛", text: "ctx len" },
15
+ defaultStyle: { fg: "brightBlack", bg: "default", bold: false },
16
+ render({ ctx, options, renderWidget }) {
17
+ return renderWidget(
18
+ ctx.contextTokens === undefined
19
+ ? "?"
20
+ : formatTokenCount(ctx.contextTokens, options.tokenFormatStyle),
21
+ {
22
+ ...contextColors(options, ctx.contextTokens, ctx.contextMaxTokens),
23
+ },
24
+ );
25
+ },
26
+ });
@@ -0,0 +1,29 @@
1
+ import { defineWidget } from "../types.js";
2
+ import { contextColors, contextColorProperties, contextPercent } from "../utils/context.js";
3
+ import { formatCount } from "../utils/token-format.js";
4
+
5
+ export const ContextRemainingWidget = defineWidget({
6
+ type: "context-remaining",
7
+ label: "Context Remaining",
8
+ category: "Tokens",
9
+ description: "Remaining context percentage",
10
+ dependencies: ["contextTokens", "contextMaxTokens"],
11
+ baseOptions: ["raw", "icon"],
12
+ baseOptionDefaults: {},
13
+ properties: contextColorProperties(),
14
+ icons: { emoji: "🧩", nerd: "󰍛", text: "ctx left" },
15
+ defaultStyle: { fg: "green", bg: "default", bold: false },
16
+ render({ ctx, options, renderWidget }) {
17
+ return renderWidget(contextRemaining(ctx.contextTokens, ctx.contextMaxTokens), {
18
+ ...contextColors(options, ctx.contextTokens, ctx.contextMaxTokens),
19
+ });
20
+ },
21
+ });
22
+
23
+ function contextRemaining(tokens: number | undefined, maxTokens: number | undefined) {
24
+ if (tokens === undefined) return "?";
25
+ if (maxTokens === undefined || maxTokens <= 0) return `${formatCount(tokens)} ctx`;
26
+
27
+ const percent = 100 - (contextPercent(tokens, maxTokens) ?? 0);
28
+ return `${percent.toFixed(1).replace(/\.0$/, "")}%`;
29
+ }
@@ -0,0 +1,29 @@
1
+ import { defineWidget } from "../types.js";
2
+ import { contextColors, contextColorProperties, contextPercent } from "../utils/context.js";
3
+ import { formatCount } from "../utils/token-format.js";
4
+
5
+ export const ContextWidget = defineWidget({
6
+ type: "context",
7
+ label: "Context %",
8
+ category: "Tokens",
9
+ description: "Current context usage percentage",
10
+ dependencies: ["contextTokens", "contextMaxTokens"],
11
+ baseOptions: ["raw", "icon"],
12
+ baseOptionDefaults: {},
13
+ properties: contextColorProperties(),
14
+ icons: { emoji: "🧩", nerd: "󰍛", text: "ctx" },
15
+ defaultStyle: { fg: "blue", bg: "default", bold: false },
16
+ render({ ctx, options, renderWidget }) {
17
+ return renderWidget(contextUsage(ctx.contextTokens, ctx.contextMaxTokens), {
18
+ ...contextColors(options, ctx.contextTokens, ctx.contextMaxTokens),
19
+ });
20
+ },
21
+ });
22
+
23
+ function contextUsage(tokens: number | undefined, maxTokens: number | undefined) {
24
+ if (tokens === undefined) return "?";
25
+ if (maxTokens === undefined || maxTokens <= 0) return `${formatCount(tokens)} ctx`;
26
+
27
+ const percent = contextPercent(tokens, maxTokens) ?? 0;
28
+ return `${percent.toFixed(1).replace(/\.0$/, "")}%`;
29
+ }
@@ -0,0 +1,51 @@
1
+ import { defineWidget } from "../types.js";
2
+
3
+ export const CostWidget = defineWidget({
4
+ type: "cost",
5
+ label: "Session Cost",
6
+ category: "Tokens",
7
+ description: "Estimated session cost",
8
+ dependencies: ["metrics", "usingSubscription"],
9
+ baseOptions: ["raw", "icon"],
10
+ baseOptionDefaults: {},
11
+ properties: [
12
+ {
13
+ id: "costFormatStyle",
14
+ label: "Cost format",
15
+ kind: "choice",
16
+ description: "Session cost display format",
17
+ default: "default",
18
+ options: {
19
+ choices: [
20
+ { id: "default", label: "Default", list: "Default" },
21
+ { id: "compact", label: "Compact", list: "Compact" },
22
+ ],
23
+ showInWidgets: true,
24
+ showInColors: false,
25
+ listProperty: "format",
26
+ },
27
+ },
28
+ {
29
+ id: "showSubscription",
30
+ label: "Show subscription",
31
+ kind: "boolean",
32
+ description: "Append subscription marker when subscription usage is active",
33
+ default: false,
34
+ options: {
35
+ label: "show-sub",
36
+ showInWidgets: true,
37
+ showInColors: false,
38
+ },
39
+ },
40
+ ],
41
+ icons: { emoji: "💸", nerd: "󱐋", text: "cost" },
42
+ defaultStyle: { fg: "green", bg: "default", bold: false },
43
+ render({ ctx, options, renderWidget }) {
44
+ const cost =
45
+ options.costFormatStyle === "compact"
46
+ ? `$${ctx.metrics.costUsd.toFixed(3)}`
47
+ : `$${ctx.metrics.costUsd.toFixed(ctx.metrics.costUsd < 1 ? 4 : 2)}`;
48
+ const suffix = options.showSubscription && ctx.usingSubscription ? " (sub)" : "";
49
+ return renderWidget(`${cost}${suffix}`);
50
+ },
51
+ });
@@ -0,0 +1,25 @@
1
+ import { defineWidget } from "../types.js";
2
+ import { formatTokenSpeed, tokenFormatStyleProperty } from "../utils/token-format.js";
3
+
4
+ export const InputSpeedWidget = defineWidget({
5
+ type: "input-speed",
6
+ label: "Input Speed",
7
+ category: "Tokens",
8
+ description: "Average input tokens per minute",
9
+ dependencies: ["metrics"],
10
+ baseOptions: ["raw", "hideWhenZero", "icon"],
11
+ baseOptionDefaults: {},
12
+ properties: [tokenFormatStyleProperty()],
13
+ icons: { emoji: "⏫", nerd: "", text: "in/min" },
14
+ defaultStyle: { fg: "brightMagenta", bg: "default", bold: false },
15
+ render({ ctx, options, renderWidget }) {
16
+ return renderWidget(
17
+ formatTokenSpeed(
18
+ ctx.metrics.inputTokens,
19
+ ctx.metrics.firstTimestampMs,
20
+ ctx.metrics.lastTimestampMs,
21
+ options.tokenFormatStyle,
22
+ ),
23
+ );
24
+ },
25
+ });
@@ -0,0 +1,18 @@
1
+ import { defineWidget } from "../types.js";
2
+ import { formatTokenCount, tokenFormatStyleProperty } from "../utils/token-format.js";
3
+
4
+ export const InputTokensWidget = defineWidget({
5
+ type: "input-tokens",
6
+ label: "Input Tokens",
7
+ category: "Tokens",
8
+ description: "Input token total",
9
+ dependencies: ["metrics"],
10
+ baseOptions: ["raw", "hideWhenZero", "icon"],
11
+ baseOptionDefaults: {},
12
+ properties: [tokenFormatStyleProperty()],
13
+ icons: { emoji: "⬆️", nerd: "󰌌", text: "in" },
14
+ defaultStyle: { fg: "blue", bg: "default", bold: false },
15
+ render({ ctx, options, renderWidget }) {
16
+ return renderWidget(formatTokenCount(ctx.metrics.inputTokens, options.tokenFormatStyle));
17
+ },
18
+ });
@@ -0,0 +1,25 @@
1
+ import { defineWidget } from "../types.js";
2
+ import { formatTokenSpeed, tokenFormatStyleProperty } from "../utils/token-format.js";
3
+
4
+ export const OutputSpeedWidget = defineWidget({
5
+ type: "output-speed",
6
+ label: "Output Speed",
7
+ category: "Tokens",
8
+ description: "Average output tokens per minute",
9
+ dependencies: ["metrics"],
10
+ baseOptions: ["raw", "hideWhenZero", "icon"],
11
+ baseOptionDefaults: {},
12
+ properties: [tokenFormatStyleProperty()],
13
+ icons: { emoji: "⏬", nerd: "", text: "out/min" },
14
+ defaultStyle: { fg: "brightCyan", bg: "default", bold: false },
15
+ render({ ctx, options, renderWidget }) {
16
+ return renderWidget(
17
+ formatTokenSpeed(
18
+ ctx.metrics.outputTokens,
19
+ ctx.metrics.firstTimestampMs,
20
+ ctx.metrics.lastTimestampMs,
21
+ options.tokenFormatStyle,
22
+ ),
23
+ );
24
+ },
25
+ });