cc-context-stats 1.8.0 → 1.8.2

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 (106) hide show
  1. package/package.json +8 -1
  2. package/scripts/context-stats.sh +1 -1
  3. package/.editorconfig +0 -60
  4. package/.eslintrc.json +0 -35
  5. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -49
  6. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -31
  7. package/.github/PULL_REQUEST_TEMPLATE.md +0 -33
  8. package/.github/dependabot.yml +0 -44
  9. package/.github/workflows/ci.yml +0 -294
  10. package/.github/workflows/release.yml +0 -151
  11. package/.pre-commit-config.yaml +0 -74
  12. package/.prettierrc +0 -33
  13. package/.shellcheckrc +0 -10
  14. package/CHANGELOG.md +0 -187
  15. package/CLAUDE.md +0 -66
  16. package/CODE_OF_CONDUCT.md +0 -59
  17. package/CONTRIBUTING.md +0 -240
  18. package/RELEASE_NOTES.md +0 -19
  19. package/SECURITY.md +0 -44
  20. package/TODOS.md +0 -72
  21. package/assets/logo/favicon.svg +0 -19
  22. package/assets/logo/logo-black.svg +0 -24
  23. package/assets/logo/logo-full.svg +0 -40
  24. package/assets/logo/logo-icon.svg +0 -27
  25. package/assets/logo/logo-mark.svg +0 -28
  26. package/assets/logo/logo-white.svg +0 -24
  27. package/assets/logo/logo-wordmark.svg +0 -6
  28. package/config/settings-example.json +0 -7
  29. package/config/settings-node.json +0 -7
  30. package/config/settings-python.json +0 -7
  31. package/docs/ARCHITECTURE.md +0 -128
  32. package/docs/CSV_FORMAT.md +0 -42
  33. package/docs/DEPLOYMENT.md +0 -71
  34. package/docs/DEVELOPMENT.md +0 -161
  35. package/docs/MODEL_INTELLIGENCE.md +0 -396
  36. package/docs/configuration.md +0 -118
  37. package/docs/context-stats.md +0 -143
  38. package/docs/installation.md +0 -255
  39. package/docs/scripts.md +0 -140
  40. package/docs/troubleshooting.md +0 -278
  41. package/images/claude-statusline-token-graph.gif +0 -0
  42. package/images/claude-statusline.png +0 -0
  43. package/images/context-status-dumbzone.png +0 -0
  44. package/images/context-status.png +0 -0
  45. package/images/statusline-detail.png +0 -0
  46. package/images/token-graph.jpeg +0 -0
  47. package/images/token-graph.png +0 -0
  48. package/images/v1.6.1.png +0 -0
  49. package/install +0 -351
  50. package/install.sh +0 -298
  51. package/jest.config.js +0 -11
  52. package/pyproject.toml +0 -115
  53. package/requirements-dev.txt +0 -12
  54. package/scripts/statusline-full.sh +0 -438
  55. package/scripts/statusline-git.sh +0 -88
  56. package/scripts/statusline-minimal.sh +0 -67
  57. package/scripts/statusline.py +0 -569
  58. package/src/claude_statusline/__init__.py +0 -11
  59. package/src/claude_statusline/__main__.py +0 -6
  60. package/src/claude_statusline/cli/__init__.py +0 -1
  61. package/src/claude_statusline/cli/context_stats.py +0 -542
  62. package/src/claude_statusline/cli/explain.py +0 -228
  63. package/src/claude_statusline/cli/statusline.py +0 -184
  64. package/src/claude_statusline/core/__init__.py +0 -1
  65. package/src/claude_statusline/core/colors.py +0 -124
  66. package/src/claude_statusline/core/config.py +0 -165
  67. package/src/claude_statusline/core/git.py +0 -78
  68. package/src/claude_statusline/core/state.py +0 -323
  69. package/src/claude_statusline/formatters/__init__.py +0 -1
  70. package/src/claude_statusline/formatters/layout.py +0 -67
  71. package/src/claude_statusline/formatters/time.py +0 -50
  72. package/src/claude_statusline/formatters/tokens.py +0 -70
  73. package/src/claude_statusline/graphs/__init__.py +0 -1
  74. package/src/claude_statusline/graphs/intelligence.py +0 -162
  75. package/src/claude_statusline/graphs/renderer.py +0 -401
  76. package/src/claude_statusline/graphs/statistics.py +0 -92
  77. package/src/claude_statusline/ui/__init__.py +0 -1
  78. package/src/claude_statusline/ui/icons.py +0 -93
  79. package/src/claude_statusline/ui/waiting.py +0 -62
  80. package/tests/bash/test_delta_parity.bats +0 -199
  81. package/tests/bash/test_install.bats +0 -29
  82. package/tests/bash/test_parity.bats +0 -315
  83. package/tests/bash/test_statusline_full.bats +0 -139
  84. package/tests/bash/test_statusline_git.bats +0 -42
  85. package/tests/bash/test_statusline_minimal.bats +0 -37
  86. package/tests/fixtures/json/comma_in_path.json +0 -31
  87. package/tests/fixtures/json/high_usage.json +0 -17
  88. package/tests/fixtures/json/low_usage.json +0 -17
  89. package/tests/fixtures/json/medium_usage.json +0 -17
  90. package/tests/fixtures/json/valid_full.json +0 -30
  91. package/tests/fixtures/json/valid_minimal.json +0 -9
  92. package/tests/fixtures/mi_test_vectors.json +0 -140
  93. package/tests/node/intelligence.test.js +0 -98
  94. package/tests/node/rotation.test.js +0 -89
  95. package/tests/node/statusline.test.js +0 -240
  96. package/tests/python/conftest.py +0 -84
  97. package/tests/python/test_colors.py +0 -105
  98. package/tests/python/test_config_colors.py +0 -78
  99. package/tests/python/test_data_pipeline.py +0 -446
  100. package/tests/python/test_explain.py +0 -177
  101. package/tests/python/test_icons.py +0 -152
  102. package/tests/python/test_intelligence.py +0 -314
  103. package/tests/python/test_layout.py +0 -127
  104. package/tests/python/test_state_rotation_validation.py +0 -232
  105. package/tests/python/test_statusline.py +0 -215
  106. package/tests/python/test_waiting.py +0 -127
