aiwcli 0.10.3 → 0.11.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.
Files changed (191) hide show
  1. package/bin/run.js +1 -1
  2. package/dist/commands/clear.js +28 -131
  3. package/dist/commands/init/index.js +3 -3
  4. package/dist/lib/gitignore-manager.d.ts +32 -0
  5. package/dist/lib/gitignore-manager.js +141 -2
  6. package/dist/templates/CLAUDE.md +8 -8
  7. package/dist/templates/_shared/.claude/commands/handoff-resume.md +64 -0
  8. package/dist/templates/_shared/.claude/commands/handoff.md +16 -10
  9. package/dist/templates/_shared/.claude/settings.json +7 -7
  10. package/dist/templates/_shared/hooks-ts/_utils/git-state.ts +2 -0
  11. package/dist/templates/_shared/hooks-ts/archive_plan.ts +159 -0
  12. package/dist/templates/_shared/hooks-ts/context_monitor.ts +147 -0
  13. package/dist/templates/_shared/hooks-ts/file-suggestion.ts +130 -0
  14. package/dist/templates/_shared/hooks-ts/pre_compact.ts +49 -0
  15. package/dist/templates/_shared/hooks-ts/session_end.ts +107 -0
  16. package/dist/templates/_shared/hooks-ts/session_start.ts +144 -0
  17. package/dist/templates/_shared/hooks-ts/task_create_capture.ts +48 -0
  18. package/dist/templates/_shared/hooks-ts/task_update_capture.ts +74 -0
  19. package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +83 -0
  20. package/dist/templates/_shared/lib-ts/CLAUDE.md +318 -0
  21. package/dist/templates/_shared/lib-ts/base/atomic-write.ts +12 -12
  22. package/dist/templates/_shared/lib-ts/base/constants.ts +22 -15
  23. package/dist/templates/_shared/lib-ts/base/git-state.ts +1 -1
  24. package/dist/templates/_shared/lib-ts/base/hook-utils.ts +129 -50
  25. package/dist/templates/_shared/lib-ts/base/inference.ts +28 -21
  26. package/dist/templates/_shared/lib-ts/base/logger.ts +15 -2
  27. package/dist/templates/_shared/lib-ts/base/state-io.ts +9 -7
  28. package/dist/templates/_shared/lib-ts/base/stop-words.ts +131 -131
  29. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +142 -0
  30. package/dist/templates/_shared/lib-ts/base/utils.ts +69 -69
  31. package/dist/templates/_shared/lib-ts/context/context-formatter.ts +30 -24
  32. package/dist/templates/_shared/lib-ts/context/context-selector.ts +50 -32
  33. package/dist/templates/_shared/lib-ts/context/context-store.ts +76 -48
  34. package/dist/templates/_shared/lib-ts/context/plan-manager.ts +43 -23
  35. package/dist/templates/_shared/lib-ts/context/task-tracker.ts +10 -6
  36. package/dist/templates/_shared/lib-ts/handoff/document-generator.ts +11 -10
  37. package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +158 -0
  38. package/dist/templates/_shared/lib-ts/templates/formatters.ts +6 -4
  39. package/dist/templates/_shared/lib-ts/types.ts +68 -55
  40. package/dist/templates/_shared/scripts/resolve_context.ts +24 -0
  41. package/dist/templates/_shared/scripts/resume_handoff.ts +345 -0
  42. package/dist/templates/_shared/scripts/save_handoff.ts +3 -3
  43. package/dist/templates/_shared/scripts/status_line.ts +687 -0
  44. package/dist/templates/cc-native/.claude/settings.json +175 -185
  45. package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +15 -17
  46. package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +0 -2
  47. package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +109 -135
  48. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.ts +119 -0
  49. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +1027 -0
  50. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +61 -0
  51. package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +156 -0
  52. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +792 -0
  53. package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +199 -0
  54. package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +144 -0
  55. package/dist/templates/cc-native/_cc-native/lib-ts/config.ts +57 -0
  56. package/dist/templates/cc-native/_cc-native/lib-ts/constants.ts +83 -0
  57. package/dist/templates/cc-native/_cc-native/lib-ts/corroboration.ts +115 -0
  58. package/dist/templates/cc-native/_cc-native/lib-ts/debug.ts +80 -0
  59. package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +120 -0
  60. package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +168 -0
  61. package/dist/templates/cc-native/_cc-native/lib-ts/nul +3 -0
  62. package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +250 -0
  63. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +275 -0
  64. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +130 -0
  65. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +107 -0
  66. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +10 -0
  67. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +23 -0
  68. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +240 -0
  69. package/dist/templates/cc-native/_cc-native/lib-ts/tsconfig.json +18 -0
  70. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +385 -0
  71. package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +72 -0
  72. package/dist/templates/cc-native/_cc-native/plan-review.config.json +14 -1
  73. package/oclif.manifest.json +1 -1
  74. package/package.json +2 -2
  75. package/dist/templates/_shared/hooks/__init__.py +0 -16
  76. package/dist/templates/_shared/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  77. package/dist/templates/_shared/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
  78. package/dist/templates/_shared/hooks/__pycache__/context_enforcer.cpython-313.pyc +0 -0
  79. package/dist/templates/_shared/hooks/__pycache__/context_monitor.cpython-313.pyc +0 -0
  80. package/dist/templates/_shared/hooks/__pycache__/file-suggestion.cpython-313.pyc +0 -0
  81. package/dist/templates/_shared/hooks/__pycache__/pre_compact.cpython-313.pyc +0 -0
  82. package/dist/templates/_shared/hooks/__pycache__/session_end.cpython-313.pyc +0 -0
  83. package/dist/templates/_shared/hooks/__pycache__/session_start.cpython-313.pyc +0 -0
  84. package/dist/templates/_shared/hooks/__pycache__/task_create_atomicity.cpython-313.pyc +0 -0
  85. package/dist/templates/_shared/hooks/__pycache__/task_create_capture.cpython-313.pyc +0 -0
  86. package/dist/templates/_shared/hooks/__pycache__/task_update_capture.cpython-313.pyc +0 -0
  87. package/dist/templates/_shared/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
  88. package/dist/templates/_shared/hooks/archive_plan.py +0 -177
  89. package/dist/templates/_shared/hooks/context_monitor.py +0 -270
  90. package/dist/templates/_shared/hooks/file-suggestion.py +0 -215
  91. package/dist/templates/_shared/hooks/pre_compact.py +0 -104
  92. package/dist/templates/_shared/hooks/session_end.py +0 -173
  93. package/dist/templates/_shared/hooks/session_start.py +0 -206
  94. package/dist/templates/_shared/hooks/task_create_capture.py +0 -108
  95. package/dist/templates/_shared/hooks/task_update_capture.py +0 -145
  96. package/dist/templates/_shared/hooks/user_prompt_submit.py +0 -139
  97. package/dist/templates/_shared/lib/__init__.py +0 -1
  98. package/dist/templates/_shared/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  99. package/dist/templates/_shared/lib/base/__init__.py +0 -65
  100. package/dist/templates/_shared/lib/base/__pycache__/__init__.cpython-313.pyc +0 -0
  101. package/dist/templates/_shared/lib/base/__pycache__/atomic_write.cpython-313.pyc +0 -0
  102. package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
  103. package/dist/templates/_shared/lib/base/__pycache__/hook_utils.cpython-313.pyc +0 -0
  104. package/dist/templates/_shared/lib/base/__pycache__/inference.cpython-313.pyc +0 -0
  105. package/dist/templates/_shared/lib/base/__pycache__/logger.cpython-313.pyc +0 -0
  106. package/dist/templates/_shared/lib/base/__pycache__/stop_words.cpython-313.pyc +0 -0
  107. package/dist/templates/_shared/lib/base/__pycache__/subprocess_utils.cpython-313.pyc +0 -0
  108. package/dist/templates/_shared/lib/base/__pycache__/utils.cpython-313.pyc +0 -0
  109. package/dist/templates/_shared/lib/base/atomic_write.py +0 -180
  110. package/dist/templates/_shared/lib/base/constants.py +0 -358
  111. package/dist/templates/_shared/lib/base/hook_utils.py +0 -339
  112. package/dist/templates/_shared/lib/base/inference.py +0 -307
  113. package/dist/templates/_shared/lib/base/logger.py +0 -305
  114. package/dist/templates/_shared/lib/base/stop_words.py +0 -221
  115. package/dist/templates/_shared/lib/base/subprocess_utils.py +0 -46
  116. package/dist/templates/_shared/lib/base/utils.py +0 -263
  117. package/dist/templates/_shared/lib/context/__init__.py +0 -102
  118. package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
  119. package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
  120. package/dist/templates/_shared/lib/context/__pycache__/context_extractor.cpython-313.pyc +0 -0
  121. package/dist/templates/_shared/lib/context/__pycache__/context_formatter.cpython-313.pyc +0 -0
  122. package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
  123. package/dist/templates/_shared/lib/context/__pycache__/context_selector.cpython-313.pyc +0 -0
  124. package/dist/templates/_shared/lib/context/__pycache__/context_store.cpython-313.pyc +0 -0
  125. package/dist/templates/_shared/lib/context/__pycache__/discovery.cpython-313.pyc +0 -0
  126. package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
  127. package/dist/templates/_shared/lib/context/__pycache__/plan_archive.cpython-313.pyc +0 -0
  128. package/dist/templates/_shared/lib/context/__pycache__/plan_manager.cpython-313.pyc +0 -0
  129. package/dist/templates/_shared/lib/context/__pycache__/task_sync.cpython-313.pyc +0 -0
  130. package/dist/templates/_shared/lib/context/__pycache__/task_tracker.cpython-313.pyc +0 -0
  131. package/dist/templates/_shared/lib/context/context_formatter.py +0 -317
  132. package/dist/templates/_shared/lib/context/context_selector.py +0 -508
  133. package/dist/templates/_shared/lib/context/context_store.py +0 -653
  134. package/dist/templates/_shared/lib/context/plan_manager.py +0 -303
  135. package/dist/templates/_shared/lib/context/task_tracker.py +0 -188
  136. package/dist/templates/_shared/lib/handoff/__init__.py +0 -22
  137. package/dist/templates/_shared/lib/handoff/__pycache__/__init__.cpython-313.pyc +0 -0
  138. package/dist/templates/_shared/lib/handoff/__pycache__/document_generator.cpython-313.pyc +0 -0
  139. package/dist/templates/_shared/lib/handoff/document_generator.py +0 -278
  140. package/dist/templates/_shared/lib/templates/README.md +0 -206
  141. package/dist/templates/_shared/lib/templates/__init__.py +0 -36
  142. package/dist/templates/_shared/lib/templates/__pycache__/__init__.cpython-313.pyc +0 -0
  143. package/dist/templates/_shared/lib/templates/__pycache__/formatters.cpython-313.pyc +0 -0
  144. package/dist/templates/_shared/lib/templates/__pycache__/persona_questions.cpython-313.pyc +0 -0
  145. package/dist/templates/_shared/lib/templates/__pycache__/plan_context.cpython-313.pyc +0 -0
  146. package/dist/templates/_shared/lib/templates/formatters.py +0 -146
  147. package/dist/templates/_shared/lib/templates/plan_context.py +0 -73
  148. package/dist/templates/_shared/scripts/__pycache__/save_handoff.cpython-313.pyc +0 -0
  149. package/dist/templates/_shared/scripts/__pycache__/status_line.cpython-313.pyc +0 -0
  150. package/dist/templates/_shared/scripts/save_handoff.py +0 -357
  151. package/dist/templates/_shared/scripts/status_line.py +0 -716
  152. package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +0 -8
  153. package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +0 -8
  154. package/dist/templates/cc-native/MIGRATION.md +0 -86
  155. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
  156. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
  157. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/mark_questions_asked.cpython-313.pyc +0 -0
  158. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_accepted.cpython-313.pyc +0 -0
  159. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_questions_early.cpython-313.pyc +0 -0
  160. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/suggest-fresh-perspective.cpython-313.pyc +0 -0
  161. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +0 -130
  162. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +0 -954
  163. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.py +0 -81
  164. package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +0 -340
  165. package/dist/templates/cc-native/_cc-native/lib/CLAUDE.md +0 -265
  166. package/dist/templates/cc-native/_cc-native/lib/__init__.py +0 -53
  167. package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  168. package/dist/templates/cc-native/_cc-native/lib/__pycache__/atomic_write.cpython-313.pyc +0 -0
  169. package/dist/templates/cc-native/_cc-native/lib/__pycache__/constants.cpython-313.pyc +0 -0
  170. package/dist/templates/cc-native/_cc-native/lib/__pycache__/debug.cpython-313.pyc +0 -0
  171. package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
  172. package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
  173. package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
  174. package/dist/templates/cc-native/_cc-native/lib/constants.py +0 -45
  175. package/dist/templates/cc-native/_cc-native/lib/debug.py +0 -139
  176. package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +0 -362
  177. package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +0 -28
  178. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
  179. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
  180. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
  181. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
  182. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
  183. package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +0 -215
  184. package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +0 -88
  185. package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +0 -124
  186. package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +0 -108
  187. package/dist/templates/cc-native/_cc-native/lib/state.py +0 -268
  188. package/dist/templates/cc-native/_cc-native/lib/utils.py +0 -1071
  189. package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
  190. package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +0 -168
  191. package/dist/templates/cc-native/_cc-native/workflows/fresh-perspective.md +0 -134
