@agi-fans/dsh-tui 0.4.0 → 0.5.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.
Files changed (242) hide show
  1. package/README.md +43 -7
  2. package/lib/CHANGELOG.md +33 -1
  3. package/lib/{box.d.ts → chrome/box.d.ts} +2 -0
  4. package/lib/{box.js → chrome/box.js} +26 -16
  5. package/lib/chrome/box.js.map +1 -0
  6. package/lib/chrome/command-output.js.map +1 -0
  7. package/lib/chrome/diff-render.d.ts +39 -0
  8. package/lib/chrome/diff-render.js +274 -0
  9. package/lib/chrome/diff-render.js.map +1 -0
  10. package/lib/chrome/markdown.d.ts +17 -0
  11. package/lib/chrome/markdown.js +537 -0
  12. package/lib/chrome/markdown.js.map +1 -0
  13. package/lib/chrome/path-mentions.js.map +1 -0
  14. package/lib/chrome/relative-time.js.map +1 -0
  15. package/lib/chrome/renderer.js.map +1 -0
  16. package/lib/chrome/status-config.d.ts +70 -0
  17. package/lib/chrome/status-config.js +147 -0
  18. package/lib/chrome/status-config.js.map +1 -0
  19. package/lib/{status-line.d.ts → chrome/status-line.d.ts} +20 -2
  20. package/lib/{status-line.js → chrome/status-line.js} +175 -113
  21. package/lib/chrome/status-line.js.map +1 -0
  22. package/lib/chrome/style.js.map +1 -0
  23. package/lib/{theme.d.ts → chrome/theme.d.ts} +5 -3
  24. package/lib/chrome/theme.js +708 -0
  25. package/lib/chrome/theme.js.map +1 -0
  26. package/lib/{tool-renderers.js → chrome/tool-renderers.js} +25 -20
  27. package/lib/chrome/tool-renderers.js.map +1 -0
  28. package/lib/chrome/tools-list.js.map +1 -0
  29. package/lib/{welcome-tips.js → chrome/welcome-tips.js} +4 -2
  30. package/lib/chrome/welcome-tips.js.map +1 -0
  31. package/lib/chrome/width.js.map +1 -0
  32. package/lib/{command-auth.js → commands/auth.js} +2 -2
  33. package/lib/commands/auth.js.map +1 -0
  34. package/lib/{command-export.js → commands/export.js} +3 -3
  35. package/lib/commands/export.js.map +1 -0
  36. package/lib/{command-integrations.js → commands/integrations.js} +2 -2
  37. package/lib/commands/integrations.js.map +1 -0
  38. package/lib/{command-loop.d.ts → commands/loop.d.ts} +1 -1
  39. package/lib/{command-loop.js → commands/loop.js} +2 -2
  40. package/lib/commands/loop.js.map +1 -0
  41. package/lib/{command-model.js → commands/model.js} +2 -2
  42. package/lib/commands/model.js.map +1 -0
  43. package/lib/{command-permission.js → commands/permission.js} +13 -8
  44. package/lib/commands/permission.js.map +1 -0
  45. package/lib/{command-registration.js → commands/registration.js} +1 -1
  46. package/lib/commands/registration.js.map +1 -0
  47. package/lib/commands/session-configuration.d.ts +5 -0
  48. package/lib/commands/session-configuration.js +108 -0
  49. package/lib/commands/session-configuration.js.map +1 -0
  50. package/lib/{command-session.js → commands/session.js} +10 -7
  51. package/lib/commands/session.js.map +1 -0
  52. package/lib/{command-steer.js → commands/steer.js} +2 -2
  53. package/lib/commands/steer.js.map +1 -0
  54. package/lib/definition.d.ts +6 -1
  55. package/lib/definition.js.map +1 -1
  56. package/lib/index.d.ts +1 -1
  57. package/lib/index.js +1 -1
  58. package/lib/index.js.map +1 -1
  59. package/lib/input/clipboard.js.map +1 -0
  60. package/lib/input/editor.js.map +1 -0
  61. package/lib/input/external-editor.js.map +1 -0
  62. package/lib/{image-paste.d.ts → input/image-paste.d.ts} +1 -1
  63. package/lib/input/image-paste.js.map +1 -0
  64. package/lib/input/keybindings-config.js.map +1 -0
  65. package/lib/input/keys.js.map +1 -0
  66. package/lib/runtime/agent-profile.d.ts +10 -0
  67. package/lib/runtime/agent-profile.js +9 -0
  68. package/lib/runtime/agent-profile.js.map +1 -0
  69. package/lib/{human-interaction.js → runtime/human-interaction.js} +1 -1
  70. package/lib/runtime/human-interaction.js.map +1 -0
  71. package/lib/{provider-local.d.ts → runtime/provider-local.d.ts} +9 -9
  72. package/lib/{provider-local.js → runtime/provider-local.js} +30 -26
  73. package/lib/runtime/provider-local.js.map +1 -0
  74. package/lib/runtime/runner.js.map +1 -0
  75. package/lib/{session-runtime.d.ts → runtime/session-runtime.d.ts} +2 -2
  76. package/lib/{session-runtime.js → runtime/session-runtime.js} +3 -3
  77. package/lib/runtime/session-runtime.js.map +1 -0
  78. package/lib/{startup-notices.js → runtime/startup-notices.js} +6 -6
  79. package/lib/runtime/startup-notices.js.map +1 -0
  80. package/lib/{tool-presentation.d.ts → runtime/tool-presentation.d.ts} +1 -1
  81. package/lib/runtime/tool-presentation.js.map +1 -0
  82. package/lib/{interaction-adapter.d.ts → session/interaction-adapter.d.ts} +1 -1
  83. package/lib/session/interaction-adapter.js.map +1 -0
  84. package/lib/{package-metadata.js → session/package-metadata.js} +1 -1
  85. package/lib/session/package-metadata.js.map +1 -0
  86. package/lib/session/project-context.js.map +1 -0
  87. package/lib/session/project-file-search.js.map +1 -0
  88. package/lib/session/release-notes.js.map +1 -0
  89. package/lib/session/session-configuration.d.ts +25 -0
  90. package/lib/session/session-configuration.js +46 -0
  91. package/lib/session/session-configuration.js.map +1 -0
  92. package/lib/{session-controller.d.ts → session/session-controller.d.ts} +13 -2
  93. package/lib/{session-controller.js → session/session-controller.js} +181 -11
  94. package/lib/session/session-controller.js.map +1 -0
  95. package/lib/{tui-settings.d.ts → session/tui-settings.d.ts} +2 -2
  96. package/lib/session/tui-settings.js +42 -0
  97. package/lib/session/tui-settings.js.map +1 -0
  98. package/lib/session/update-check.js.map +1 -0
  99. package/lib/{autocomplete.d.ts → views/autocomplete.d.ts} +1 -1
  100. package/lib/{autocomplete.js → views/autocomplete.js} +2 -2
  101. package/lib/views/autocomplete.js.map +1 -0
  102. package/lib/{copy-selector.d.ts → views/copy-selector.d.ts} +2 -2
  103. package/lib/{copy-selector.js → views/copy-selector.js} +2 -2
  104. package/lib/views/copy-selector.js.map +1 -0
  105. package/lib/views/copy-targets.js.map +1 -0
  106. package/lib/{event-views.d.ts → views/event-views.d.ts} +8 -8
  107. package/lib/{event-views.js → views/event-views.js} +64 -25
  108. package/lib/views/event-views.js.map +1 -0
  109. package/lib/{history-search.d.ts → views/history-search.d.ts} +2 -2
  110. package/lib/{history-search.js → views/history-search.js} +3 -3
  111. package/lib/views/history-search.js.map +1 -0
  112. package/lib/views/history-store.js.map +1 -0
  113. package/lib/{hotkeys.d.ts → views/hotkeys.d.ts} +1 -1
  114. package/lib/{hotkeys.js → views/hotkeys.js} +1 -1
  115. package/lib/views/hotkeys.js.map +1 -0
  116. package/lib/{path-complete.d.ts → views/path-complete.d.ts} +2 -2
  117. package/lib/{path-complete.js → views/path-complete.js} +1 -1
  118. package/lib/views/path-complete.js.map +1 -0
  119. package/lib/{prompt-selector.d.ts → views/prompt-selector.d.ts} +2 -2
  120. package/lib/{prompt-selector.js → views/prompt-selector.js} +4 -4
  121. package/lib/views/prompt-selector.js.map +1 -0
  122. package/lib/{settings-list.d.ts → views/settings-list.d.ts} +11 -6
  123. package/lib/{settings-list.js → views/settings-list.js} +217 -85
  124. package/lib/views/settings-list.js.map +1 -0
  125. package/lib/views/transcript-export.js.map +1 -0
  126. package/package.json +64 -54
  127. package/lib/autocomplete.js.map +0 -1
  128. package/lib/box.js.map +0 -1
  129. package/lib/clipboard.js.map +0 -1
  130. package/lib/command-auth.js.map +0 -1
  131. package/lib/command-export.js.map +0 -1
  132. package/lib/command-integrations.js.map +0 -1
  133. package/lib/command-loop.js.map +0 -1
  134. package/lib/command-model.js.map +0 -1
  135. package/lib/command-output.js.map +0 -1
  136. package/lib/command-permission.js.map +0 -1
  137. package/lib/command-registration.js.map +0 -1
  138. package/lib/command-session.js.map +0 -1
  139. package/lib/command-steer.js.map +0 -1
  140. package/lib/copy-selector.js.map +0 -1
  141. package/lib/copy-targets.js.map +0 -1
  142. package/lib/editor.js.map +0 -1
  143. package/lib/event-views.js.map +0 -1
  144. package/lib/external-editor.js.map +0 -1
  145. package/lib/history-search.js.map +0 -1
  146. package/lib/history-store.js.map +0 -1
  147. package/lib/hotkeys.js.map +0 -1
  148. package/lib/human-interaction.js.map +0 -1
  149. package/lib/image-paste.js.map +0 -1
  150. package/lib/interaction-adapter.js.map +0 -1
  151. package/lib/keybindings-config.js.map +0 -1
  152. package/lib/keys.js.map +0 -1
  153. package/lib/markdown.d.ts +0 -13
  154. package/lib/markdown.js +0 -453
  155. package/lib/markdown.js.map +0 -1
  156. package/lib/package-metadata.js.map +0 -1
  157. package/lib/path-complete.js.map +0 -1
  158. package/lib/path-mentions.js.map +0 -1
  159. package/lib/project-context.js.map +0 -1
  160. package/lib/project-file-search.js.map +0 -1
  161. package/lib/prompt-selector.js.map +0 -1
  162. package/lib/provider-local.js.map +0 -1
  163. package/lib/relative-time.js.map +0 -1
  164. package/lib/release-notes.js.map +0 -1
  165. package/lib/renderer.js.map +0 -1
  166. package/lib/runner.js.map +0 -1
  167. package/lib/session-controller.js.map +0 -1
  168. package/lib/session-runtime.js.map +0 -1
  169. package/lib/settings-list.js.map +0 -1
  170. package/lib/startup-notices.js.map +0 -1
  171. package/lib/status-config.d.ts +0 -32
  172. package/lib/status-config.js +0 -52
  173. package/lib/status-config.js.map +0 -1
  174. package/lib/status-line.js.map +0 -1
  175. package/lib/style.js.map +0 -1
  176. package/lib/theme.js +0 -317
  177. package/lib/theme.js.map +0 -1
  178. package/lib/tool-presentation.js.map +0 -1
  179. package/lib/tool-renderers.js.map +0 -1
  180. package/lib/tools-list.js.map +0 -1
  181. package/lib/transcript-export.js.map +0 -1
  182. package/lib/tui-settings.js +0 -26
  183. package/lib/tui-settings.js.map +0 -1
  184. package/lib/update-check.js.map +0 -1
  185. package/lib/welcome-tips.js.map +0 -1
  186. package/lib/width.js.map +0 -1
  187. /package/lib/{command-output.d.ts → chrome/command-output.d.ts} +0 -0
  188. /package/lib/{command-output.js → chrome/command-output.js} +0 -0
  189. /package/lib/{path-mentions.d.ts → chrome/path-mentions.d.ts} +0 -0
  190. /package/lib/{path-mentions.js → chrome/path-mentions.js} +0 -0
  191. /package/lib/{relative-time.d.ts → chrome/relative-time.d.ts} +0 -0
  192. /package/lib/{relative-time.js → chrome/relative-time.js} +0 -0
  193. /package/lib/{renderer.d.ts → chrome/renderer.d.ts} +0 -0
  194. /package/lib/{renderer.js → chrome/renderer.js} +0 -0
  195. /package/lib/{style.d.ts → chrome/style.d.ts} +0 -0
  196. /package/lib/{style.js → chrome/style.js} +0 -0
  197. /package/lib/{tool-renderers.d.ts → chrome/tool-renderers.d.ts} +0 -0
  198. /package/lib/{tools-list.d.ts → chrome/tools-list.d.ts} +0 -0
  199. /package/lib/{tools-list.js → chrome/tools-list.js} +0 -0
  200. /package/lib/{welcome-tips.d.ts → chrome/welcome-tips.d.ts} +0 -0
  201. /package/lib/{width.d.ts → chrome/width.d.ts} +0 -0
  202. /package/lib/{width.js → chrome/width.js} +0 -0
  203. /package/lib/{command-auth.d.ts → commands/auth.d.ts} +0 -0
  204. /package/lib/{command-export.d.ts → commands/export.d.ts} +0 -0
  205. /package/lib/{command-integrations.d.ts → commands/integrations.d.ts} +0 -0
  206. /package/lib/{command-model.d.ts → commands/model.d.ts} +0 -0
  207. /package/lib/{command-permission.d.ts → commands/permission.d.ts} +0 -0
  208. /package/lib/{command-registration.d.ts → commands/registration.d.ts} +0 -0
  209. /package/lib/{command-session.d.ts → commands/session.d.ts} +0 -0
  210. /package/lib/{command-steer.d.ts → commands/steer.d.ts} +0 -0
  211. /package/lib/{clipboard.d.ts → input/clipboard.d.ts} +0 -0
  212. /package/lib/{clipboard.js → input/clipboard.js} +0 -0
  213. /package/lib/{editor.d.ts → input/editor.d.ts} +0 -0
  214. /package/lib/{editor.js → input/editor.js} +0 -0
  215. /package/lib/{external-editor.d.ts → input/external-editor.d.ts} +0 -0
  216. /package/lib/{external-editor.js → input/external-editor.js} +0 -0
  217. /package/lib/{image-paste.js → input/image-paste.js} +0 -0
  218. /package/lib/{keybindings-config.d.ts → input/keybindings-config.d.ts} +0 -0
  219. /package/lib/{keybindings-config.js → input/keybindings-config.js} +0 -0
  220. /package/lib/{keys.d.ts → input/keys.d.ts} +0 -0
  221. /package/lib/{keys.js → input/keys.js} +0 -0
  222. /package/lib/{human-interaction.d.ts → runtime/human-interaction.d.ts} +0 -0
  223. /package/lib/{runner.d.ts → runtime/runner.d.ts} +0 -0
  224. /package/lib/{runner.js → runtime/runner.js} +0 -0
  225. /package/lib/{startup-notices.d.ts → runtime/startup-notices.d.ts} +0 -0
  226. /package/lib/{tool-presentation.js → runtime/tool-presentation.js} +0 -0
  227. /package/lib/{interaction-adapter.js → session/interaction-adapter.js} +0 -0
  228. /package/lib/{package-metadata.d.ts → session/package-metadata.d.ts} +0 -0
  229. /package/lib/{project-context.d.ts → session/project-context.d.ts} +0 -0
  230. /package/lib/{project-context.js → session/project-context.js} +0 -0
  231. /package/lib/{project-file-search.d.ts → session/project-file-search.d.ts} +0 -0
  232. /package/lib/{project-file-search.js → session/project-file-search.js} +0 -0
  233. /package/lib/{release-notes.d.ts → session/release-notes.d.ts} +0 -0
  234. /package/lib/{release-notes.js → session/release-notes.js} +0 -0
  235. /package/lib/{update-check.d.ts → session/update-check.d.ts} +0 -0
  236. /package/lib/{update-check.js → session/update-check.js} +0 -0
  237. /package/lib/{copy-targets.d.ts → views/copy-targets.d.ts} +0 -0
  238. /package/lib/{copy-targets.js → views/copy-targets.js} +0 -0
  239. /package/lib/{history-store.d.ts → views/history-store.d.ts} +0 -0
  240. /package/lib/{history-store.js → views/history-store.js} +0 -0
  241. /package/lib/{transcript-export.d.ts → views/transcript-export.d.ts} +0 -0
  242. /package/lib/{transcript-export.js → views/transcript-export.js} +0 -0
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Status-line customization shared by settings, persistence, and rendering.
3
+ * @module @agi-fans/dsh-tui/status-config
4
+ */
5
+ /** Stable telemetry groups users can show, hide, and reorder. */
6
+ export declare const STATUS_GROUP_IDS: readonly ["context", "cache", "tokens", "speed", "durations", "counts"];
7
+ export type StatusGroupId = (typeof STATUS_GROUP_IDS)[number];
8
+ /** First-line pieces: model, reasoning effort, workspace path, and Git branch. */
9
+ export declare const STATUS_META_IDS: readonly ["model", "effort", "path", "git"];
10
+ export type StatusMetaId = (typeof STATUS_META_IDS)[number];
11
+ /** Every independently placed footer item. */
12
+ export declare const STATUS_ITEM_IDS: readonly ["model", "effort", "path", "git", "context", "cache", "tokens", "speed", "durations", "counts"];
13
+ export type StatusItemId = (typeof STATUS_ITEM_IDS)[number];
14
+ export declare const STATUS_LABEL_STYLES: readonly ["compact", "full"];
15
+ export type StatusLabelStyle = (typeof STATUS_LABEL_STYLES)[number];
16
+ /**
17
+ * Color slots: one per preview item, plus legacy `metrics` as a fallback for
18
+ * telemetry groups that do not have their own color yet.
19
+ */
20
+ export declare const STATUS_COLOR_SLOTS: readonly ["model", "effort", "path", "git", "context", "cache", "tokens", "speed", "durations", "counts", "metrics"];
21
+ export type StatusColorSlot = (typeof STATUS_COLOR_SLOTS)[number];
22
+ /** Theme tokens offered for a status item. `default` keeps the built-in hierarchy. */
23
+ export declare const STATUS_COLOR_TOKENS: readonly ["default", "text", "muted", "accent", "success", "warning", "border", "label"];
24
+ export type StatusColorToken = (typeof STATUS_COLOR_TOKENS)[number];
25
+ /** Footer column an item can occupy. */
26
+ export declare const STATUS_SIDES: readonly ["left", "right"];
27
+ export type StatusSide = (typeof STATUS_SIDES)[number];
28
+ export declare const DEFAULT_STATUS_SIDES: Record<StatusItemId, StatusSide>;
29
+ /** Per-item color choices. Omitted items keep their built-in tone. */
30
+ export type StatusBarColors = Partial<Record<StatusColorSlot, StatusColorToken>>;
31
+ /** User-owned status-line layout. Missing `groups`/`meta` are hidden; `order` retains their slots. */
32
+ export interface StatusBarConfig {
33
+ enabled: boolean;
34
+ labels: StatusLabelStyle;
35
+ groups: StatusGroupId[];
36
+ /** Complete visual order, including hidden groups. Absent in legacy settings. */
37
+ order?: StatusGroupId[];
38
+ /** Visible first-line items. Absent means every meta item is shown. */
39
+ meta?: StatusMetaId[];
40
+ /** Complete first-line order, including hidden meta items. */
41
+ metaOrder?: StatusMetaId[];
42
+ /** Optional per-item colors for the two-line footer. */
43
+ colors?: StatusBarColors;
44
+ /** Optional left/right column for each item. */
45
+ sides?: Partial<Record<StatusItemId, StatusSide>>;
46
+ }
47
+ /** Normalized layout always carries complete orders, colors, and columns. */
48
+ export interface ResolvedStatusBarConfig extends StatusBarConfig {
49
+ order: StatusGroupId[];
50
+ meta: StatusMetaId[];
51
+ metaOrder: StatusMetaId[];
52
+ colors: Required<StatusBarColors>;
53
+ sides: Record<StatusItemId, StatusSide>;
54
+ }
55
+ /** Legacy value accepted while existing settings documents migrate. */
56
+ export declare const STATUS_PRESETS: readonly ["minimal", "compact", "full"];
57
+ export type StatusPreset = (typeof STATUS_PRESETS)[number];
58
+ export declare const DEFAULT_STATUS_GROUPS: readonly StatusGroupId[];
59
+ export declare const DEFAULT_STATUS_META: readonly StatusMetaId[];
60
+ export declare const DEFAULT_STATUS_COLORS: Required<StatusBarColors>;
61
+ /** Return a fresh default so callers can safely replace its group list. */
62
+ export declare function defaultStatusBarConfig(): ResolvedStatusBarConfig;
63
+ export declare function isStatusMetaId(value: unknown): value is StatusMetaId;
64
+ export declare function isStatusItemId(value: unknown): value is StatusItemId;
65
+ export declare function itemSide(config: StatusBarConfig, id: StatusItemId): StatusSide;
66
+ /**
67
+ * Normalize persisted input at the single configuration seam. Invalid and
68
+ * duplicate groups disappear; legacy presets retain their previous meaning.
69
+ */
70
+ export declare function resolveStatusBarConfig(config?: Partial<StatusBarConfig>, legacyPreset?: StatusPreset): ResolvedStatusBarConfig;
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Status-line customization shared by settings, persistence, and rendering.
3
+ * @module @agi-fans/dsh-tui/status-config
4
+ */
5
+ /** Stable telemetry groups users can show, hide, and reorder. */
6
+ export const STATUS_GROUP_IDS = ['context', 'cache', 'tokens', 'speed', 'durations', 'counts'];
7
+ /** First-line pieces: model, reasoning effort, workspace path, and Git branch. */
8
+ export const STATUS_META_IDS = ['model', 'effort', 'path', 'git'];
9
+ /** Every independently placed footer item. */
10
+ export const STATUS_ITEM_IDS = [...STATUS_META_IDS, ...STATUS_GROUP_IDS];
11
+ export const STATUS_LABEL_STYLES = ['compact', 'full'];
12
+ /**
13
+ * Color slots: one per preview item, plus legacy `metrics` as a fallback for
14
+ * telemetry groups that do not have their own color yet.
15
+ */
16
+ export const STATUS_COLOR_SLOTS = [...STATUS_ITEM_IDS, 'metrics'];
17
+ /** Theme tokens offered for a status item. `default` keeps the built-in hierarchy. */
18
+ export const STATUS_COLOR_TOKENS = ['default', 'text', 'muted', 'accent', 'success', 'warning', 'border', 'label'];
19
+ /** Footer column an item can occupy. */
20
+ export const STATUS_SIDES = ['left', 'right'];
21
+ export const DEFAULT_STATUS_SIDES = {
22
+ model: 'left',
23
+ effort: 'left',
24
+ path: 'right',
25
+ git: 'right',
26
+ context: 'left',
27
+ cache: 'left',
28
+ tokens: 'left',
29
+ speed: 'left',
30
+ durations: 'right',
31
+ counts: 'right',
32
+ };
33
+ /** Legacy value accepted while existing settings documents migrate. */
34
+ export const STATUS_PRESETS = ['minimal', 'compact', 'full'];
35
+ export const DEFAULT_STATUS_GROUPS = STATUS_GROUP_IDS;
36
+ export const DEFAULT_STATUS_META = STATUS_META_IDS;
37
+ export const DEFAULT_STATUS_COLORS = {
38
+ model: 'default',
39
+ effort: 'default',
40
+ path: 'default',
41
+ git: 'default',
42
+ metrics: 'default',
43
+ context: 'default',
44
+ cache: 'default',
45
+ tokens: 'default',
46
+ speed: 'default',
47
+ durations: 'default',
48
+ counts: 'default',
49
+ };
50
+ /** Return a fresh default so callers can safely replace its group list. */
51
+ export function defaultStatusBarConfig() {
52
+ return {
53
+ enabled: true,
54
+ labels: 'compact',
55
+ groups: [...DEFAULT_STATUS_GROUPS],
56
+ order: [...DEFAULT_STATUS_GROUPS],
57
+ meta: [...DEFAULT_STATUS_META],
58
+ metaOrder: [...DEFAULT_STATUS_META],
59
+ colors: { ...DEFAULT_STATUS_COLORS },
60
+ sides: { ...DEFAULT_STATUS_SIDES },
61
+ };
62
+ }
63
+ function isStatusGroupId(value) {
64
+ return typeof value === 'string' && STATUS_GROUP_IDS.includes(value);
65
+ }
66
+ export function isStatusMetaId(value) {
67
+ return typeof value === 'string' && STATUS_META_IDS.includes(value);
68
+ }
69
+ export function isStatusItemId(value) {
70
+ return typeof value === 'string' && STATUS_ITEM_IDS.includes(value);
71
+ }
72
+ function isStatusLabelStyle(value) {
73
+ return typeof value === 'string' && STATUS_LABEL_STYLES.includes(value);
74
+ }
75
+ function isStatusColorToken(value) {
76
+ return typeof value === 'string' && STATUS_COLOR_TOKENS.includes(value);
77
+ }
78
+ function isStatusSide(value) {
79
+ return value === 'left' || value === 'right';
80
+ }
81
+ export function itemSide(config, id) {
82
+ return config.sides?.[id] ?? DEFAULT_STATUS_SIDES[id];
83
+ }
84
+ function resolveStatusSides(sides) {
85
+ const resolved = { ...DEFAULT_STATUS_SIDES };
86
+ if (sides === undefined)
87
+ return resolved;
88
+ for (const id of STATUS_ITEM_IDS) {
89
+ const side = sides[id];
90
+ if (isStatusSide(side))
91
+ resolved[id] = side;
92
+ }
93
+ return resolved;
94
+ }
95
+ function completeOrder(ids, preferred) {
96
+ const ordered = [...new Set(preferred.filter((id) => ids.includes(id)))];
97
+ return [...ordered, ...ids.filter(id => !ordered.includes(id))];
98
+ }
99
+ function resolveStatusColors(colors) {
100
+ const resolved = { ...DEFAULT_STATUS_COLORS };
101
+ if (colors === undefined)
102
+ return resolved;
103
+ for (const slot of STATUS_COLOR_SLOTS) {
104
+ const token = colors[slot];
105
+ if (isStatusColorToken(token))
106
+ resolved[slot] = token;
107
+ }
108
+ const legacyMetrics = resolved.metrics;
109
+ if (legacyMetrics !== 'default') {
110
+ for (const group of STATUS_GROUP_IDS) {
111
+ if (colors[group] === undefined)
112
+ resolved[group] = legacyMetrics;
113
+ }
114
+ }
115
+ return resolved;
116
+ }
117
+ /**
118
+ * Normalize persisted input at the single configuration seam. Invalid and
119
+ * duplicate groups disappear; legacy presets retain their previous meaning.
120
+ */
121
+ export function resolveStatusBarConfig(config, legacyPreset) {
122
+ if (config === undefined) {
123
+ const fallback = defaultStatusBarConfig();
124
+ if (legacyPreset === 'minimal')
125
+ fallback.enabled = false;
126
+ if (legacyPreset === 'full')
127
+ fallback.labels = 'full';
128
+ return fallback;
129
+ }
130
+ const groups = config.groups === undefined
131
+ ? [...DEFAULT_STATUS_GROUPS]
132
+ : [...new Set(config.groups.filter(isStatusGroupId))];
133
+ const meta = config.meta === undefined
134
+ ? [...DEFAULT_STATUS_META]
135
+ : [...new Set(config.meta.filter(isStatusMetaId))];
136
+ return {
137
+ enabled: config.enabled ?? true,
138
+ labels: isStatusLabelStyle(config.labels) ? config.labels : 'compact',
139
+ groups,
140
+ order: completeOrder(STATUS_GROUP_IDS, config.order === undefined ? groups : config.order),
141
+ meta,
142
+ metaOrder: completeOrder(STATUS_META_IDS, config.metaOrder === undefined ? meta : config.metaOrder),
143
+ colors: resolveStatusColors(config.colors),
144
+ sides: resolveStatusSides(config.sides),
145
+ };
146
+ }
147
+ //# sourceMappingURL=status-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status-config.js","sourceRoot":"","sources":["../../src/chrome/status-config.ts"],"names":[],"mappings":"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"}
@@ -7,8 +7,8 @@
7
7
  * second locale — then this is the single seam where a dictionary is chosen.