@@ -1,542 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Context Stats Visualizer for Claude Code.
3
-
4
- Displays ASCII graphs of token consumption over time.
5
-
6
- Usage:
7
- context-stats [session_id] [options]
8
-
9
- Options:
10
- --type <cumulative|delta|io|both|all> Graph type to display (default: both)
11
- --watch, -w [interval] Real-time monitoring mode (default: 2s)
12
- --no-color Disable color output
13
- --version, -V Show version and exit
14
- --help Show this help
15
- """
16
-
17
- from __future__ import annotations
18
-
19
- import argparse
20
- import signal
21
- import sys
22
- import time
23
- from pathlib import Path
24
-
25
- from claude_statusline import __version__
26
- from claude_statusline.core.colors import ColorManager
27
- from claude_statusline.core.config import Config
28
- from claude_statusline.core.state import StateFile, _validate_session_id
29
- from claude_statusline.graphs.renderer import GraphDimensions, GraphRenderer
30
- from claude_statusline.graphs.statistics import calculate_deltas
31
- from claude_statusline.ui.icons import get_activity_tier, get_tier_label
32
- from claude_statusline.ui.waiting import get_waiting_text, is_active
33
-
34
- # Cursor control sequences
35
- CURSOR_HOME = "\033[H"
36
- CLEAR_SCREEN = "\033[2J"
37
- HIDE_CURSOR = "\033[?25l"
38
- SHOW_CURSOR = "\033[?25h"
39
- CLEAR_TO_END = "\033[J"
40
-
41
-
42
- def show_help() -> None:
43
- """Show help message."""
44
- print(
45
- """Context Stats Visualizer for Claude Code
46
-
47
- USAGE:
48
- context-stats [session_id] [options]
49
- context-stats explain
50
-
51
- ARGUMENTS:
52
- session_id Optional session ID. If not provided, uses the latest session.
53
-
54
- COMMANDS:
55
- explain Diagnostic dump of Claude Code's JSON context (pipe JSON to stdin)
56
-
57
- OPTIONS:
58
- --type <type> Graph type to display:
59
- - delta: Context growth per interaction (default)
60
- - cumulative: Total context usage over time
61
- - io: Input/output tokens over time
62
- - mi: Model Intelligence score over time
63
- - both: Show cumulative and delta graphs
64
- - all: Show all graphs including I/O and MI
65
- -w [interval] Set refresh interval in seconds (default: 2)
66
- --no-watch Show graphs once and exit (disable live monitoring)
67
- --no-color Disable color output
68
- --version, -V Show version and exit
69
- --help Show this help message
70
-
71
- NOTE:
72
- By default, context-stats runs in live monitoring mode, refreshing every 2 seconds.
73
- Press Ctrl+C to exit. Use --no-watch to display graphs once and exit.
74
-
75
- EXAMPLES:
76
- # Live monitoring (default, refreshes every 2s)
77
- context-stats
78
-
79
- # Live monitoring with custom interval
80
- context-stats -w 5
81
-
82
- # Show graphs once and exit
83
- context-stats --no-watch
84
-
85
- # Show graphs for specific session
86
- context-stats abc123def
87
-
88
- # Show only cumulative graph
89
- context-stats --type cumulative
90
-
91
- # Combine options
92
- context-stats abc123 --type cumulative -w 3
93
-
94
- # Output to file (no colors, single run)
95
- context-stats --no-watch --no-color > output.txt
96
-
97
- # Diagnostic dump (pipe Claude Code JSON context)
98
- echo '{"model":{"display_name":"Opus"},...}' | context-stats explain
99
-
100
- DATA SOURCE:
101
- Reads token history from ~/.claude/statusline/statusline.<session_id>.state
102
- """
103
- )
104
-
105
-
106
- def parse_args() -> argparse.Namespace:
107
- """Parse command-line arguments."""
108
- parser = argparse.ArgumentParser(
109
- description="Context Stats Visualizer for Claude Code",
110
- add_help=False,
111
- )
112
- parser.add_argument("session_id", nargs="?", default=None, help="Session ID")
113
- parser.add_argument(
114
- "--type",
115
- choices=["cumulative", "delta", "io", "mi", "both", "all"],
116
- default="delta",
117
- help="Graph type to display (default: delta)",
118
- )
119
- parser.add_argument(
120
- "--watch",
121
- "-w",
122
- nargs="?",
123
- const=2,
124
- type=int,
125
- default=2,
126
- help="Watch mode interval in seconds (default: 2, use --no-watch to disable)",
127
- )
128
- parser.add_argument(
129
- "--no-watch",
130
- action="store_true",
131
- help="Disable watch mode (show graphs once and exit)",
132
- )
133
- parser.add_argument(
134
- "--no-color",
135
- action="store_true",
136
- help="Disable color output",
137
- )
138
- parser.add_argument(
139
- "--help",
140
- "-h",
141
- action="store_true",
142
- help="Show help message",
143
- )
144
- parser.add_argument(
145
- "--version",
146
- "-V",
147
- action="store_true",
148
- help="Show version and exit",
149
- )
150
-
151
- args = parser.parse_args()
152
-
153
- if args.version:
154
- print(f"cc-context-stats {__version__}")
155
- sys.exit(0)
156
-
157
- if args.help:
158
- show_help()
159
- sys.exit(0)
160
-
161
- if args.session_id is not None:
162
- try:
163
- _validate_session_id(args.session_id)
164
- except ValueError as e:
165
- sys.stderr.write(f"Error: {e}\n")
166
- sys.exit(1)
167
-
168
- return args
169
-
170
-
171
- def render_once(
172
- state_file: StateFile,
173
- graph_type: str,
174
- renderer: GraphRenderer,
175
- colors: ColorManager,
176
- watch_mode: bool = False,
177
- config: Config | None = None,
178
- cycle_index: int = 0,
179
- ) -> bool | str:
180
- """Render graphs once.
181
-
182
- Args:
183
- state_file: StateFile instance
184
- graph_type: Type of graphs to render
185
- renderer: GraphRenderer instance
186
- colors: ColorManager instance
187
- watch_mode: Whether running in watch mode
188
- config: Config instance for motion settings
189
- cycle_index: Watch mode refresh counter for rotating text
190
-
191
- Returns:
192
- True if rendering was successful (non-watch mode),
193
- buffered string if watch_mode is True,
194
- False if not enough data
195
- """
196
- entries = state_file.read_history()
197
-
198
- if len(entries) < 2:
199
- msg = (
200
- f"\n{colors.yellow}Need at least 2 data points to generate graphs.{colors.reset}\n"
201
- f"{colors.dim}Found: {len(entries)} entry. Use Claude Code to accumulate more data.{colors.reset}"
202
- )
203
- if watch_mode:
204
- return msg
205
- print(msg)
206
- return False
207
-
208
- # In watch mode, buffer all output
209
- lines: list[str] = []
210
-
211
- def emit(line: str = "") -> None:
212
- if watch_mode:
213
- lines.append(line)
214
- else:
215
- print(line)
216
-
217
- # Extract data for graphs
218
- timestamps = [e.timestamp for e in entries]
219
- # Current context window usage (what's actually in the context)
220
- # This is: cache_read + cache_creation + current_input_tokens
221
- context_used = [e.current_used_tokens for e in entries]
222
- # Per-request I/O tokens from current_usage
223
- current_input = [e.current_input_tokens for e in entries]
224
- current_output = [e.current_output_tokens for e in entries]
225
- deltas = calculate_deltas(context_used)
226
- delta_times = timestamps[1:] # Deltas start from second entry
227
-
228
- # Get session name and project from entries
229
- file_path = state_file.find_latest_state_file()
230
- session_name = file_path.stem.replace("statusline.", "") if file_path else "unknown"
231
-
232
- # Get project name from the last entry (most recent)
233
- last_entry = entries[-1]
234
- project_name = ""
235
- if last_entry.workspace_project_dir:
236
- # Extract just the project folder name from the path
237
- project_name = Path(last_entry.workspace_project_dir).name
238
-
239
- # Header
240
- if not watch_mode:
241
- emit()
242
- if project_name:
243
- emit(
244
- f"{colors.bold}{colors.magenta}Context Stats{colors.reset} "
245
- f"{colors.dim}({colors.cyan}{project_name}{colors.dim} • {session_name}){colors.reset}"
246
- )
247
- else:
248
- emit(
249
- f"{colors.bold}{colors.magenta}Context Stats{colors.reset} "
250
- f"{colors.dim}(Session: {session_name}){colors.reset}"
251
- )
252
-
253
- # Activity indicator (waiting text + label)
254
- reduced_motion = config.reduced_motion if config else False
255
- tier = get_activity_tier(entries, last_entry.context_window_size)
256
- label = get_tier_label(tier)
257
- active = is_active(entries)
258
-
259
- if active:
260
- text = get_waiting_text(cycle_index, reduced_motion)
261
- emit(f" {colors.dim}{text} [{label}]{colors.reset}")
262
- else:
263
- emit(f" {colors.dim}{label}{colors.reset}")
264
-
265
- # In watch mode, enable renderer buffering
266
- if watch_mode:
267
- renderer.begin_buffering()
268
-
269
- # Render requested graphs
270
- if graph_type in ("cumulative", "both", "all"):
271
- renderer.render_timeseries(
272
- context_used, timestamps, "Context Usage Over Time", colors.green
273
- )
274
-
275
- if graph_type in ("delta", "both", "all"):
276
- renderer.render_timeseries(
277
- deltas, delta_times, "Context Growth Per Interaction", colors.cyan
278
- )
279
-
280
- if graph_type in ("io", "all"):
281
- renderer.render_timeseries(
282
- current_input, timestamps, "Input Tokens (per request)", colors.blue
283
- )
284
- renderer.render_timeseries(
285
- current_output, timestamps, "Output Tokens (per request)", colors.magenta
286
- )
287
-
288
- # Compute MI scores for graph and summary
289
- mi_score = None
290
- if graph_type in ("mi", "all") or True: # Always compute for summary
291
- from claude_statusline.graphs.intelligence import calculate_intelligence
292
-
293
- mi_config = config if config else Config.load()
294
- beta = mi_config.mi_curve_beta if config else 1.5
295
-
296
- mi_scores = []
297
- for i, entry in enumerate(entries):
298
- prev = entries[i - 1] if i > 0 else None
299
- ctx_window = entry.context_window_size
300
- score = calculate_intelligence(entry, prev, ctx_window, beta)
301
- mi_scores.append(score)
302
-
303
- if mi_scores:
304
- mi_score = mi_scores[-1]
305
-
306
- if graph_type in ("mi", "all") and len(mi_scores) > 0:
307
- # Scale MI scores to [0, 1000] for integer renderer
308
- mi_data = [int(s.mi * 1000) for s in mi_scores]
309
- renderer.render_timeseries(
310
- mi_data,
311
- timestamps,
312
- "Model Intelligence Over Time",
313
- colors.yellow,
314
- label_fn=lambda v: f"{v / 1000:.2f}",
315
- )
316
-
317
- # Summary and footer
318
- renderer.render_summary(entries, deltas, mi_score=mi_score)
319
- renderer.render_footer(__version__)
320
-
321
- if watch_mode:
322
- # Collect renderer buffer and combine with header lines
323
- renderer_output = renderer.get_buffer()
324
- lines.append(renderer_output)
325
- return "\n".join(lines)
326
-
327
- return True
328
-
329
-
330
- def run_watch_mode(
331
- state_file: StateFile,
332
- graph_type: str,
333
- interval: int,
334
- renderer: GraphRenderer,
335
- colors: ColorManager,
336
- config: Config | None = None,
337
- ) -> None:
338
- """Run in watch mode with continuous refresh.
339
-
340
- Args:
341
- state_file: StateFile instance
342
- graph_type: Type of graphs to render
343
- interval: Refresh interval in seconds
344
- renderer: GraphRenderer instance
345
- colors: ColorManager instance
346
- config: Config instance for motion settings
347
- """
348
-
349
- # Signal handler for clean exit
350
- def handle_signal(_signum: int, _frame: object) -> None:
351
- sys.stdout.write(SHOW_CURSOR)
352
- sys.stdout.flush()
353
- print(f"\n{colors.dim}Watch mode stopped.{colors.reset}")
354
- sys.exit(0)
355
-
356
- signal.signal(signal.SIGINT, handle_signal)
357
- signal.signal(signal.SIGTERM, handle_signal)
358
-
359
- # Hide cursor and initial clear in one write
360
- sys.stdout.write(f"{HIDE_CURSOR}{CLEAR_SCREEN}{CURSOR_HOME}")
361
- sys.stdout.flush()
362
-
363
- cycle_counter = 0
364
-
365
- try:
366
- while True:
367
- # Update dimensions in case of terminal resize
368
- renderer.dimensions = GraphDimensions.detect()
369
-
370
- # Build all output into a buffer
371
- buf_lines: list[str] = []
372
-
373
- # Watch mode indicator
374
- current_time = time.strftime("%H:%M:%S")
375
- buf_lines.append(
376
- f"{colors.dim}[LIVE {current_time}] Refresh: {interval}s | Ctrl+C to exit{colors.reset}"
377
- )
378
-
379
- # Check if state file exists now (may have been created since start)
380
- file_path = state_file.find_latest_state_file()
381
- if not file_path or not file_path.exists():
382
- # Show waiting message for new session
383
- reduced_motion = config.reduced_motion if config else False
384
- text = get_waiting_text(cycle_counter, reduced_motion)
385
- buf_lines.append(
386
- _format_waiting_message(
387
- colors,
388
- state_file.session_id,
389
- text,
390
- )
391
- )
392
- else:
393
- # Render graphs (returns buffered string in watch mode)
394
- result = render_once(
395
- state_file,
396
- graph_type,
397
- renderer,
398
- colors,
399
- watch_mode=True,
400
- config=config,
401
- cycle_index=cycle_counter,
402
- )
403
- if isinstance(result, str):
404
- buf_lines.append(result)
405
-
406
- # Atomic write: CURSOR_HOME + content + CLEAR_TO_END (clean up stale trailing lines)
407
- buffered_content = "\n".join(buf_lines)
408
- sys.stdout.write(f"{CURSOR_HOME}{buffered_content}\n{CLEAR_TO_END}")
409
- sys.stdout.flush()
410
-
411
- cycle_counter += 1
412
- time.sleep(interval)
413
- finally:
414
- sys.stdout.write(SHOW_CURSOR)
415
- sys.stdout.flush()
416
-
417
-
418
- def _format_waiting_message(
419
- colors: ColorManager,
420
- session_id: str | None,
421
- message: str = "Waiting for session data...",
422
- ) -> str:
423
- """Format a waiting message as a string.
424
-
425
- Args:
426
- colors: ColorManager instance
427
- session_id: Session ID if specified
428
- message: Message to display
429
-
430
- Returns:
431
- Formatted waiting message string
432
- """
433
- lines = [""]
434
- if session_id:
435
- lines.append(
436
- f"{colors.bold}{colors.magenta}Context Stats{colors.reset} "
437
- f"{colors.dim}(Session: {session_id}){colors.reset}"
438
- )
439
- else:
440
- lines.append(f"{colors.bold}{colors.magenta}Context Stats{colors.reset}")
441
- lines.append("")
442
- lines.append(f" {colors.cyan}⏳ {message}{colors.reset}")
443
- lines.append("")
444
- lines.append(
445
- f" {colors.dim}The session has just started and no data has been recorded yet.{colors.reset}"
446
- )
447
- lines.append(
448
- f" {colors.dim}Data will appear after the first Claude interaction.{colors.reset}"
449
- )
450
- lines.append("")
451
- return "\n".join(lines)
452
-
453
-
454
- def show_waiting_message(
455
- colors: ColorManager,
456
- session_id: str | None,
457
- message: str = "Waiting for session data...",
458
- ) -> None:
459
- """Show a friendly waiting message for new sessions.
460
-
461
- Args:
462
- colors: ColorManager instance
463
- session_id: Session ID if specified
464
- message: Message to display
465
- """
466
- print(_format_waiting_message(colors, session_id, message))
467
-
468
-
469
- def _ensure_utf8_stdout() -> None:
470
- """Reconfigure stdout/stderr to UTF-8 on Windows where cp1252 is the default."""
471
- if sys.stdout.encoding and sys.stdout.encoding.lower().replace("-", "") != "utf8":
472
- sys.stdout.reconfigure(encoding="utf-8", errors="replace") # type: ignore[union-attr]
473
- if sys.stderr.encoding and sys.stderr.encoding.lower().replace("-", "") != "utf8":
474
- sys.stderr.reconfigure(encoding="utf-8", errors="replace") # type: ignore[union-attr]
475
-
476
-
477
- def main() -> None:
478
- """Main entry point for context-stats CLI."""
479
- _ensure_utf8_stdout()
480
-
481
- # Handle 'explain' subcommand before argparse (it expects stdin JSON, not flags)
482
- if len(sys.argv) > 1 and sys.argv[1] == "explain":
483
- import json
484
-
485
- from claude_statusline.cli.explain import run_explain
486
-
487
- no_color = "--no-color" in sys.argv
488
- try:
489
- data = json.load(sys.stdin)
490
- except json.JSONDecodeError as e:
491
- sys.stderr.write(f"Error: invalid JSON on stdin: {e}\n")
492
- sys.stderr.write("Usage: echo '{...}' | context-stats explain\n")
493
- sys.exit(1)
494
- run_explain(data, no_color=no_color)
495
- return
496
-
497
- args = parse_args()
498
-
499
- # Load config for token_detail setting
500
- config = Config.load()
501
-
502
- # Setup colors with any user overrides from config
503
- color_enabled = not args.no_color and sys.stdout.isatty()
504
- colors = ColorManager(enabled=color_enabled, overrides=config.color_overrides)
505
-
506
- # Setup state file
507
- state_file = StateFile(args.session_id)
508
-
509
- # Find state file
510
- file_path = state_file.find_latest_state_file()
511
-
512
- # Handle case where no state file exists yet
513
- if not file_path or not file_path.exists():
514
- if args.no_watch:
515
- # Single run mode - show friendly message and exit
516
- if args.session_id:
517
- show_waiting_message(colors, args.session_id)
518
- else:
519
- print(f"{colors.yellow}No session data found.{colors.reset}")
520
- print(f"{colors.dim}Run Claude Code to generate token usage data.{colors.reset}")
521
- sys.exit(0)
522
- else:
523
- # Watch mode - continue and wait for data
524
- pass
525
-
526
- # Setup renderer
527
- renderer = GraphRenderer(
528
- colors=colors,
529
- token_detail=config.token_detail,
530
- )
531
-
532
- # Run
533
- if args.no_watch:
534
- success = render_once(state_file, args.type, renderer, colors, config=config)
535
- if not success:
536
- sys.exit(1)
537
- else:
538
- run_watch_mode(state_file, args.type, args.watch, renderer, colors, config=config)
539
-
540
-
541
- if __name__ == "__main__":
542
- main()