@@ -0,0 +1,687 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Status line for Claude Code sessions.
4
+ *
5
+ * Renders context window usage and git status with ANSI colors.
6
+ * Optionally persists context_window data to the session's state.json.
7
+ *
8
+ * Ported from status_line.py — context and git sections only.
9
+ *
10
+ * Usage: echo '{"session_id":"...","model":{"display_name":"Opus"},...}' | bun status_line.ts
11
+ */
12
+ import * as fs from "node:fs";
13
+ import * as path from "node:path";
14
+ import { execFileSync } from "node:child_process";
15
+ import { homedir } from "node:os";
16
+
17
+ import { CONTEXT_BASELINE_TOKENS } from "../lib-ts/base/hook-utils.js";
18
+ import { getContextBySessionId, getContext, loadState, saveState } from "../lib-ts/context/context-store.js";
19
+ import { findLatestPlan } from "../lib-ts/context/plan-manager.js";
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Path setup
23
+ // ---------------------------------------------------------------------------
24
+ const SCRIPT_DIR = path.dirname(new URL(import.meta.url).pathname);
25
+ const OUTPUT_DIR = path.join(".", "_output");
26
+ const CACHE_DIR = path.join(OUTPUT_DIR, "cache");
27
+ const STATUSLINE_CACHE = path.join(CACHE_DIR, ".statusline-cache.json");
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // NO_COLOR support (https://no-color.org)
31
+ // ---------------------------------------------------------------------------
32
+ const NO_COLOR = Boolean(process.env.NO_COLOR);
33
+
34
+ const RESET = NO_COLOR ? "" : "\x1b[0m";
35
+
36
+ // Structural
37
+ const SLATE_300 = NO_COLOR ? "" : "\x1b[38;2;203;213;225m";
38
+ const SLATE_400 = NO_COLOR ? "" : "\x1b[38;2;148;163;184m";
39
+ const SLATE_500 = NO_COLOR ? "" : "\x1b[38;2;100;116;139m";
40
+ const SLATE_600 = NO_COLOR ? "" : "\x1b[38;2;71;85;105m";
41
+
42
+ // Semantic
43
+ const EMERALD = NO_COLOR ? "" : "\x1b[38;2;74;222;128m";
44
+ const ROSE = NO_COLOR ? "" : "\x1b[38;2;251;113;133m";
45
+ const AMBER = NO_COLOR ? "" : "\x1b[38;2;251;191;36m";
46
+
47
+ // Context colors
48
+ const CTX_PRIMARY = NO_COLOR ? "" : "\x1b[38;2;129;140;248m";
49
+ const CTX_SECONDARY = NO_COLOR ? "" : "\x1b[38;2;165;180;252m";
50
+ const CTX_ACCENT = NO_COLOR ? "" : "\x1b[38;2;139;92;246m";
51
+ const CTX_BUCKET_EMPTY = NO_COLOR ? "" : "\x1b[38;2;75;82;95m";
52
+
53
+ // Git colors
54
+ const GIT_PRIMARY = NO_COLOR ? "" : "\x1b[38;2;56;189;248m";
55
+ const GIT_VALUE = NO_COLOR ? "" : "\x1b[38;2;186;230;253m";
56
+ const GIT_DIR = NO_COLOR ? "" : "\x1b[38;2;147;197;253m";
57
+ const GIT_CLEAN = NO_COLOR ? "" : "\x1b[38;2;125;211;252m";
58
+ const GIT_MODIFIED = NO_COLOR ? "" : "\x1b[38;2;96;165;250m";
59
+ const GIT_ADDED = NO_COLOR ? "" : "\x1b[38;2;59;130;246m";
60
+ const GIT_STASH = NO_COLOR ? "" : "\x1b[38;2;165;180;252m";
61
+ const GIT_AGE_FRESH = NO_COLOR ? "" : "\x1b[38;2;125;211;252m";
62
+ const GIT_AGE_RECENT = NO_COLOR ? "" : "\x1b[38;2;96;165;250m";
63
+ const GIT_AGE_STALE = NO_COLOR ? "" : "\x1b[38;2;59;130;246m";
64
+ const GIT_AGE_OLD = NO_COLOR ? "" : "\x1b[38;2;99;102;241m";
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // Display modes
68
+ // ---------------------------------------------------------------------------
69
+
70
+ function getTerminalWidth(): number {
71
+ const colsEnv = process.env.COLUMNS;
72
+ if (colsEnv) {
73
+ const cols = parseInt(colsEnv, 10);
74
+ if (cols > 0) return cols;
75
+ }
76
+ try {
77
+ if (process.stdout.columns && process.stdout.columns > 0) {
78
+ return process.stdout.columns;
79
+ }
80
+ } catch { /* ignore */ }
81
+ return 80;
82
+ }
83
+
84
+ function getDisplayMode(width: number): string {
85
+ if (width < 35) return "nano";
86
+ if (width < 55) return "micro";
87
+ if (width < 80) return "mini";
88
+ return "normal";
89
+ }
90
+
91
+ // ---------------------------------------------------------------------------
92
+ // Color helpers
93
+ // ---------------------------------------------------------------------------
94
+
95
+ function getBucketColor(pos: number, maxPos: number): string {
96
+ if (NO_COLOR) return "";
97
+ const pct = Math.floor((pos * 100) / maxPos);
98
+
99
+ let r: number, g: number, b: number;
100
+
101
+ if (pct <= 33) {
102
+ r = 74 + Math.floor(((250 - 74) * pct) / 33);
103
+ g = 222 + Math.floor(((204 - 222) * pct) / 33);
104
+ b = 128 + Math.floor(((21 - 128) * pct) / 33);
105
+ } else if (pct <= 66) {
106
+ const t = pct - 33;
107
+ r = 250 + Math.floor(((251 - 250) * t) / 33);
108
+ g = 204 + Math.floor(((146 - 204) * t) / 33);
109
+ b = 21 + Math.floor(((60 - 21) * t) / 33);
110
+ } else {
111
+ const t = pct - 66;
112
+ r = 251 + Math.floor(((239 - 251) * t) / 34);
113
+ g = 146 + Math.floor(((68 - 146) * t) / 34);
114
+ b = 60 + Math.floor(((68 - 60) * t) / 34);
115
+ }
116
+
117
+ return `\x1b[38;2;${r};${g};${b}m`;
118
+ }
119
+
120
+ // ---------------------------------------------------------------------------
121
+ // Context bar rendering
122
+ // ---------------------------------------------------------------------------
123
+
124
+ function renderContextBar(width: number, pct: number): [string, string] {
125
+ pct = Math.max(0, Math.min(100, pct));
126
+ const filled = Math.floor((pct * width) / 100);
127
+ let lastColor = EMERALD;
128
+ const parts: string[] = [];
129
+
130
+ for (let i = 1; i <= width; i++) {
131
+ if (i <= filled) {
132
+ const color = getBucketColor(i, width);
133
+ lastColor = color;
134
+ parts.push(`${color}\u26c1${RESET}`);
135
+ } else {
136
+ parts.push(`${CTX_BUCKET_EMPTY}\u26c1${RESET}`);
137
+ }
138
+ if (width > 8) {
139
+ parts.push(" ");
140
+ }
141
+ }
142
+
143
+ return [parts.join("").trimEnd(), lastColor];
144
+ }
145
+
146
+ // ---------------------------------------------------------------------------
147
+ // Separator
148
+ // ---------------------------------------------------------------------------
149
+
150
+ const SEPARATOR = `${SLATE_600}${"─".repeat(72)}${RESET}`;
151
+
152
+ // ---------------------------------------------------------------------------
153
+ // Context section
154
+ // ---------------------------------------------------------------------------
155
+
156
+ function shortenModel(name: string): string {
157
+ const replacements: [string, string][] = [
158
+ ["claude-opus-4-6", "opus-4.6"],
159
+ ["claude-opus-4-5", "opus-4.5"],
160
+ ["claude-sonnet-4", "sonnet-4"],
161
+ ["claude-3-5-sonnet", "sonnet-3.5"],
162
+ ["claude-3-5-haiku", "haiku-3.5"],
163
+ ["claude-", ""],
164
+ ];
165
+ let result = name;
166
+ for (const [old, replacement] of replacements) {
167
+ result = result.replace(old, replacement);
168
+ }
169
+ return result;
170
+ }
171
+
172
+ function renderContext(
173
+ mode: string,
174
+ contextPct: number,
175
+ contextK: number,
176
+ maxK: number,
177
+ timeDisplay: string,
178
+ modelName: string,
179
+ ): void {
180
+ let pctColor: string;
181
+ if (contextPct <= 33) pctColor = EMERALD;
182
+ else if (contextPct <= 66) pctColor = AMBER;
183
+ else pctColor = ROSE;
184
+
185
+ const shortModel = shortenModel(modelName);
186
+
187
+ if (mode === "nano") {
188
+ const [bar] = renderContextBar(5, contextPct);
189
+ console.log(
190
+ `${CTX_PRIMARY}\u25c9${RESET} ${CTX_ACCENT}${shortModel}${RESET} ` +
191
+ `${bar} ${pctColor}${contextPct}%${RESET} ` +
192
+ `${CTX_ACCENT}\u23f1${RESET} ${SLATE_300}${timeDisplay}${RESET}`,
193
+ );
194
+ } else if (mode === "micro") {
195
+ const [bar] = renderContextBar(6, contextPct);
196
+ console.log(
197
+ `${CTX_PRIMARY}\u25c9${RESET} ${CTX_ACCENT}${shortModel}${RESET} ` +
198
+ `${SLATE_600}\u2502${RESET} ` +
199
+ `${bar} ${pctColor}${contextPct}%${RESET} ${SLATE_500}(${contextK}k)${RESET} ` +
200
+ `${CTX_ACCENT}\u23f1${RESET} ${SLATE_300}${timeDisplay}${RESET}`,
201
+ );
202
+ } else if (mode === "mini") {
203
+ const [bar] = renderContextBar(8, contextPct);
204
+ console.log(
205
+ `${CTX_PRIMARY}\u25c9${RESET} ${CTX_ACCENT}${shortModel}${RESET} ` +
206
+ `${SLATE_600}\u2502${RESET} ` +
207
+ `${CTX_SECONDARY}CTX:${RESET} ${bar} ` +
208
+ `${pctColor}${contextPct}%${RESET} ${SLATE_500}(${contextK}k/${maxK}k)${RESET} ` +
209
+ `${CTX_ACCENT}\u23f1${RESET} ${SLATE_300}${timeDisplay}${RESET}`,
210
+ );
211
+ } else {
212
+ const [bar, lastColor] = renderContextBar(16, contextPct);
213
+ console.log(
214
+ `${CTX_PRIMARY}\u25c9${RESET} ${CTX_SECONDARY}Model:${RESET} ${CTX_ACCENT}${shortModel}${RESET} ` +
215
+ `${SLATE_600}\u2502${RESET} ` +
216
+ `${CTX_SECONDARY}Context:${RESET} ${bar} ` +
217
+ `${lastColor}${contextPct}%${RESET} ${SLATE_500}(${contextK}k/${maxK}k)${RESET} ` +
218
+ `${SLATE_600}\u2502${RESET} ` +
219
+ `${CTX_ACCENT}\u23f1${RESET} ${SLATE_300}${timeDisplay}${RESET}`,
220
+ );
221
+ }
222
+
223
+ console.log(SEPARATOR);
224
+ }
225
+
226
+ // ---------------------------------------------------------------------------
227
+ // Git status
228
+ // ---------------------------------------------------------------------------
229
+
230
+ interface GitStatus {
231
+ branch: string;
232
+ modified: number;
233
+ staged: number;
234
+ untracked: number;
235
+ stash_count: number;
236
+ ahead: number;
237
+ behind: number;
238
+ age_display: string;
239
+ age_color: string;
240
+ }
241
+
242
+ function runGit(args: string[], cwd: string, timeout = 2000): string | null {
243
+ try {
244
+ const result = execFileSync("git", args, {
245
+ cwd,
246
+ timeout,
247
+ encoding: "utf-8",
248
+ stdio: ["pipe", "pipe", "pipe"],
249
+ windowsHide: true,
250
+ });
251
+ return result.trim();
252
+ } catch {
253
+ return null;
254
+ }
255
+ }
256
+
257
+ function getGitStatus(cwd: string): GitStatus | null {
258
+ if (runGit(["rev-parse", "--git-dir"], cwd) === null) {
259
+ return null;
260
+ }
261
+
262
+ const status: GitStatus = {
263
+ branch: "detached",
264
+ modified: 0,
265
+ staged: 0,
266
+ untracked: 0,
267
+ stash_count: 0,
268
+ ahead: 0,
269
+ behind: 0,
270
+ age_display: "",
271
+ age_color: GIT_AGE_FRESH,
272
+ };
273
+
274
+ // Branch
275
+ const branch = runGit(["branch", "--show-current"], cwd);
276
+ if (branch) status.branch = branch;
277
+
278
+ // Modified files
279
+ const diff = runGit(["diff", "--name-only"], cwd);
280
+ if (diff) status.modified = diff.split(/\r?\n/).filter(Boolean).length;
281
+
282
+ // Staged files
283
+ const staged = runGit(["diff", "--cached", "--name-only"], cwd);
284
+ if (staged) status.staged = staged.split(/\r?\n/).filter(Boolean).length;
285
+
286
+ // Untracked files
287
+ const untracked = runGit(["ls-files", "--others", "--exclude-standard"], cwd);
288
+ if (untracked) status.untracked = untracked.split(/\r?\n/).filter(Boolean).length;
289
+
290
+ // Stash count
291
+ const stash = runGit(["stash", "list"], cwd);
292
+ if (stash) status.stash_count = stash.split(/\r?\n/).filter(Boolean).length;
293
+
294
+ // Ahead/behind
295
+ const ab = runGit(["rev-list", "--left-right", "--count", "HEAD...@{u}"], cwd);
296
+ if (ab) {
297
+ const parts = ab.split(/\s+/);
298
+ if (parts.length >= 2) {
299
+ status.ahead = parseInt(parts[0]!, 10) || 0;
300
+ status.behind = parseInt(parts[1]!, 10) || 0;
301
+ }
302
+ }
303
+
304
+ // Commit age
305
+ const log = runGit(["log", "-1", "--format=%ct"], cwd);
306
+ if (log) {
307
+ try {
308
+ const lastEpoch = parseInt(log, 10);
309
+ const nowEpoch = Math.floor(Date.now() / 1000);
310
+ const ageSec = nowEpoch - lastEpoch;
311
+ const ageMin = Math.floor(ageSec / 60);
312
+ const ageHrs = Math.floor(ageSec / 3600);
313
+ const ageDays = Math.floor(ageSec / 86400);
314
+
315
+ if (ageMin < 1) {
316
+ status.age_display = "now";
317
+ status.age_color = GIT_AGE_FRESH;
318
+ } else if (ageHrs < 1) {
319
+ status.age_display = `${ageMin}m`;
320
+ status.age_color = GIT_AGE_FRESH;
321
+ } else if (ageHrs < 24) {
322
+ status.age_display = `${ageHrs}h`;
323
+ status.age_color = GIT_AGE_RECENT;
324
+ } else if (ageDays < 7) {
325
+ status.age_display = `${ageDays}d`;
326
+ status.age_color = GIT_AGE_STALE;
327
+ } else {
328
+ status.age_display = `${ageDays}d`;
329
+ status.age_color = GIT_AGE_OLD;
330
+ }
331
+ } catch { /* ignore */ }
332
+ }
333
+
334
+ return status;
335
+ }
336
+
337
+ function renderGit(mode: string, git: GitStatus, dirName: string): void {
338
+ const totalChanged = git.modified + git.staged;
339
+ const statusIcon = (totalChanged > 0 || git.untracked > 0) ? "*" : "\u2713";
340
+
341
+ if (mode === "nano") {
342
+ let line = `${GIT_PRIMARY}\u25c8${RESET} ${GIT_DIR}${dirName}${RESET} ${GIT_VALUE}${git.branch}${RESET} `;
343
+ if (statusIcon === "\u2713") {
344
+ line += `${GIT_CLEAN}\u2713${RESET}`;
345
+ } else {
346
+ line += `${GIT_MODIFIED}*${totalChanged}${RESET}`;
347
+ }
348
+ console.log(line);
349
+ } else if (mode === "micro") {
350
+ let line = `${GIT_PRIMARY}\u25c8${RESET} ${GIT_DIR}${dirName}${RESET} ${GIT_VALUE}${git.branch}${RESET}`;
351
+ if (git.age_display) {
352
+ line += ` ${git.age_color}${git.age_display}${RESET}`;
353
+ }
354
+ line += " ";
355
+ if (statusIcon === "\u2713") {
356
+ line += `${GIT_CLEAN}${statusIcon}${RESET}`;
357
+ } else {
358
+ line += `${GIT_MODIFIED}${statusIcon}${totalChanged}${RESET}`;
359
+ }
360
+ console.log(line);
361
+ } else if (mode === "mini") {
362
+ let line =
363
+ `${GIT_PRIMARY}\u25c8${RESET} ${GIT_DIR}${dirName}${RESET} ` +
364
+ `${SLATE_600}\u2502${RESET} ${GIT_VALUE}${git.branch}${RESET}`;
365
+ if (git.age_display) {
366
+ line += ` ${SLATE_600}\u2502${RESET} ${git.age_color}${git.age_display}${RESET}`;
367
+ }
368
+ line += ` ${SLATE_600}\u2502${RESET} `;
369
+ if (statusIcon === "\u2713") {
370
+ line += `${GIT_CLEAN}${statusIcon}${RESET}`;
371
+ } else {
372
+ line += `${GIT_MODIFIED}${statusIcon}${totalChanged}${RESET}`;
373
+ if (git.untracked > 0) {
374
+ line += ` ${GIT_ADDED}+${git.untracked}${RESET}`;
375
+ }
376
+ }
377
+ console.log(line);
378
+ } else {
379
+ let line =
380
+ `${GIT_PRIMARY}\u25c8${RESET} ${GIT_PRIMARY}PWD:${RESET} ${GIT_DIR}${dirName}${RESET} ` +
381
+ `${SLATE_600}\u2502${RESET} ` +
382
+ `${GIT_PRIMARY}Branch:${RESET} ${GIT_VALUE}${git.branch}${RESET}`;
383
+ if (git.age_display) {
384
+ line += ` ${SLATE_600}\u2502${RESET} ${GIT_PRIMARY}Age:${RESET} ${git.age_color}${git.age_display}${RESET}`;
385
+ }
386
+ if (git.stash_count > 0) {
387
+ line += ` ${SLATE_600}\u2502${RESET} ${GIT_PRIMARY}Stash:${RESET} ${GIT_STASH}${git.stash_count}${RESET}`;
388
+ }
389
+
390
+ if (totalChanged > 0 || git.untracked > 0) {
391
+ line += ` ${SLATE_600}\u2502${RESET} `;
392
+ if (totalChanged > 0) {
393
+ line += `${GIT_PRIMARY}Mod:${RESET} ${GIT_MODIFIED}${totalChanged}${RESET}`;
394
+ }
395
+ if (git.untracked > 0) {
396
+ if (totalChanged > 0) line += " ";
397
+ line += `${GIT_PRIMARY}New:${RESET} ${GIT_ADDED}${git.untracked}${RESET}`;
398
+ }
399
+ } else {
400
+ line += ` ${SLATE_600}\u2502${RESET} ${GIT_CLEAN}\u2713 clean${RESET}`;
401
+ }
402
+
403
+ if (git.ahead > 0 || git.behind > 0) {
404
+ line += ` ${SLATE_600}\u2502${RESET} ${GIT_PRIMARY}Sync:${RESET} `;
405
+ if (git.ahead > 0) {
406
+ line += `${GIT_CLEAN}\u2191${git.ahead}${RESET}`;
407
+ }
408
+ if (git.behind > 0) {
409
+ line += `${GIT_STASH}\u2193${git.behind}${RESET}`;
410
+ }
411
+ }
412
+ console.log(line);
413
+ }
414
+ }
415
+
416
+ // ---------------------------------------------------------------------------
417
+ // Context manager line (line 3)
418
+ // ---------------------------------------------------------------------------
419
+
420
+ function findActivePlanFile(): string | null {
421
+ try {
422
+ const plansDir = path.join(homedir(), ".claude", "plans");
423
+ if (!fs.existsSync(plansDir)) return null;
424
+ const planFiles = fs.readdirSync(plansDir)
425
+ .filter(f => f.endsWith(".md"))
426
+ .map(f => {
427
+ const fullPath = path.join(plansDir, f);
428
+ return { path: fullPath, mtime: fs.statSync(fullPath).mtimeMs };
429
+ })
430
+ .sort((a, b) => b.mtime - a.mtime);
431
+ return planFiles.length > 0 ? planFiles[0]!.path : null;
432
+ } catch {
433
+ return null;
434
+ }
435
+ }
436
+
437
+ function renderContextManager(
438
+ mode: string,
439
+ contextId: string,
440
+ contextState: Record<string, any> | null,
441
+ ): void {
442
+ // Strip YYMMDD-HHMM- timestamp prefix from context ID for display
443
+ let displayId = contextId.replace(/^\d{6}-\d{4}-/, "");
444
+ if (!displayId) displayId = contextId;
445
+
446
+ // Truncate display_id per mode
447
+ const maxIdLen: Record<string, number> = { nano: 14, micro: 18, mini: 22, normal: 30 };
448
+ const maxLen = maxIdLen[mode] ?? 30;
449
+ let truncatedId = displayId.slice(0, maxLen);
450
+ if (displayId.length > maxLen) truncatedId += "\u2026";
451
+
452
+ // Read state fields
453
+ const stateMode = contextState?.mode ?? "idle";
454
+ const statePlanPath = contextState?.plan_path ?? null;
455
+
456
+ // Detect plan mode heuristic
457
+ const activePlanFile = findActivePlanFile();
458
+ const isPlanning = stateMode === "idle" && activePlanFile !== null;
459
+
460
+ // Build mode badge
461
+ let modeBadge = "";
462
+ if (isPlanning) {
463
+ const label = mode === "nano" ? "Plan" : "Planning";
464
+ modeBadge = ` ${SLATE_600}\u2502${RESET} ${CTX_SECONDARY}Mode:${RESET} ${AMBER}${label}${RESET}`;
465
+ } else if (stateMode === "has_plan") {
466
+ const label = mode === "nano" ? "Ready" : "Plan Ready";
467
+ modeBadge = ` ${SLATE_600}\u2502${RESET} ${CTX_SECONDARY}Mode:${RESET} ${EMERALD}${label}${RESET}`;
468
+ } else if (stateMode === "active") {
469
+ const label = "Active";
470
+ modeBadge = ` ${SLATE_600}\u2502${RESET} ${CTX_SECONDARY}Mode:${RESET} ${CTX_ACCENT}${label}${RESET}`;
471
+ }
472
+
473
+ // Resolve plan file path for display
474
+ let planFilePath: string | null = null;
475
+ if (isPlanning) {
476
+ planFilePath = activePlanFile;
477
+ } else if (statePlanPath) {
478
+ planFilePath = statePlanPath;
479
+ } else if (stateMode === "has_plan" || stateMode === "active") {
480
+ try {
481
+ planFilePath = findLatestPlan(contextId) ?? null;
482
+ } catch { /* ignore */ }
483
+ }
484
+
485
+ // Build plan name (mini/normal only)
486
+ let planPart = "";
487
+ if ((mode === "mini" || mode === "normal") && planFilePath) {
488
+ const planStem = path.basename(planFilePath, path.extname(planFilePath))
489
+ .replace(/^\d{4}-\d{2}-\d{2}-(\d{4}-)?/, "");
490
+ const maxPlanLen = mode === "mini" ? 20 : 30;
491
+ let truncatedPlan = planStem.slice(0, maxPlanLen);
492
+ if (planStem.length > maxPlanLen) truncatedPlan += "\u2026";
493
+ planPart = ` ${SLATE_600}\u2502${RESET} ${CTX_SECONDARY}Plan:${RESET} ${SLATE_300}${truncatedPlan}${RESET}`;
494
+ }
495
+
496
+ if (mode === "nano") {
497
+ console.log(`${CTX_ACCENT}\u25c6${RESET} ${SLATE_400}${truncatedId}${RESET}${modeBadge}`);
498
+ } else if (mode === "micro") {
499
+ console.log(`${CTX_ACCENT}\u25c6${RESET} ${SLATE_400}${truncatedId}${RESET}${modeBadge}`);
500
+ } else if (mode === "mini") {
501
+ console.log(
502
+ `${CTX_ACCENT}\u25c6${RESET} ${SLATE_400}${truncatedId}${RESET}` +
503
+ `${modeBadge}${planPart}`,
504
+ );
505
+ } else {
506
+ console.log(
507
+ `${CTX_ACCENT}\u25c6${RESET} ${CTX_SECONDARY}Context:${RESET} ${SLATE_300}${truncatedId}${RESET}` +
508
+ `${modeBadge}${planPart}`,
509
+ );
510
+ }
511
+ }
512
+
513
+ function renderNoContext(mode: string): void {
514
+ const warn = `${ROSE}\u26a0 ${RESET}`;
515
+ if (mode === "normal") {
516
+ console.log(`${warn} ${ROSE}NO CONTEXT${RESET} ${SLATE_500}\u2014 type ^ for context manager${RESET}`);
517
+ } else {
518
+ console.log(`${warn} ${ROSE}NO CONTEXT${RESET}`);
519
+ }
520
+ }
521
+
522
+ // ---------------------------------------------------------------------------
523
+ // Context persistence
524
+ // ---------------------------------------------------------------------------
525
+
526
+ interface StatuslineCache {
527
+ sessions?: Record<string, { context_id: string | null }>;
528
+ }
529
+
530
+ function loadCache(): StatuslineCache {
531
+ try {
532
+ if (fs.existsSync(STATUSLINE_CACHE)) {
533
+ return JSON.parse(fs.readFileSync(STATUSLINE_CACHE, "utf-8"));
534
+ }
535
+ } catch { /* ignore */ }
536
+ return {};
537
+ }
538
+
539
+ function saveCache(cache: StatuslineCache): void {
540
+ try {
541
+ fs.mkdirSync(path.dirname(STATUSLINE_CACHE), { recursive: true });
542
+ fs.writeFileSync(STATUSLINE_CACHE, JSON.stringify(cache, null, 2), "utf-8");
543
+ } catch { /* ignore */ }
544
+ }
545
+
546
+ function resolveContextId(sessionId: string): string | null {
547
+ if (!sessionId || sessionId === "unknown") return null;
548
+
549
+ // Check cache first
550
+ const cache = loadCache();
551
+ const cachedEntry = cache.sessions?.[sessionId];
552
+ if (cachedEntry && cachedEntry.context_id !== undefined) {
553
+ return cachedEntry.context_id;
554
+ }
555
+
556
+ // Cache miss — look up via context manager
557
+ try {
558
+ const context = getContextBySessionId(sessionId);
559
+ if (context) {
560
+ if (!cache.sessions) cache.sessions = {};
561
+ cache.sessions[sessionId] = { context_id: (context as any).id };
562
+ saveCache(cache);
563
+ return (context as any).id;
564
+ }
565
+ } catch { /* ignore */ }
566
+
567
+ // Don't cache negative results — context may be bound by a later hook
568
+ return null;
569
+ }
570
+
571
+ function loadContextState(contextId: string): Record<string, any> | null {
572
+ try {
573
+ return loadState(contextId) as Record<string, any> | null;
574
+ } catch {
575
+ return null;
576
+ }
577
+ }
578
+
579
+ function writeContextWindow(contextId: string, contextWindowData: Record<string, any>): void {
580
+ try {
581
+ const state = getContext(contextId) as Record<string, any> | null;
582
+ if (state) {
583
+ if (!state.last_session) state.last_session = {};
584
+ state.last_session.context_remaining_pct = contextWindowData.remaining_percentage;
585
+ saveState(contextId, state as any);
586
+ }
587
+ } catch { /* ignore */ }
588
+ }
589
+
590
+ // ---------------------------------------------------------------------------
591
+ // Main
592
+ // ---------------------------------------------------------------------------
593
+
594
+ function main(): void {
595
+ // Read JSON from stdin
596
+ let inputData: Record<string, any>;
597
+ try {
598
+ inputData = JSON.parse(fs.readFileSync(0, "utf-8"));
599
+ } catch {
600
+ inputData = {};
601
+ }
602
+
603
+ // Terminal width and mode
604
+ const termWidth = getTerminalWidth();
605
+ const mode = getDisplayMode(termWidth);
606
+
607
+ // Extract input fields
608
+ const sessionId = inputData.session_id ?? "";
609
+ const modelName = inputData.model?.display_name ?? "unknown";
610
+ const cost = inputData.cost ?? {};
611
+ const durationMs: number = cost.total_duration_ms ?? 0;
612
+ const workspace = inputData.workspace ?? {};
613
+ const currentDir: string = workspace.project_dir ?? process.cwd();
614
+ const dirName = path.basename(currentDir);
615
+
616
+ // Context window data
617
+ const ctxWin = inputData.context_window ?? {};
618
+ const usage = ctxWin.current_usage ?? {};
619
+ const cacheRead: number = usage.cache_read_input_tokens ?? 0;
620
+ const inputTokens: number = usage.input_tokens ?? 0;
621
+ const cacheCreation: number = usage.cache_creation_input_tokens ?? 0;
622
+ const outputTokens: number = usage.output_tokens ?? 0;
623
+ const contextMax: number = ctxWin.context_window_size ?? 200000;
624
+
625
+ // Calculate context percentage
626
+ const usedPct = ctxWin.used_percentage;
627
+ let contextPct: number;
628
+ const totalInput = cacheRead + inputTokens + cacheCreation;
629
+ const contextUsed = totalInput + outputTokens + CONTEXT_BASELINE_TOKENS;
630
+
631
+ if (usedPct !== undefined && usedPct !== null) {
632
+ contextPct = Math.floor(usedPct);
633
+ } else {
634
+ contextPct = contextMax > 0 ? Math.floor((contextUsed * 100) / contextMax) : 0;
635
+ }
636
+
637
+ const contextK = Math.floor(contextUsed / 1000);
638
+ const maxK = Math.floor(contextMax / 1000);
639
+
640
+ // Format duration
641
+ const durationSec = Math.floor(durationMs / 1000);
642
+ let timeDisplay: string;
643
+ if (durationSec >= 3600) {
644
+ timeDisplay = `${Math.floor(durationSec / 3600)}h${Math.floor((durationSec % 3600) / 60)}m`;
645
+ } else if (durationSec >= 60) {
646
+ timeDisplay = `${Math.floor(durationSec / 60)}m${durationSec % 60}s`;
647
+ } else {
648
+ timeDisplay = `${durationSec}s`;
649
+ }
650
+
651
+ // Resolve context ID for display and persistence
652
+ const contextId = resolveContextId(sessionId);
653
+
654
+ // Render context section
655
+ renderContext(mode, contextPct, contextK, maxK, timeDisplay, modelName);
656
+
657
+ // Render git section
658
+ const git = getGitStatus(currentDir);
659
+ if (git) {
660
+ renderGit(mode, git, dirName);
661
+ }
662
+
663
+ // Render context manager line (line 3) with separator
664
+ console.log(SEPARATOR);
665
+ if (contextId) {
666
+ const contextState = loadContextState(contextId);
667
+ renderContextManager(mode, contextId, contextState);
668
+ } else {
669
+ renderNoContext(mode);
670
+ }
671
+
672
+ // Persist context_window to state.json
673
+ if (contextId) {
674
+ writeContextWindow(contextId, {
675
+ used_percentage: contextPct,
676
+ remaining_percentage: 100 - contextPct,
677
+ context_window_size: contextMax,
678
+ tokens_used: contextUsed,
679
+ total_input_tokens: totalInput,
680
+ total_output_tokens: outputTokens,
681
+ model: modelName,
682
+ last_updated: new Date().toISOString().split(".")[0],
683
+ });
684
+ }
685
+ }
686
+
687
+ main();