8
8
  * @module @agi-fans/dsh-tui/status-line
9
9
  */
10
- import type { TuiLoopStatus, TuiSessionControls, TuiSessionStats } from './definition.ts';
11
- import { type StatusBarConfig } from './status-config.ts';
10
+ import type { TuiLoopStatus, TuiSessionControls, TuiSessionStats } from '../definition.ts';
11
+ import { type StatusBarConfig, type StatusItemId } from './status-config.ts';
12
12
  import type { Theme } from './theme.ts';
13
13
  /** Context needed to render the fixed session footer. */
14
14
  export interface StatusFooterOptions {
@@ -21,6 +21,8 @@ export interface StatusFooterOptions {
21
21
  stats?: TuiSessionStats;
22
22
  config: StatusBarConfig;
23
23
  width: number;
24
+ /** Settings preview: underline the focused item. */
25
+ focus?: StatusItemId;
24
26
  }
25
27
  /** Compact token count: 517 / 12.2K / 517K / 1.2M. */
26
28
  export declare function formatTokens(value: number): string;
@@ -32,11 +34,27 @@ export declare function formatTokensPerSecond(value: number): string;
32
34
  export declare function sessionStatusGroups(stats: TuiSessionStats, config?: StatusBarConfig): string[];
33
35
  /** Human-facing permission preset used consistently across terminal surfaces. */
34
36
  export declare function formatPermission(permission: string): string;
37
+ /** Painted permission mode for the composer top-right cap. */
38
+ export declare function renderPermissionBadge(permission: string | undefined, theme: Theme): string;
35
39
  /**
36
40
  * Render the fixed footer: model/workspace metadata first, customizable
37
41
  * session telemetry second. Both rows use left/right columns and exact width.
38
42
  */
39
43
  export declare function renderStatusFooter(options: StatusFooterOptions, theme: Theme): string[];
44
+ /**
45
+ * Status sample for `/settings`. Uses the same left/right footer layout as
46
+ * the live two-line status bar.
47
+ */
48
+ export declare function renderStatusPreviewLines(options: {
49
+ model: string;
50
+ reasoningEffort?: string;
51
+ pwd?: string;
52
+ branch?: string;
53
+ stats?: TuiSessionStats;
54
+ config: StatusBarConfig;
55
+ width: number;
56
+ focus?: StatusItemId;
57
+ }, theme: Theme): string[];
40
58
  /**
41
59
  * Render the telemetry row without footer metadata. Used by the settings
42
60
  * preview and retained as a compatibility seam for direct render callers.
@@ -7,13 +7,13 @@
7
7
  * second locale — then this is the single seam where a dictionary is chosen.
8
8
  * @module @agi-fans/dsh-tui/status-line
9
9
  */
10
- import { defaultStatusBarConfig, resolveStatusBarConfig } from "./status-config.js";
10
+ import { formatAgentPreset, formatToolPresentation } from "../session/session-configuration.js";
11
+ import { defaultStatusBarConfig, itemSide, resolveStatusBarConfig, } from "./status-config.js";
11
12
  import { padToWidth, truncateToWidth, visibleWidth } from "./width.js";
12
13
  const LABEL_PADDING = 2;
13
14
  const GROUP_SEPARATOR = ' • ';
14
15
  const FOOTER_PADDING = 2;
15
16
  const COLUMN_GAP = 3;
16
- const RIGHT_GROUPS = new Set(['durations', 'counts']);
17
17
  /** Compact token count: 517 / 12.2K / 517K / 1.2M. */
18
18
  export function formatTokens(value) {
19
19
  const scaled = (n) => n >= 100 ? String(Math.round(n)) : String(Math.round(n * 10) / 10);
@@ -144,37 +144,55 @@ function selectGroups(groups, width) {
144
144
  }
145
145
  return selected;
146
146
  }
147
- function toneColor(tone) {
148
- if (tone === 'value')
149
- return 'text';
147
+ function tokenThemeColor(token, fallback) {
148
+ if (token === undefined || token === 'default')
149
+ return fallback;
150
+ return token === 'label' ? 'customMessageLabel' : token;
151
+ }
152
+ function itemColor(config, id, fallback) {
153
+ const direct = config.colors?.[id];
154
+ if (direct !== undefined && direct !== 'default')
155
+ return tokenThemeColor(direct, fallback);
156
+ return fallback;
157
+ }
158
+ function toneColor(tone, config, group) {
150
159
  if (tone === 'positive')
151
160
  return 'success';
152
- if (tone === 'token')
153
- return 'customMessageLabel';
154
- return tone === 'separator' ? 'dim' : 'muted';
161
+ if (tone === 'separator')
162
+ return 'dim';
163
+ if (tone === 'label')
164
+ return 'muted';
165
+ const custom = itemColor(config, group, 'text');
166
+ if (config.colors?.[group] !== undefined && config.colors[group] !== 'default')
167
+ return custom;
168
+ if (config.colors?.metrics !== undefined && config.colors.metrics !== 'default')
169
+ return tokenThemeColor(config.colors.metrics, 'text');
170
+ return tone === 'value' ? 'text' : 'customMessageLabel';
155
171
  }
156
- function paintGroup(group, theme) {
157
- return group.parts.map(item => theme.fg(toneColor(item.tone), item.text)).join('');
172
+ function paintGroup(group, theme, config, focus) {
173
+ const painted = group.parts.map(item => theme.fg(toneColor(item.tone, config, group.id), item.text)).join('');
174
+ return focus === group.id ? theme.underline(painted) : painted;
158
175
  }
159
- function paintColumn(groups, theme) {
176
+ function paintColumn(groups, theme, config, focus) {
160
177
  const separator = theme.fg('dim', GROUP_SEPARATOR);
161
- return groups.map(group => paintGroup(group, theme)).join(separator);
178
+ return groups.map(group => paintGroup(group, theme, config, focus)).join(separator);
162
179
  }
163
180
  function splitWidth(left, right) {
164
181
  const leftWidth = groupsWidth(left);
165
182
  const rightWidth = groupsWidth(right);
166
183
  return leftWidth + rightWidth + (leftWidth > 0 && rightWidth > 0 ? COLUMN_GAP : 0);
167
184
  }
168
- /** Select whole groups in user order, then place timing/activity on the right. */
169
- function selectFooterGroups(groups, width) {
185
+ /** Select whole groups in user order, then place each on its configured column. */
186
+ function selectFooterGroups(groups, width, config) {
170
187
  const left = [];
171
188
  const right = [];
172
189
  for (const group of groups) {
173
- const candidateLeft = RIGHT_GROUPS.has(group.id) ? left : [...left, group];
174
- const candidateRight = RIGHT_GROUPS.has(group.id) ? [...right, group] : right;
190
+ const rightSide = itemSide(config, group.id) === 'right';
191
+ const candidateLeft = rightSide ? left : [...left, group];
192
+ const candidateRight = rightSide ? [...right, group] : right;
175
193
  if (splitWidth(candidateLeft, candidateRight) > width)
176
194
  break;
177
- if (RIGHT_GROUPS.has(group.id))
195
+ if (rightSide)
178
196
  right.push(group);
179
197
  else
180
198
  left.push(group);
@@ -195,14 +213,22 @@ function renderSplitRow(left, right, width) {
195
213
  const gap = Math.max(1, innerWidth - leftWidth - rightWidth);
196
214
  return padToWidth(' '.repeat(padding) + left + ' '.repeat(gap) + right, width);
197
215
  }
198
- function planStatus(plan) {
199
- if (plan === undefined)
216
+ function sessionConfigurationStatus(controls) {
217
+ if (controls === undefined)
200
218
  return undefined;
201
- if (plan.pending)
202
- return plan.active
203
- ? { text: 'PLAN→DEFAULT', tone: 'warning' }
204
- : { text: 'PLAN…', tone: 'accent' };
205
- return plan.active ? { text: 'PLAN', tone: 'accent' } : undefined;
219
+ const agent = formatAgentPreset(controls.agentPreset ?? 'standard').toLowerCase();
220
+ const parts = [agent];
221
+ if (controls.plan?.pending === true)
222
+ parts.push(controls.plan.active ? 'plan off…' : 'plan…');
223
+ else if (controls.plan?.active === true)
224
+ parts.push('plan');
225
+ if (controls.tools !== undefined && controls.tools !== 'native') {
226
+ parts.push(formatToolPresentation(controls.tools).toLowerCase());
227
+ }
228
+ return {
229
+ text: parts.join(' · '),
230
+ tone: controls.plan?.pending === true ? 'warning' : 'accent',
231
+ };
206
232
  }
207
233
  function loopStatus(loop) {
208
234
  if (loop === undefined)
@@ -228,49 +254,34 @@ function loopStatus(loop) {
228
254
  return { text: `LOOP · ${loop.limit}`, tone: 'accent' };
229
255
  return { text: 'LOOP', tone: 'accent' };
230
256
  }
231
- function fitModelColumn(model, effort, plan, loop, theme, width) {
232
- if (width <= 0)
233
- return '';
234
- const separator = ' · ';
235
- const statuses = [planStatus(plan), loopStatus(loop)].filter((status) => status !== undefined);
236
- const statusWidth = statuses.reduce((total, status) => total + visibleWidth(status.text), 0)
237
- + Math.max(0, statuses.length - 1) * visibleWidth(separator);
238
- if (statusWidth >= width) {
239
- const summary = statuses.map(status => status.text).join(separator);
240
- return theme.fg(statuses.at(-1)?.tone ?? 'accent', truncateToWidth(summary, width));
257
+ function gitTone(branch, config) {
258
+ const custom = config.colors?.git;
259
+ if (custom !== undefined && custom !== 'default')
260
+ return tokenThemeColor(custom, 'muted');
261
+ return /(?:^|\s)[*?]\d+/.test(branch) ? 'warning' : 'muted';
262
+ }
263
+ function visibleMetaIds(config) {
264
+ const order = config.metaOrder ?? ['model', 'effort', 'path', 'git'];
265
+ const visible = new Set(config.meta ?? ['model', 'effort', 'path', 'git']);
266
+ return order.filter(id => visible.has(id));
267
+ }
268
+ function buildMetaParts(options, config) {
269
+ const parts = [];
270
+ for (const id of visibleMetaIds(config)) {
271
+ if (id === 'model' && options.model !== '') {
272
+ parts.push({ id, text: options.model, color: itemColor(config, 'model', 'text') });
273
+ }
274
+ if (id === 'effort' && options.reasoningEffort !== undefined && options.reasoningEffort !== '') {
275
+ parts.push({ id, text: options.reasoningEffort, color: itemColor(config, 'effort', 'customMessageLabel') });
276
+ }
277
+ if (id === 'path' && options.pwd !== undefined && options.pwd !== '') {
278
+ parts.push({ id, text: options.pwd, color: itemColor(config, 'path', 'muted') });
279
+ }
280
+ if (id === 'git' && options.branch !== undefined && options.branch !== '') {
281
+ parts.push({ id, text: options.branch, color: gitTone(options.branch, config) });
282
+ }
241
283
  }
242
- const suffix = statuses.map((status, index) => (index === 0 ? '' : theme.fg('dim', separator)) + theme.fg(status.tone, status.text)).join('');
243
- const prefixWidth = statuses.length === 0 ? width : width - visibleWidth(separator) - statusWidth;
244
- const prefix = effort === undefined || effort === ''
245
- ? theme.fg('text', truncateToWidth(model, Math.max(1, prefixWidth)))
246
- : (() => {
247
- const effortWidth = visibleWidth(effort);
248
- if (effortWidth + visibleWidth(separator) >= prefixWidth) {
249
- return theme.fg('customMessageLabel', truncateToWidth(effort, Math.max(1, prefixWidth)));
250
- }
251
- const modelWidth = Math.max(1, prefixWidth - visibleWidth(separator) - effortWidth);
252
- return theme.fg('text', truncateToWidth(model, modelWidth))
253
- + theme.fg('dim', separator)
254
- + theme.fg('customMessageLabel', effort);
255
- })();
256
- return statuses.length === 0 ? prefix : prefix + theme.fg('dim', separator) + suffix;
257
- }
258
- function fitLocationColumn(pwd, branch, theme, width) {
259
- if (width <= 0)
260
- return '';
261
- if (branch === undefined || branch === '')
262
- return theme.fg('muted', truncateToWidth(pwd ?? '', width));
263
- const branchTone = /(?:^|\s)[*?]\d+/.test(branch) ? 'warning' : 'muted';
264
- if (pwd === undefined || pwd === '')
265
- return theme.fg(branchTone, truncateToWidth(branch, width));
266
- const separator = ' · ';
267
- const branchWidth = visibleWidth(branch);
268
- if (branchWidth >= width)
269
- return theme.fg(branchTone, truncateToWidth(branch, width));
270
- const pwdWidth = Math.max(1, width - visibleWidth(separator) - branchWidth);
271
- return theme.fg('muted', truncateToWidth(pwd, pwdWidth))
272
- + theme.fg('dim', separator)
273
- + theme.fg(branchTone, branch);
284
+ return parts;
274
285
  }
275
286
  function permissionTone(permission) {
276
287
  if (permission === 'danger-full-access' || permission === 'custom')
@@ -290,53 +301,51 @@ export function formatPermission(permission) {
290
301
  return permission === 'custom' ? 'Custom access' : permission;
291
302
  }
292
303
  function permissionLabel(permission) {
293
- const label = formatPermission(permission);
294
- return permission === 'danger-full-access' || permission === 'custom' ? label.toUpperCase() : label;
304
+ return formatPermission(permission).toLowerCase();
295
305
  }
296
- function fitWorkspaceColumn(pwd, branch, permission, theme, width) {
306
+ /** Painted permission mode for the composer top-right cap. */
307
+ export function renderPermissionBadge(permission, theme) {
297
308
  if (permission === undefined || permission === '')
298
- return fitLocationColumn(pwd, branch, theme, width);
299
- const label = permissionLabel(permission);
300
- const separator = ' · ';
301
- const labelWidth = visibleWidth(label);
302
- if (labelWidth >= width)
303
- return theme.fg(permissionTone(permission), truncateToWidth(label, width));
304
- const locationWidth = width - labelWidth - visibleWidth(separator);
305
- const location = fitLocationColumn(pwd, branch, theme, locationWidth);
306
- if (location === '')
307
- return theme.fg(permissionTone(permission), label);
308
- return theme.fg(permissionTone(permission), label) + theme.fg('dim', separator) + location;
309
- }
310
- function metadataColumns(options, theme, innerWidth) {
311
- const plan = planStatus(options.controls?.plan);
312
- const loop = loopStatus(options.loop);
313
- const permission = options.controls?.permission;
314
- const rawLeftWidth = visibleWidth(options.model)
315
- + (options.reasoningEffort === undefined || options.reasoningEffort === '' ? 0 : 3 + visibleWidth(options.reasoningEffort))
316
- + (plan === undefined ? 0 : 3 + visibleWidth(plan.text))
317
- + (loop === undefined ? 0 : 3 + visibleWidth(loop.text));
318
- const rawLocationWidth = visibleWidth(options.pwd ?? '')
319
- + (options.branch === undefined || options.branch === '' ? 0 : 3 + visibleWidth(options.branch));
320
- const rawRightWidth = rawLocationWidth
321
- + (permission === undefined || permission === ''
322
- ? 0
323
- : visibleWidth(permissionLabel(permission)) + (rawLocationWidth === 0 ? 0 : 3));
324
- if (rawRightWidth === 0) {
325
- return { left: fitModelColumn(options.model, options.reasoningEffort, options.controls?.plan, options.loop, theme, innerWidth), right: '' };
309
+ return '';
310
+ return theme.fg(permissionTone(permission), permissionLabel(permission));
311
+ }
312
+ function splitMetaParts(options, config) {
313
+ const left = [];
314
+ const right = [];
315
+ for (const part of buildMetaParts(options, config)) {
316
+ if (itemSide(config, part.id) === 'right')
317
+ right.push(part);
318
+ else
319
+ left.push(part);
326
320
  }
327
- if (rawLeftWidth + COLUMN_GAP + rawRightWidth <= innerWidth) {
328
- return {
329
- left: fitModelColumn(options.model, options.reasoningEffort, options.controls?.plan, options.loop, theme, rawLeftWidth),
330
- right: fitWorkspaceColumn(options.pwd, options.branch, permission, theme, rawRightWidth),
331
- };
321
+ return { left, right };
322
+ }
323
+ function metadataColumns(options, theme, innerWidth, config, focus) {
324
+ const separator = ' · ';
325
+ const statuses = [sessionConfigurationStatus(options.controls), loopStatus(options.loop)].filter((status) => status !== undefined);
326
+ const columns = splitMetaParts(options, config);
327
+ const statusWidth = statuses.reduce((total, status) => total + visibleWidth(status.text), 0)
328
+ + Math.max(0, statuses.length - 1) * visibleWidth(separator);
329
+ const rightNatural = columns.right.reduce((total, part) => total + visibleWidth(part.text), 0)
330
+ + Math.max(0, columns.right.length - 1) * visibleWidth(separator);
331
+ const leftNatural = columns.left.reduce((total, part) => total + visibleWidth(part.text), 0)
332
+ + Math.max(0, columns.left.length - 1) * visibleWidth(separator)
333
+ + (statusWidth === 0 ? 0 : visibleWidth(separator) + statusWidth);
334
+ const gap = leftNatural > 0 && rightNatural > 0 ? COLUMN_GAP : 0;
335
+ let leftWidth = leftNatural;
336
+ let rightWidth = rightNatural;
337
+ if (leftWidth + gap + rightWidth > innerWidth) {
338
+ const available = Math.max(2, innerWidth - gap);
339
+ leftWidth = Math.min(leftNatural, Math.max(1, Math.floor(available * 0.45)));
340
+ rightWidth = Math.max(1, available - leftWidth);
332
341
  }
333
- const available = Math.max(2, innerWidth - COLUMN_GAP);
334
- const leftWidth = Math.min(rawLeftWidth, Math.max(1, Math.floor(available * 0.45)));
335
- const rightWidth = Math.max(1, available - leftWidth);
336
- return {
337
- left: fitModelColumn(options.model, options.reasoningEffort, options.controls?.plan, options.loop, theme, leftWidth),
338
- right: fitWorkspaceColumn(options.pwd, options.branch, permission, theme, rightWidth),
339
- };
342
+ const statusPainted = statuses.map((status, index) => (index === 0 ? '' : theme.fg('dim', separator)) + theme.fg(status.tone, status.text)).join('');
343
+ const metaLeft = packPreviewParts(columns.left, theme, Math.max(1, leftWidth - (statusWidth === 0 ? 0 : visibleWidth(separator) + statusWidth)), focus);
344
+ const left = metaLeft === ''
345
+ ? statusPainted
346
+ : statusPainted === '' ? metaLeft : metaLeft + theme.fg('dim', separator) + statusPainted;
347
+ const right = packPreviewParts(columns.right, theme, rightWidth, focus);
348
+ return { left, right };
340
349
  }
341
350
  /**
342
351
  * Render the fixed footer: model/workspace metadata first, customizable
@@ -348,15 +357,68 @@ export function renderStatusFooter(options, theme) {
348
357
  if (width === 0)
349
358
  return [];
350
359
  const innerWidth = Math.max(0, width - FOOTER_PADDING * 2);
351
- const metadata = metadataColumns(options, theme, innerWidth);
360
+ const metadata = metadataColumns({ ...options, config: normalized }, theme, innerWidth, normalized, options.focus);
352
361
  const telemetryGroups = options.stats === undefined || !normalized.enabled
353
362
  ? { left: [], right: [] }
354
- : selectFooterGroups(buildStatusGroups(options.stats, normalized), innerWidth);
363
+ : selectFooterGroups(buildStatusGroups(options.stats, normalized), innerWidth, normalized);
355
364
  return [
356
365
  renderSplitRow(metadata.left, metadata.right, width),
357
- renderSplitRow(paintColumn(telemetryGroups.left, theme), paintColumn(telemetryGroups.right, theme), width),
366
+ renderSplitRow(paintColumn(telemetryGroups.left, theme, normalized, options.focus), paintColumn(telemetryGroups.right, theme, normalized, options.focus), width),
358
367
  ];
359
368
  }
369
+ function packPreviewParts(parts, theme, width, focus) {
370
+ if (width <= 0)
371
+ return '';
372
+ const separator = ' · ';
373
+ const separatorWidth = visibleWidth(separator);
374
+ let used = 0;
375
+ let out = '';
376
+ for (const part of parts) {
377
+ if (part.text === '')
378
+ continue;
379
+ const prefix = out === '' ? 0 : separatorWidth;
380
+ const textWidth = visibleWidth(part.text);
381
+ const paint = (text) => {
382
+ const colored = theme.fg(part.color, text);
383
+ return focus !== undefined && part.id === focus ? theme.underline(colored) : colored;
384
+ };
385
+ if (used + prefix + textWidth <= width) {
386
+ if (out !== '')
387
+ out += theme.fg('dim', separator);
388
+ out += paint(part.text);
389
+ used += prefix + textWidth;
390
+ continue;
391
+ }
392
+ const remaining = width - used - prefix;
393
+ if (remaining <= 0)
394
+ break;
395
+ if (out !== '')
396
+ out += theme.fg('dim', separator);
397
+ out += paint(truncateToWidth(part.text, remaining));
398
+ break;
399
+ }
400
+ return out;
401
+ }
402
+ /**
403
+ * Status sample for `/settings`. Uses the same left/right footer layout as
404
+ * the live two-line status bar.
405
+ */
406
+ export function renderStatusPreviewLines(options, theme) {
407
+ const normalized = resolveStatusBarConfig(options.config);
408
+ const width = Math.max(0, options.width);
409
+ if (width === 0)
410
+ return [];
411
+ return renderStatusFooter({
412
+ model: options.model,
413
+ ...(options.reasoningEffort === undefined ? {} : { reasoningEffort: options.reasoningEffort }),
414
+ ...(options.pwd === undefined ? {} : { pwd: options.pwd }),
415
+ ...(options.branch === undefined ? {} : { branch: options.branch }),
416
+ ...(options.stats === undefined ? {} : { stats: options.stats }),
417
+ config: normalized,
418
+ width,
419
+ ...(options.focus === undefined ? {} : { focus: options.focus }),
420
+ }, theme).map(line => line.trim());
421
+ }
360
422
  /**
361
423
  * Render the telemetry row without footer metadata. Used by the settings
362
424
  * preview and retained as a compatibility seam for direct render callers.
@@ -368,7 +430,7 @@ export function renderSessionStatusLabel(stats, config, theme, width) {
368
430
  const groups = selectGroups(buildStatusGroups(stats, normalized), width);
369
431
  if (groups.length === 0)
370
432
  return '';
371
- const line = ' ' + paintColumn(groups, theme) + ' ';
433
+ const line = ' ' + paintColumn(groups, theme, normalized) + ' ';
372
434
  return truncateToWidth(line, width);
373
435
  }
374
436
  //# sourceMappingURL=status-line.js.map