@@ -125,20 +125,88 @@ func registerUtilityActionCommands(r *Registry) {
125125}
126126
127127func cmdContext (ctx Context ) Result {
128- fmt .Printf ("%s── Context ─────────────────────────%s\n " , ColorDim , ColorReset )
129- if ctx .Agent != nil {
130- fmt .Printf (" Messages: %d\n " , len (ctx .Agent .Messages ))
131- }
128+ const barWidth = 24
129+
130+ inTok := 0
131+ outTok := 0
132+ cacheRd := 0
133+ cacheWr := 0
132134 if ctx .SessionInputTokens != nil {
133- fmt . Printf ( " Session input: ~%d tokens \n " , * ctx .SessionInputTokens )
135+ inTok = * ctx .SessionInputTokens
134136 }
135137 if ctx .SessionOutputTokens != nil {
136- fmt .Printf (" Session output: ~%d tokens\n " , * ctx .SessionOutputTokens )
138+ outTok = * ctx .SessionOutputTokens
139+ }
140+ if ctx .SessionCacheRead != nil {
141+ cacheRd = * ctx .SessionCacheRead
142+ }
143+ if ctx .SessionCacheWrite != nil {
144+ cacheWr = * ctx .SessionCacheWrite
145+ }
146+
147+ windowSize := 200000 // default assumption
148+ if ctx .ContextWindow != nil && * ctx .ContextWindow > 0 {
149+ windowSize = * ctx .ContextWindow
150+ }
151+
152+ used := inTok + outTok
153+ pct := 0.0
154+ if windowSize > 0 {
155+ pct = float64 (used ) / float64 (windowSize )
156+ if pct > 1.0 {
157+ pct = 1.0
158+ }
159+ }
160+
161+ filled := int (pct * float64 (barWidth ))
162+ if filled > barWidth {
163+ filled = barWidth
137164 }
138- fmt .Printf ("%s──────────────────────────────────%s\n \n " , ColorDim , ColorReset )
165+ bar := strings .Repeat ("█" , filled ) + strings .Repeat ("░" , barWidth - filled )
166+
167+ barColor := ColorLime
168+ if pct >= 0.85 {
169+ barColor = ColorRed
170+ } else if pct >= 0.6 {
171+ barColor = ColorYellow
172+ }
173+
174+ msgs := 0
175+ if ctx .Agent != nil {
176+ msgs = len (ctx .Agent .Messages )
177+ }
178+
179+ fmt .Printf ("%s── Context Window ────────────────────────────%s\n " , ColorDim , ColorReset )
180+ fmt .Printf (" %s[%s%s%s]%s %s%.0f%%%s (%s / %s tokens)\n " ,
181+ ColorDim , barColor , bar , ColorDim , ColorReset ,
182+ ColorBold , pct * 100 , ColorReset ,
183+ formatTokenCount (used ), formatTokenCount (windowSize ))
184+ fmt .Println ()
185+ fmt .Printf (" %-18s %s%s%s\n " , "Input tokens:" , ColorCyan , formatTokenCount (inTok ), ColorReset )
186+ fmt .Printf (" %-18s %s%s%s\n " , "Output tokens:" , ColorCyan , formatTokenCount (outTok ), ColorReset )
187+ if cacheRd > 0 || cacheWr > 0 {
188+ fmt .Printf (" %-18s %s%s read%s / %s%s write%s\n " ,
189+ "Cache:" ,
190+ ColorDim , formatTokenCount (cacheRd ), ColorReset ,
191+ ColorDim , formatTokenCount (cacheWr ), ColorReset )
192+ }
193+ fmt .Printf (" %-18s %d\n " , "Messages:" , msgs )
194+ fmt .Printf ("%s──────────────────────────────────────────────%s\n \n " , ColorDim , ColorReset )
139195 return Result {Handled : true }
140196}
141197
198+ // formatTokenCount formats a token count as "42k" or "1.2M" for readability.
199+ func formatTokenCount (n int ) string {
200+ switch {
201+ case n >= 1_000_000 :
202+ return fmt .Sprintf ("%.1fM" , float64 (n )/ 1_000_000 )
203+ case n >= 1_000 :
204+ return fmt .Sprintf ("%.1fk" , float64 (n )/ 1_000 )
205+ default :
206+ return fmt .Sprintf ("%d" , n )
207+ }
208+ }
209+
142210func cmdExport (ctx Context ) Result {
143211 if ctx .Agent == nil || len (ctx .Agent .Messages ) == 0 {
144212 PrintError ("no conversation to export" )
@@ -369,24 +437,55 @@ func savePins(repoPath string, pins []iteragent.Message) {
369437}
370438
371439func cmdPin (ctx Context ) Result {
440+ // /pin <text> — pin arbitrary text as a persistent user message.
441+ if ctx .HasArg (1 ) {
442+ text := ctx .Args ()
443+ pins := loadPins (ctx .RepoPath )
444+ pins = append (pins , iteragent.Message {Role : "user" , Content : text })
445+ savePins (ctx .RepoPath , pins )
446+ PrintSuccess ("pinned: %q — will survive /compact" , truncate (text , 60 ))
447+ return Result {Handled : true }
448+ }
449+ // /pin with no args — pin the last message in the conversation.
372450 if ctx .Agent == nil || len (ctx .Agent .Messages ) == 0 {
373- PrintError ("no messages to pin" )
451+ PrintError ("no messages to pin; use /pin <text> to pin arbitrary text " )
374452 return Result {Handled : true }
375453 }
376454 last := ctx .Agent .Messages [len (ctx .Agent .Messages )- 1 ]
377455 pins := loadPins (ctx .RepoPath )
378456 pins = append (pins , last )
379457 savePins (ctx .RepoPath , pins )
380- PrintSuccess ("message pinned — will survive /compact" )
458+ PrintSuccess ("last message pinned — will survive /compact" )
381459 return Result {Handled : true }
382460}
383461
384462func cmdUnpin (ctx Context ) Result {
463+ // /unpin <n> — remove nth pin (1-indexed); no arg clears all.
464+ pins := loadPins (ctx .RepoPath )
465+ if ctx .HasArg (1 ) {
466+ var n int
467+ fmt .Sscanf (ctx .Arg (1 ), "%d" , & n )
468+ if n < 1 || n > len (pins ) {
469+ PrintError ("pin index out of range (1–%d)" , len (pins ))
470+ return Result {Handled : true }
471+ }
472+ pins = append (pins [:n - 1 ], pins [n :]... )
473+ savePins (ctx .RepoPath , pins )
474+ PrintSuccess ("pin #%d removed (%d remaining)" , n , len (pins ))
475+ return Result {Handled : true }
476+ }
385477 savePins (ctx .RepoPath , nil )
386478 PrintSuccess ("all pins cleared" )
387479 return Result {Handled : true }
388480}
389481
482+ func truncate (s string , n int ) string {
483+ if len (s ) <= n {
484+ return s
485+ }
486+ return s [:n ] + "…"
487+ }
488+
390489func cmdRewind (ctx Context ) Result {
391490 n := 1
392491 if ctx .HasArg (1 ) {
0 commit comments