aiwcli 0.10.2 → 0.11.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.
- package/bin/run.js +1 -1
- package/dist/commands/clear.d.ts +11 -6
- package/dist/commands/clear.js +229 -381
- package/dist/commands/init/index.d.ts +1 -17
- package/dist/commands/init/index.js +22 -107
- package/dist/lib/gitignore-manager.d.ts +32 -0
- package/dist/lib/gitignore-manager.js +141 -2
- package/dist/lib/template-installer.d.ts +7 -12
- package/dist/lib/template-installer.js +69 -193
- package/dist/lib/template-settings-reconstructor.d.ts +35 -0
- package/dist/lib/template-settings-reconstructor.js +130 -0
- package/dist/templates/CLAUDE.md +8 -8
- package/dist/templates/_shared/.claude/commands/handoff-resume.md +64 -0
- package/dist/templates/_shared/.claude/commands/handoff.md +16 -10
- package/dist/templates/_shared/.claude/settings.json +7 -7
- package/dist/templates/_shared/hooks-ts/_utils/git-state.ts +2 -0
- package/dist/templates/_shared/hooks-ts/archive_plan.ts +159 -0
- package/dist/templates/_shared/hooks-ts/context_monitor.ts +147 -0
- package/dist/templates/_shared/hooks-ts/file-suggestion.ts +130 -0
- package/dist/templates/_shared/hooks-ts/pre_compact.ts +49 -0
- package/dist/templates/_shared/hooks-ts/session_end.ts +104 -0
- package/dist/templates/_shared/hooks-ts/session_start.ts +144 -0
- package/dist/templates/_shared/hooks-ts/task_create_capture.ts +48 -0
- package/dist/templates/_shared/hooks-ts/task_update_capture.ts +74 -0
- package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +83 -0
- package/dist/templates/_shared/lib-ts/CLAUDE.md +318 -0
- package/dist/templates/_shared/lib-ts/base/atomic-write.ts +138 -0
- package/dist/templates/_shared/lib-ts/base/constants.ts +306 -0
- package/dist/templates/_shared/lib-ts/base/git-state.ts +58 -0
- package/dist/templates/_shared/lib-ts/base/hook-utils.ts +439 -0
- package/dist/templates/_shared/lib-ts/base/inference.ts +252 -0
- package/dist/templates/_shared/lib-ts/base/logger.ts +250 -0
- package/dist/templates/_shared/lib-ts/base/state-io.ts +116 -0
- package/dist/templates/_shared/lib-ts/base/stop-words.ts +184 -0
- package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +162 -0
- package/dist/templates/_shared/lib-ts/base/utils.ts +184 -0
- package/dist/templates/_shared/lib-ts/context/context-formatter.ts +438 -0
- package/dist/templates/_shared/lib-ts/context/context-selector.ts +515 -0
- package/dist/templates/_shared/lib-ts/context/context-store.ts +707 -0
- package/dist/templates/_shared/lib-ts/context/plan-manager.ts +316 -0
- package/dist/templates/_shared/lib-ts/context/task-tracker.ts +185 -0
- package/dist/templates/_shared/lib-ts/handoff/document-generator.ts +216 -0
- package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +159 -0
- package/dist/templates/_shared/lib-ts/package.json +21 -0
- package/dist/templates/_shared/lib-ts/templates/formatters.ts +104 -0
- package/dist/templates/_shared/{lib/templates/plan_context.py → lib-ts/templates/plan-context.ts} +14 -22
- package/dist/templates/_shared/lib-ts/tsconfig.json +13 -0
- package/dist/templates/_shared/lib-ts/types.ts +164 -0
- package/dist/templates/_shared/scripts/resolve_context.ts +24 -0
- package/dist/templates/_shared/scripts/resume_handoff.ts +321 -0
- package/dist/templates/_shared/scripts/save_handoff.ts +359 -0
- package/dist/templates/_shared/scripts/status_line.ts +733 -0
- package/dist/templates/cc-native/.claude/settings.json +175 -185
- package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +15 -17
- package/dist/templates/cc-native/_cc-native/agents/ARCH-EVOLUTION.md +63 -0
- package/dist/templates/cc-native/_cc-native/agents/ARCH-PATTERNS.md +62 -0
- package/dist/templates/cc-native/_cc-native/agents/ARCH-STRUCTURE.md +63 -0
- package/dist/templates/cc-native/_cc-native/agents/{ASSUMPTION-CHAIN-TRACER.md → ASSUMPTION-TRACER.md} +6 -10
- package/dist/templates/cc-native/_cc-native/agents/CLARITY-AUDITOR.md +6 -10
- package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +74 -3
- package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-FEASIBILITY.md +67 -0
- package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-GAPS.md +71 -0
- package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-ORDERING.md +63 -0
- package/dist/templates/cc-native/_cc-native/agents/CONSTRAINT-VALIDATOR.md +73 -0
- package/dist/templates/cc-native/_cc-native/agents/DESIGN-ADR-VALIDATOR.md +62 -0
- package/dist/templates/cc-native/_cc-native/agents/DESIGN-SCALE-MATCHER.md +65 -0
- package/dist/templates/cc-native/_cc-native/agents/DEVILS-ADVOCATE.md +6 -9
- package/dist/templates/cc-native/_cc-native/agents/DOCUMENTATION-PHILOSOPHY.md +87 -0
- package/dist/templates/cc-native/_cc-native/agents/HANDOFF-READINESS.md +5 -9
- package/dist/templates/cc-native/_cc-native/agents/{HIDDEN-COMPLEXITY-DETECTOR.md → HIDDEN-COMPLEXITY.md} +6 -10
- package/dist/templates/cc-native/_cc-native/agents/INCREMENTAL-DELIVERY.md +67 -0
- package/dist/templates/cc-native/_cc-native/agents/PLAN-ORCHESTRATOR.md +91 -18
- package/dist/templates/cc-native/_cc-native/agents/RISK-DEPENDENCY.md +63 -0
- package/dist/templates/cc-native/_cc-native/agents/RISK-FMEA.md +67 -0
- package/dist/templates/cc-native/_cc-native/agents/RISK-PREMORTEM.md +72 -0
- package/dist/templates/cc-native/_cc-native/agents/RISK-REVERSIBILITY.md +75 -0
- package/dist/templates/cc-native/_cc-native/agents/SCOPE-BOUNDARY.md +78 -0
- package/dist/templates/cc-native/_cc-native/agents/SIMPLICITY-GUARDIAN.md +5 -9
- package/dist/templates/cc-native/_cc-native/agents/SKEPTIC.md +16 -12
- package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-BEHAVIOR-AUDITOR.md +62 -0
- package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-CHARACTERIZATION.md +72 -0
- package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-FIRST-VALIDATOR.md +62 -0
- package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-PYRAMID-ANALYZER.md +62 -0
- package/dist/templates/cc-native/_cc-native/agents/TRADEOFF-COSTS.md +68 -0
- package/dist/templates/cc-native/_cc-native/agents/TRADEOFF-STAKEHOLDERS.md +66 -0
- package/dist/templates/cc-native/_cc-native/agents/VERIFY-COVERAGE.md +75 -0
- package/dist/templates/cc-native/_cc-native/agents/VERIFY-STRENGTH.md +70 -0
- package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +109 -135
- package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.ts +119 -0
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +921 -0
- package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +61 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +157 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +709 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +199 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +124 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/config.ts +57 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/constants.ts +83 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/debug.ts +80 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +119 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +162 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/nul +3 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +249 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +155 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +130 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +106 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +10 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +23 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +243 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/tsconfig.json +18 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +310 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +72 -0
- package/dist/templates/cc-native/_cc-native/plan-review.config.json +12 -16
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/dist/lib/template-merger.d.ts +0 -47
- package/dist/lib/template-merger.js +0 -162
- package/dist/templates/_shared/hooks/__init__.py +0 -16
- package/dist/templates/_shared/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/context_enforcer.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/context_monitor.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/file-suggestion.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/pre_compact.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/session_end.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/session_start.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/task_create_atomicity.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/task_create_capture.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/task_update_capture.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/archive_plan.py +0 -169
- package/dist/templates/_shared/hooks/context_monitor.py +0 -270
- package/dist/templates/_shared/hooks/file-suggestion.py +0 -215
- package/dist/templates/_shared/hooks/pre_compact.py +0 -104
- package/dist/templates/_shared/hooks/session_end.py +0 -173
- package/dist/templates/_shared/hooks/session_start.py +0 -206
- package/dist/templates/_shared/hooks/task_create_capture.py +0 -108
- package/dist/templates/_shared/hooks/task_update_capture.py +0 -145
- package/dist/templates/_shared/hooks/user_prompt_submit.py +0 -139
- package/dist/templates/_shared/lib/__init__.py +0 -1
- package/dist/templates/_shared/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__init__.py +0 -65
- package/dist/templates/_shared/lib/base/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/atomic_write.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/hook_utils.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/inference.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/logger.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/stop_words.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/subprocess_utils.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/utils.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/atomic_write.py +0 -180
- package/dist/templates/_shared/lib/base/constants.py +0 -358
- package/dist/templates/_shared/lib/base/hook_utils.py +0 -341
- package/dist/templates/_shared/lib/base/inference.py +0 -318
- package/dist/templates/_shared/lib/base/logger.py +0 -291
- package/dist/templates/_shared/lib/base/stop_words.py +0 -213
- package/dist/templates/_shared/lib/base/subprocess_utils.py +0 -46
- package/dist/templates/_shared/lib/base/utils.py +0 -242
- package/dist/templates/_shared/lib/context/__init__.py +0 -102
- package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_extractor.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_formatter.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_selector.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_store.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/discovery.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/plan_archive.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/plan_manager.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/task_sync.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/task_tracker.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/context_formatter.py +0 -317
- package/dist/templates/_shared/lib/context/context_selector.py +0 -508
- package/dist/templates/_shared/lib/context/context_store.py +0 -653
- package/dist/templates/_shared/lib/context/plan_manager.py +0 -204
- package/dist/templates/_shared/lib/context/task_tracker.py +0 -188
- package/dist/templates/_shared/lib/handoff/__init__.py +0 -22
- package/dist/templates/_shared/lib/handoff/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/handoff/__pycache__/document_generator.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/handoff/document_generator.py +0 -278
- package/dist/templates/_shared/lib/templates/README.md +0 -206
- package/dist/templates/_shared/lib/templates/__init__.py +0 -36
- package/dist/templates/_shared/lib/templates/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/__pycache__/formatters.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/__pycache__/persona_questions.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/__pycache__/plan_context.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/formatters.py +0 -146
- package/dist/templates/_shared/scripts/__pycache__/save_handoff.cpython-313.pyc +0 -0
- package/dist/templates/_shared/scripts/__pycache__/status_line.cpython-313.pyc +0 -0
- package/dist/templates/_shared/scripts/save_handoff.py +0 -357
- package/dist/templates/_shared/scripts/status_line.py +0 -701
- package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +0 -8
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +0 -8
- package/dist/templates/cc-native/MIGRATION.md +0 -86
- package/dist/templates/cc-native/_cc-native/agents/ACCESSIBILITY-TESTER.md +0 -79
- package/dist/templates/cc-native/_cc-native/agents/ARCHITECT-REVIEWER.md +0 -48
- package/dist/templates/cc-native/_cc-native/agents/CODE-REVIEWER.md +0 -70
- package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-CHECKER.md +0 -59
- package/dist/templates/cc-native/_cc-native/agents/CONTEXT-EXTRACTOR.md +0 -92
- package/dist/templates/cc-native/_cc-native/agents/DOCUMENTATION-REVIEWER.md +0 -51
- package/dist/templates/cc-native/_cc-native/agents/FEASIBILITY-ANALYST.md +0 -57
- package/dist/templates/cc-native/_cc-native/agents/FRESH-PERSPECTIVE.md +0 -54
- package/dist/templates/cc-native/_cc-native/agents/INCENTIVE-MAPPER.md +0 -61
- package/dist/templates/cc-native/_cc-native/agents/PENETRATION-TESTER.md +0 -79
- package/dist/templates/cc-native/_cc-native/agents/PERFORMANCE-ENGINEER.md +0 -75
- package/dist/templates/cc-native/_cc-native/agents/PRECEDENT-FINDER.md +0 -70
- package/dist/templates/cc-native/_cc-native/agents/REVERSIBILITY-ANALYST.md +0 -61
- package/dist/templates/cc-native/_cc-native/agents/RISK-ASSESSOR.md +0 -58
- package/dist/templates/cc-native/_cc-native/agents/SECOND-ORDER-ANALYST.md +0 -61
- package/dist/templates/cc-native/_cc-native/agents/STAKEHOLDER-ADVOCATE.md +0 -55
- package/dist/templates/cc-native/_cc-native/agents/TRADE-OFF-ILLUMINATOR.md +0 -204
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/mark_questions_asked.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_accepted.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_questions_early.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/suggest-fresh-perspective.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +0 -130
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +0 -869
- package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.py +0 -81
- package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +0 -340
- package/dist/templates/cc-native/_cc-native/lib/CLAUDE.md +0 -265
- package/dist/templates/cc-native/_cc-native/lib/__init__.py +0 -53
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/atomic_write.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/constants.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/debug.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/constants.py +0 -45
- package/dist/templates/cc-native/_cc-native/lib/debug.py +0 -139
- package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +0 -362
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +0 -28
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +0 -215
- package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +0 -88
- package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +0 -124
- package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +0 -108
- package/dist/templates/cc-native/_cc-native/lib/state.py +0 -268
- package/dist/templates/cc-native/_cc-native/lib/utils.py +0 -1027
- package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +0 -168
- package/dist/templates/cc-native/_cc-native/workflows/fresh-perspective.md +0 -134
|
@@ -1,701 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Status line for Claude Code sessions.
|
|
3
|
-
|
|
4
|
-
Renders context window usage and git status with ANSI colors.
|
|
5
|
-
Optionally persists context_window data to the session's state.json.
|
|
6
|
-
|
|
7
|
-
Ported from PAI statusline.ts — context and git sections only.
|
|
8
|
-
|
|
9
|
-
Usage: echo '{"session_id":"...","model":{"display_name":"Opus"},...}' | python status_line.py
|
|
10
|
-
"""
|
|
11
|
-
import json
|
|
12
|
-
import os
|
|
13
|
-
import re
|
|
14
|
-
import subprocess
|
|
15
|
-
import sys
|
|
16
|
-
from datetime import datetime
|
|
17
|
-
from pathlib import Path
|
|
18
|
-
from typing import Any, Dict, Optional, Tuple
|
|
19
|
-
|
|
20
|
-
# ---------------------------------------------------------------------------
|
|
21
|
-
# Path setup (matches save_handoff.py pattern)
|
|
22
|
-
# ---------------------------------------------------------------------------
|
|
23
|
-
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
24
|
-
SHARED_ROOT = SCRIPT_DIR.parent # _shared/
|
|
25
|
-
sys.path.insert(0, str(SHARED_ROOT))
|
|
26
|
-
|
|
27
|
-
from lib.base.atomic_write import atomic_write
|
|
28
|
-
from lib.base.hook_utils import CONTEXT_BASELINE_TOKENS
|
|
29
|
-
|
|
30
|
-
# Cache file for session_id → context_id mapping
|
|
31
|
-
OUTPUT_DIR = Path(".") / "_output"
|
|
32
|
-
STATUSLINE_CACHE = OUTPUT_DIR / ".statusline-cache.json"
|
|
33
|
-
|
|
34
|
-
# ---------------------------------------------------------------------------
|
|
35
|
-
# NO_COLOR support (https://no-color.org)
|
|
36
|
-
# ---------------------------------------------------------------------------
|
|
37
|
-
NO_COLOR = bool(os.environ.get("NO_COLOR"))
|
|
38
|
-
|
|
39
|
-
RESET = "" if NO_COLOR else "\x1b[0m"
|
|
40
|
-
|
|
41
|
-
# Structural
|
|
42
|
-
SLATE_300 = "" if NO_COLOR else "\x1b[38;2;203;213;225m"
|
|
43
|
-
SLATE_400 = "" if NO_COLOR else "\x1b[38;2;148;163;184m"
|
|
44
|
-
SLATE_500 = "" if NO_COLOR else "\x1b[38;2;100;116;139m"
|
|
45
|
-
SLATE_600 = "" if NO_COLOR else "\x1b[38;2;71;85;105m"
|
|
46
|
-
|
|
47
|
-
# Semantic
|
|
48
|
-
EMERALD = "" if NO_COLOR else "\x1b[38;2;74;222;128m"
|
|
49
|
-
ROSE = "" if NO_COLOR else "\x1b[38;2;251;113;133m"
|
|
50
|
-
AMBER = "" if NO_COLOR else "\x1b[38;2;251;191;36m"
|
|
51
|
-
|
|
52
|
-
# Context colors
|
|
53
|
-
CTX_PRIMARY = "" if NO_COLOR else "\x1b[38;2;129;140;248m"
|
|
54
|
-
CTX_SECONDARY = "" if NO_COLOR else "\x1b[38;2;165;180;252m"
|
|
55
|
-
CTX_ACCENT = "" if NO_COLOR else "\x1b[38;2;139;92;246m"
|
|
56
|
-
CTX_BUCKET_EMPTY = "" if NO_COLOR else "\x1b[38;2;75;82;95m"
|
|
57
|
-
|
|
58
|
-
# Git colors
|
|
59
|
-
GIT_PRIMARY = "" if NO_COLOR else "\x1b[38;2;56;189;248m"
|
|
60
|
-
GIT_VALUE = "" if NO_COLOR else "\x1b[38;2;186;230;253m"
|
|
61
|
-
GIT_DIR = "" if NO_COLOR else "\x1b[38;2;147;197;253m"
|
|
62
|
-
GIT_CLEAN = "" if NO_COLOR else "\x1b[38;2;125;211;252m"
|
|
63
|
-
GIT_MODIFIED = "" if NO_COLOR else "\x1b[38;2;96;165;250m"
|
|
64
|
-
GIT_ADDED = "" if NO_COLOR else "\x1b[38;2;59;130;246m"
|
|
65
|
-
GIT_STASH = "" if NO_COLOR else "\x1b[38;2;165;180;252m"
|
|
66
|
-
GIT_AGE_FRESH = "" if NO_COLOR else "\x1b[38;2;125;211;252m"
|
|
67
|
-
GIT_AGE_RECENT = "" if NO_COLOR else "\x1b[38;2;96;165;250m"
|
|
68
|
-
GIT_AGE_STALE = "" if NO_COLOR else "\x1b[38;2;59;130;246m"
|
|
69
|
-
GIT_AGE_OLD = "" if NO_COLOR else "\x1b[38;2;99;102;241m"
|
|
70
|
-
|
|
71
|
-
# ---------------------------------------------------------------------------
|
|
72
|
-
# Display modes
|
|
73
|
-
# ---------------------------------------------------------------------------
|
|
74
|
-
|
|
75
|
-
def get_terminal_width() -> int:
|
|
76
|
-
"""Detect terminal width with fallbacks."""
|
|
77
|
-
# Try COLUMNS env var first
|
|
78
|
-
cols_env = os.environ.get("COLUMNS")
|
|
79
|
-
if cols_env:
|
|
80
|
-
try:
|
|
81
|
-
cols = int(cols_env)
|
|
82
|
-
if cols > 0:
|
|
83
|
-
return cols
|
|
84
|
-
except ValueError:
|
|
85
|
-
pass
|
|
86
|
-
|
|
87
|
-
# Try os.get_terminal_size
|
|
88
|
-
try:
|
|
89
|
-
return os.get_terminal_size().columns
|
|
90
|
-
except (OSError, ValueError):
|
|
91
|
-
pass
|
|
92
|
-
|
|
93
|
-
return 80
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def get_display_mode(width: int) -> str:
|
|
97
|
-
"""Map terminal width to display mode."""
|
|
98
|
-
if width < 35:
|
|
99
|
-
return "nano"
|
|
100
|
-
if width < 55:
|
|
101
|
-
return "micro"
|
|
102
|
-
if width < 80:
|
|
103
|
-
return "mini"
|
|
104
|
-
return "normal"
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
# ---------------------------------------------------------------------------
|
|
108
|
-
# Color helpers
|
|
109
|
-
# ---------------------------------------------------------------------------
|
|
110
|
-
|
|
111
|
-
def get_bucket_color(pos: int, max_pos: int) -> str:
|
|
112
|
-
"""Get gradient color for context bar bucket at position."""
|
|
113
|
-
if NO_COLOR:
|
|
114
|
-
return ""
|
|
115
|
-
pct = (pos * 100) // max_pos
|
|
116
|
-
|
|
117
|
-
if pct <= 33:
|
|
118
|
-
r = 74 + ((250 - 74) * pct) // 33
|
|
119
|
-
g = 222 + ((204 - 222) * pct) // 33
|
|
120
|
-
b = 128 + ((21 - 128) * pct) // 33
|
|
121
|
-
elif pct <= 66:
|
|
122
|
-
t = pct - 33
|
|
123
|
-
r = 250 + ((251 - 250) * t) // 33
|
|
124
|
-
g = 204 + ((146 - 204) * t) // 33
|
|
125
|
-
b = 21 + ((60 - 21) * t) // 33
|
|
126
|
-
else:
|
|
127
|
-
t = pct - 66
|
|
128
|
-
r = 251 + ((239 - 251) * t) // 34
|
|
129
|
-
g = 146 + ((68 - 146) * t) // 34
|
|
130
|
-
b = 60 + ((68 - 60) * t) // 34
|
|
131
|
-
|
|
132
|
-
return f"\x1b[38;2;{r};{g};{b}m"
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
# ---------------------------------------------------------------------------
|
|
136
|
-
# Context bar rendering
|
|
137
|
-
# ---------------------------------------------------------------------------
|
|
138
|
-
|
|
139
|
-
def render_context_bar(width: int, pct: int) -> Tuple[str, str]:
|
|
140
|
-
"""Render the context usage bar with gradient colors.
|
|
141
|
-
|
|
142
|
-
Returns (bar_string, last_filled_color).
|
|
143
|
-
"""
|
|
144
|
-
pct = max(0, min(100, pct))
|
|
145
|
-
filled = (pct * width) // 100
|
|
146
|
-
last_color = EMERALD
|
|
147
|
-
parts = []
|
|
148
|
-
|
|
149
|
-
for i in range(1, width + 1):
|
|
150
|
-
if i <= filled:
|
|
151
|
-
color = get_bucket_color(i, width)
|
|
152
|
-
last_color = color
|
|
153
|
-
parts.append(f"{color}\u26C1{RESET}")
|
|
154
|
-
else:
|
|
155
|
-
parts.append(f"{CTX_BUCKET_EMPTY}\u26C1{RESET}")
|
|
156
|
-
if width > 8:
|
|
157
|
-
parts.append(" ")
|
|
158
|
-
|
|
159
|
-
return "".join(parts).rstrip(), last_color
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
# ---------------------------------------------------------------------------
|
|
163
|
-
# Separator
|
|
164
|
-
# ---------------------------------------------------------------------------
|
|
165
|
-
|
|
166
|
-
SEPARATOR = f"{SLATE_600}" + "\u2500" * 72 + f"{RESET}"
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
# ---------------------------------------------------------------------------
|
|
170
|
-
# Context section
|
|
171
|
-
# ---------------------------------------------------------------------------
|
|
172
|
-
|
|
173
|
-
def shorten_model(name: str) -> str:
|
|
174
|
-
"""Shorten common model display names."""
|
|
175
|
-
replacements = [
|
|
176
|
-
("claude-opus-4-6", "opus-4.6"),
|
|
177
|
-
("claude-opus-4-5", "opus-4.5"),
|
|
178
|
-
("claude-sonnet-4", "sonnet-4"),
|
|
179
|
-
("claude-3-5-sonnet", "sonnet-3.5"),
|
|
180
|
-
("claude-3-5-haiku", "haiku-3.5"),
|
|
181
|
-
("claude-", ""),
|
|
182
|
-
]
|
|
183
|
-
result = name
|
|
184
|
-
for old, new in replacements:
|
|
185
|
-
result = result.replace(old, new)
|
|
186
|
-
return result
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
def render_context(
|
|
190
|
-
mode: str,
|
|
191
|
-
context_pct: int,
|
|
192
|
-
context_k: int,
|
|
193
|
-
max_k: int,
|
|
194
|
-
time_display: str,
|
|
195
|
-
model_name: str,
|
|
196
|
-
) -> None:
|
|
197
|
-
"""Render the context usage section."""
|
|
198
|
-
if context_pct <= 33:
|
|
199
|
-
pct_color = EMERALD
|
|
200
|
-
elif context_pct <= 66:
|
|
201
|
-
pct_color = AMBER
|
|
202
|
-
else:
|
|
203
|
-
pct_color = ROSE
|
|
204
|
-
|
|
205
|
-
short_model = shorten_model(model_name)
|
|
206
|
-
|
|
207
|
-
if mode == "nano":
|
|
208
|
-
bar, _ = render_context_bar(5, context_pct)
|
|
209
|
-
print(
|
|
210
|
-
f"{CTX_PRIMARY}\u25C9{RESET} {CTX_ACCENT}{short_model}{RESET} "
|
|
211
|
-
f"{bar} {pct_color}{context_pct}%{RESET} "
|
|
212
|
-
f"{CTX_ACCENT}\u23F1{RESET} {SLATE_300}{time_display}{RESET}"
|
|
213
|
-
)
|
|
214
|
-
elif mode == "micro":
|
|
215
|
-
bar, _ = render_context_bar(6, context_pct)
|
|
216
|
-
print(
|
|
217
|
-
f"{CTX_PRIMARY}\u25C9{RESET} {CTX_ACCENT}{short_model}{RESET} "
|
|
218
|
-
f"{SLATE_600}\u2502{RESET} "
|
|
219
|
-
f"{bar} {pct_color}{context_pct}%{RESET} {SLATE_500}({context_k}k){RESET} "
|
|
220
|
-
f"{CTX_ACCENT}\u23F1{RESET} {SLATE_300}{time_display}{RESET}"
|
|
221
|
-
)
|
|
222
|
-
elif mode == "mini":
|
|
223
|
-
bar, _ = render_context_bar(8, context_pct)
|
|
224
|
-
print(
|
|
225
|
-
f"{CTX_PRIMARY}\u25C9{RESET} {CTX_ACCENT}{short_model}{RESET} "
|
|
226
|
-
f"{SLATE_600}\u2502{RESET} "
|
|
227
|
-
f"{CTX_SECONDARY}CTX:{RESET} {bar} "
|
|
228
|
-
f"{pct_color}{context_pct}%{RESET} {SLATE_500}({context_k}k/{max_k}k){RESET} "
|
|
229
|
-
f"{CTX_ACCENT}\u23F1{RESET} {SLATE_300}{time_display}{RESET}"
|
|
230
|
-
)
|
|
231
|
-
else: # normal
|
|
232
|
-
bar, last_color = render_context_bar(16, context_pct)
|
|
233
|
-
print(
|
|
234
|
-
f"{CTX_PRIMARY}\u25C9{RESET} {CTX_SECONDARY}Model:{RESET} {CTX_ACCENT}{short_model}{RESET} "
|
|
235
|
-
f"{SLATE_600}\u2502{RESET} "
|
|
236
|
-
f"{CTX_SECONDARY}Context:{RESET} {bar} "
|
|
237
|
-
f"{last_color}{context_pct}%{RESET} {SLATE_500}({context_k}k/{max_k}k){RESET} "
|
|
238
|
-
f"{SLATE_600}\u2502{RESET} "
|
|
239
|
-
f"{CTX_ACCENT}\u23F1{RESET} {SLATE_300}{time_display}{RESET}"
|
|
240
|
-
)
|
|
241
|
-
|
|
242
|
-
print(SEPARATOR)
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
# ---------------------------------------------------------------------------
|
|
246
|
-
# Git status
|
|
247
|
-
# ---------------------------------------------------------------------------
|
|
248
|
-
|
|
249
|
-
def _run_git(args: list, cwd: str, timeout: int = 2) -> Optional[str]:
|
|
250
|
-
"""Run a git command and return stdout, or None on failure."""
|
|
251
|
-
try:
|
|
252
|
-
kwargs: Dict[str, Any] = {
|
|
253
|
-
"capture_output": True,
|
|
254
|
-
"text": True,
|
|
255
|
-
"timeout": timeout,
|
|
256
|
-
"cwd": cwd,
|
|
257
|
-
}
|
|
258
|
-
if sys.platform == "win32":
|
|
259
|
-
kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW
|
|
260
|
-
result = subprocess.run(["git"] + args, **kwargs)
|
|
261
|
-
if result.returncode == 0:
|
|
262
|
-
return result.stdout.strip()
|
|
263
|
-
except Exception:
|
|
264
|
-
pass
|
|
265
|
-
return None
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
def get_git_status(cwd: str) -> Optional[Dict[str, Any]]:
|
|
269
|
-
"""Gather git repository status."""
|
|
270
|
-
# Check if git repo
|
|
271
|
-
if _run_git(["rev-parse", "--git-dir"], cwd) is None:
|
|
272
|
-
return None
|
|
273
|
-
|
|
274
|
-
status: Dict[str, Any] = {
|
|
275
|
-
"branch": "detached",
|
|
276
|
-
"modified": 0,
|
|
277
|
-
"staged": 0,
|
|
278
|
-
"untracked": 0,
|
|
279
|
-
"stash_count": 0,
|
|
280
|
-
"ahead": 0,
|
|
281
|
-
"behind": 0,
|
|
282
|
-
"age_display": "",
|
|
283
|
-
"age_color": GIT_AGE_FRESH,
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
# Branch
|
|
287
|
-
branch = _run_git(["branch", "--show-current"], cwd)
|
|
288
|
-
if branch:
|
|
289
|
-
status["branch"] = branch
|
|
290
|
-
|
|
291
|
-
# Modified files
|
|
292
|
-
diff = _run_git(["diff", "--name-only"], cwd)
|
|
293
|
-
if diff:
|
|
294
|
-
status["modified"] = len([l for l in diff.splitlines() if l])
|
|
295
|
-
|
|
296
|
-
# Staged files
|
|
297
|
-
staged = _run_git(["diff", "--cached", "--name-only"], cwd)
|
|
298
|
-
if staged:
|
|
299
|
-
status["staged"] = len([l for l in staged.splitlines() if l])
|
|
300
|
-
|
|
301
|
-
# Untracked files
|
|
302
|
-
untracked = _run_git(["ls-files", "--others", "--exclude-standard"], cwd)
|
|
303
|
-
if untracked:
|
|
304
|
-
status["untracked"] = len([l for l in untracked.splitlines() if l])
|
|
305
|
-
|
|
306
|
-
# Stash count
|
|
307
|
-
stash = _run_git(["stash", "list"], cwd)
|
|
308
|
-
if stash:
|
|
309
|
-
status["stash_count"] = len([l for l in stash.splitlines() if l])
|
|
310
|
-
|
|
311
|
-
# Ahead/behind
|
|
312
|
-
ab = _run_git(["rev-list", "--left-right", "--count", "HEAD...@{u}"], cwd)
|
|
313
|
-
if ab:
|
|
314
|
-
parts = ab.split()
|
|
315
|
-
if len(parts) >= 2:
|
|
316
|
-
status["ahead"] = int(parts[0] or 0)
|
|
317
|
-
status["behind"] = int(parts[1] or 0)
|
|
318
|
-
|
|
319
|
-
# Commit age
|
|
320
|
-
log = _run_git(["log", "-1", "--format=%ct"], cwd)
|
|
321
|
-
if log:
|
|
322
|
-
try:
|
|
323
|
-
import time
|
|
324
|
-
last_epoch = int(log)
|
|
325
|
-
now_epoch = int(time.time())
|
|
326
|
-
age_sec = now_epoch - last_epoch
|
|
327
|
-
age_min = age_sec // 60
|
|
328
|
-
age_hrs = age_sec // 3600
|
|
329
|
-
age_days = age_sec // 86400
|
|
330
|
-
|
|
331
|
-
if age_min < 1:
|
|
332
|
-
status["age_display"] = "now"
|
|
333
|
-
status["age_color"] = GIT_AGE_FRESH
|
|
334
|
-
elif age_hrs < 1:
|
|
335
|
-
status["age_display"] = f"{age_min}m"
|
|
336
|
-
status["age_color"] = GIT_AGE_FRESH
|
|
337
|
-
elif age_hrs < 24:
|
|
338
|
-
status["age_display"] = f"{age_hrs}h"
|
|
339
|
-
status["age_color"] = GIT_AGE_RECENT
|
|
340
|
-
elif age_days < 7:
|
|
341
|
-
status["age_display"] = f"{age_days}d"
|
|
342
|
-
status["age_color"] = GIT_AGE_STALE
|
|
343
|
-
else:
|
|
344
|
-
status["age_display"] = f"{age_days}d"
|
|
345
|
-
status["age_color"] = GIT_AGE_OLD
|
|
346
|
-
except (ValueError, TypeError):
|
|
347
|
-
pass
|
|
348
|
-
|
|
349
|
-
return status
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
def render_git(mode: str, git: Dict[str, Any], dir_name: str) -> None:
|
|
353
|
-
"""Render the git status section."""
|
|
354
|
-
total_changed = git["modified"] + git["staged"]
|
|
355
|
-
status_icon = "*" if (total_changed > 0 or git["untracked"] > 0) else "\u2713"
|
|
356
|
-
|
|
357
|
-
if mode == "nano":
|
|
358
|
-
line = f"{GIT_PRIMARY}\u25C8{RESET} {GIT_DIR}{dir_name}{RESET} {GIT_VALUE}{git['branch']}{RESET} "
|
|
359
|
-
if status_icon == "\u2713":
|
|
360
|
-
line += f"{GIT_CLEAN}\u2713{RESET}"
|
|
361
|
-
else:
|
|
362
|
-
line += f"{GIT_MODIFIED}*{total_changed}{RESET}"
|
|
363
|
-
print(line)
|
|
364
|
-
|
|
365
|
-
elif mode == "micro":
|
|
366
|
-
line = f"{GIT_PRIMARY}\u25C8{RESET} {GIT_DIR}{dir_name}{RESET} {GIT_VALUE}{git['branch']}{RESET}"
|
|
367
|
-
if git["age_display"]:
|
|
368
|
-
line += f" {git['age_color']}{git['age_display']}{RESET}"
|
|
369
|
-
line += " "
|
|
370
|
-
if status_icon == "\u2713":
|
|
371
|
-
line += f"{GIT_CLEAN}{status_icon}{RESET}"
|
|
372
|
-
else:
|
|
373
|
-
line += f"{GIT_MODIFIED}{status_icon}{total_changed}{RESET}"
|
|
374
|
-
print(line)
|
|
375
|
-
|
|
376
|
-
elif mode == "mini":
|
|
377
|
-
line = (
|
|
378
|
-
f"{GIT_PRIMARY}\u25C8{RESET} {GIT_DIR}{dir_name}{RESET} "
|
|
379
|
-
f"{SLATE_600}\u2502{RESET} {GIT_VALUE}{git['branch']}{RESET}"
|
|
380
|
-
)
|
|
381
|
-
if git["age_display"]:
|
|
382
|
-
line += f" {SLATE_600}\u2502{RESET} {git['age_color']}{git['age_display']}{RESET}"
|
|
383
|
-
line += f" {SLATE_600}\u2502{RESET} "
|
|
384
|
-
if status_icon == "\u2713":
|
|
385
|
-
line += f"{GIT_CLEAN}{status_icon}{RESET}"
|
|
386
|
-
else:
|
|
387
|
-
line += f"{GIT_MODIFIED}{status_icon}{total_changed}{RESET}"
|
|
388
|
-
if git["untracked"] > 0:
|
|
389
|
-
line += f" {GIT_ADDED}+{git['untracked']}{RESET}"
|
|
390
|
-
print(line)
|
|
391
|
-
|
|
392
|
-
else: # normal
|
|
393
|
-
line = (
|
|
394
|
-
f"{GIT_PRIMARY}\u25C8{RESET} {GIT_PRIMARY}PWD:{RESET} {GIT_DIR}{dir_name}{RESET} "
|
|
395
|
-
f"{SLATE_600}\u2502{RESET} "
|
|
396
|
-
f"{GIT_PRIMARY}Branch:{RESET} {GIT_VALUE}{git['branch']}{RESET}"
|
|
397
|
-
)
|
|
398
|
-
if git["age_display"]:
|
|
399
|
-
line += f" {SLATE_600}\u2502{RESET} {GIT_PRIMARY}Age:{RESET} {git['age_color']}{git['age_display']}{RESET}"
|
|
400
|
-
if git["stash_count"] > 0:
|
|
401
|
-
line += f" {SLATE_600}\u2502{RESET} {GIT_PRIMARY}Stash:{RESET} {GIT_STASH}{git['stash_count']}{RESET}"
|
|
402
|
-
|
|
403
|
-
if total_changed > 0 or git["untracked"] > 0:
|
|
404
|
-
line += f" {SLATE_600}\u2502{RESET} "
|
|
405
|
-
if total_changed > 0:
|
|
406
|
-
line += f"{GIT_PRIMARY}Mod:{RESET} {GIT_MODIFIED}{total_changed}{RESET}"
|
|
407
|
-
if git["untracked"] > 0:
|
|
408
|
-
if total_changed > 0:
|
|
409
|
-
line += " "
|
|
410
|
-
line += f"{GIT_PRIMARY}New:{RESET} {GIT_ADDED}{git['untracked']}{RESET}"
|
|
411
|
-
else:
|
|
412
|
-
line += f" {SLATE_600}\u2502{RESET} {GIT_CLEAN}\u2713 clean{RESET}"
|
|
413
|
-
|
|
414
|
-
if git["ahead"] > 0 or git["behind"] > 0:
|
|
415
|
-
line += f" {SLATE_600}\u2502{RESET} {GIT_PRIMARY}Sync:{RESET} "
|
|
416
|
-
if git["ahead"] > 0:
|
|
417
|
-
line += f"{GIT_CLEAN}\u2191{git['ahead']}{RESET}"
|
|
418
|
-
if git["behind"] > 0:
|
|
419
|
-
line += f"{GIT_STASH}\u2193{git['behind']}{RESET}"
|
|
420
|
-
print(line)
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
# ---------------------------------------------------------------------------
|
|
424
|
-
# Context manager line (line 3)
|
|
425
|
-
# ---------------------------------------------------------------------------
|
|
426
|
-
|
|
427
|
-
def render_context_manager(
|
|
428
|
-
mode: str,
|
|
429
|
-
context_id: str,
|
|
430
|
-
context_state=None,
|
|
431
|
-
) -> None:
|
|
432
|
-
"""Render the context manager line (line 3) showing context ID, mode, and plan."""
|
|
433
|
-
# Strip YYMMDD-HHMM- timestamp prefix from context ID for display
|
|
434
|
-
display_id = re.sub(r"^\d{6}-\d{4}-", "", context_id)
|
|
435
|
-
if not display_id:
|
|
436
|
-
display_id = context_id # fallback if regex strips everything
|
|
437
|
-
|
|
438
|
-
# Truncate display_id per mode
|
|
439
|
-
max_id_len = {"nano": 14, "micro": 18, "mini": 22, "normal": 30}.get(mode, 30)
|
|
440
|
-
truncated_id = display_id[:max_id_len]
|
|
441
|
-
if len(display_id) > max_id_len:
|
|
442
|
-
truncated_id += "\u2026"
|
|
443
|
-
|
|
444
|
-
# Read state fields (ContextState object from context_store)
|
|
445
|
-
state_mode = getattr(context_state, "mode", "idle") if context_state else "idle"
|
|
446
|
-
state_plan_path = getattr(context_state, "plan_path", None) if context_state else None
|
|
447
|
-
|
|
448
|
-
# Detect plan mode heuristic: if state is idle but a recent plan file exists
|
|
449
|
-
# in ~/.claude/plans/, we're likely in active planning (transient, not persisted)
|
|
450
|
-
active_plan_file = _find_active_plan_file()
|
|
451
|
-
is_planning = state_mode == "idle" and active_plan_file is not None
|
|
452
|
-
|
|
453
|
-
# Build mode badge
|
|
454
|
-
mode_badge = ""
|
|
455
|
-
if is_planning:
|
|
456
|
-
label = "Plan" if mode == "nano" else "Planning"
|
|
457
|
-
mode_badge = f" {SLATE_600}\u2502{RESET} {CTX_SECONDARY}Mode:{RESET} {AMBER}{label}{RESET}"
|
|
458
|
-
elif state_mode == "has_plan":
|
|
459
|
-
label = "Ready" if mode == "nano" else "Plan Ready"
|
|
460
|
-
mode_badge = f" {SLATE_600}\u2502{RESET} {CTX_SECONDARY}Mode:{RESET} {EMERALD}{label}{RESET}"
|
|
461
|
-
elif state_mode == "active":
|
|
462
|
-
label = "Active" if mode == "nano" else "Active"
|
|
463
|
-
mode_badge = f" {SLATE_600}\u2502{RESET} {CTX_SECONDARY}Mode:{RESET} {CTX_ACCENT}{label}{RESET}"
|
|
464
|
-
|
|
465
|
-
# Resolve plan file path for display
|
|
466
|
-
plan_file_path = None
|
|
467
|
-
if is_planning:
|
|
468
|
-
plan_file_path = active_plan_file
|
|
469
|
-
elif state_plan_path:
|
|
470
|
-
plan_file_path = state_plan_path
|
|
471
|
-
elif state_mode in ("has_plan", "active"):
|
|
472
|
-
# Fallback: check context's plans/ folder
|
|
473
|
-
try:
|
|
474
|
-
from lib.context.plan_manager import find_latest_plan
|
|
475
|
-
plan_file_path = find_latest_plan(context_id)
|
|
476
|
-
except Exception:
|
|
477
|
-
pass
|
|
478
|
-
|
|
479
|
-
# Build plan name (mini/normal only)
|
|
480
|
-
plan_part = ""
|
|
481
|
-
if mode in ("mini", "normal") and plan_file_path:
|
|
482
|
-
plan_stem = re.sub(r"^\d{4}-\d{2}-\d{2}-", "", Path(plan_file_path).stem)
|
|
483
|
-
max_plan_len = 20 if mode == "mini" else 30
|
|
484
|
-
truncated_plan = plan_stem[:max_plan_len]
|
|
485
|
-
if len(plan_stem) > max_plan_len:
|
|
486
|
-
truncated_plan += "\u2026"
|
|
487
|
-
plan_part = f" {SLATE_600}\u2502{RESET} {CTX_SECONDARY}Plan:{RESET} {SLATE_300}{truncated_plan}{RESET}"
|
|
488
|
-
|
|
489
|
-
if mode == "nano":
|
|
490
|
-
print(
|
|
491
|
-
f"{CTX_ACCENT}\u25C6{RESET} {SLATE_400}{truncated_id}{RESET}"
|
|
492
|
-
f"{mode_badge}"
|
|
493
|
-
)
|
|
494
|
-
elif mode == "micro":
|
|
495
|
-
print(
|
|
496
|
-
f"{CTX_ACCENT}\u25C6{RESET} {SLATE_400}{truncated_id}{RESET}"
|
|
497
|
-
f"{mode_badge}"
|
|
498
|
-
)
|
|
499
|
-
elif mode == "mini":
|
|
500
|
-
print(
|
|
501
|
-
f"{CTX_ACCENT}\u25C6{RESET} {SLATE_400}{truncated_id}{RESET}"
|
|
502
|
-
f"{mode_badge}{plan_part}"
|
|
503
|
-
)
|
|
504
|
-
else: # normal
|
|
505
|
-
print(
|
|
506
|
-
f"{CTX_ACCENT}\u25C6{RESET} {CTX_SECONDARY}Context:{RESET} {SLATE_300}{truncated_id}{RESET}"
|
|
507
|
-
f"{mode_badge}{plan_part}"
|
|
508
|
-
)
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
# ---------------------------------------------------------------------------
|
|
512
|
-
# Context persistence
|
|
513
|
-
# ---------------------------------------------------------------------------
|
|
514
|
-
|
|
515
|
-
def _load_cache() -> Dict[str, Any]:
|
|
516
|
-
"""Load the statusline cache file."""
|
|
517
|
-
try:
|
|
518
|
-
if STATUSLINE_CACHE.exists():
|
|
519
|
-
return json.loads(STATUSLINE_CACHE.read_text(encoding="utf-8"))
|
|
520
|
-
except Exception:
|
|
521
|
-
pass
|
|
522
|
-
return {}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
def _save_cache(cache: Dict[str, Any]) -> None:
|
|
526
|
-
"""Save the statusline cache file."""
|
|
527
|
-
try:
|
|
528
|
-
STATUSLINE_CACHE.parent.mkdir(parents=True, exist_ok=True)
|
|
529
|
-
STATUSLINE_CACHE.write_text(
|
|
530
|
-
json.dumps(cache, indent=2, ensure_ascii=False), encoding="utf-8"
|
|
531
|
-
)
|
|
532
|
-
except Exception:
|
|
533
|
-
pass
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
def _resolve_context_id(session_id: str) -> Optional[str]:
|
|
537
|
-
"""Resolve session_id to context_id, using cache when possible."""
|
|
538
|
-
if not session_id or session_id == "unknown":
|
|
539
|
-
return None
|
|
540
|
-
|
|
541
|
-
# Check cache first
|
|
542
|
-
cache = _load_cache()
|
|
543
|
-
cached_entry = cache.get("sessions", {}).get(session_id)
|
|
544
|
-
if cached_entry and cached_entry.get("context_id") is not None:
|
|
545
|
-
return cached_entry["context_id"]
|
|
546
|
-
|
|
547
|
-
# Cache miss — look up via context manager
|
|
548
|
-
try:
|
|
549
|
-
from lib.context.context_store import get_context_by_session_id
|
|
550
|
-
context = get_context_by_session_id(session_id)
|
|
551
|
-
if context:
|
|
552
|
-
# Update cache
|
|
553
|
-
if "sessions" not in cache:
|
|
554
|
-
cache["sessions"] = {}
|
|
555
|
-
cache["sessions"][session_id] = {"context_id": context.id}
|
|
556
|
-
_save_cache(cache)
|
|
557
|
-
return context.id
|
|
558
|
-
except Exception:
|
|
559
|
-
pass
|
|
560
|
-
|
|
561
|
-
# Mark as no-context in cache to avoid repeated lookups
|
|
562
|
-
if "sessions" not in cache:
|
|
563
|
-
cache["sessions"] = {}
|
|
564
|
-
cache["sessions"][session_id] = {"context_id": None}
|
|
565
|
-
_save_cache(cache)
|
|
566
|
-
return None
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
def _load_context_state(context_id: str):
|
|
570
|
-
"""Load context state from state.json (with context.json fallback)."""
|
|
571
|
-
try:
|
|
572
|
-
from lib.context.context_store import load_state
|
|
573
|
-
return load_state(context_id)
|
|
574
|
-
except Exception:
|
|
575
|
-
return None
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
def _find_active_plan_file() -> Optional[str]:
|
|
579
|
-
"""Find most recent plan file in ~/.claude/plans/."""
|
|
580
|
-
try:
|
|
581
|
-
plans_dir = Path.home() / ".claude" / "plans"
|
|
582
|
-
if not plans_dir.exists():
|
|
583
|
-
return None
|
|
584
|
-
plan_files = list(plans_dir.glob("*.md"))
|
|
585
|
-
if not plan_files:
|
|
586
|
-
return None
|
|
587
|
-
plan_files.sort(key=lambda p: p.stat().st_mtime, reverse=True)
|
|
588
|
-
return str(plan_files[0])
|
|
589
|
-
except Exception:
|
|
590
|
-
return None
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
def _write_context_window(context_id: str, context_window_data: Dict[str, Any]) -> None:
|
|
594
|
-
"""Write context_window data to state.json last_session."""
|
|
595
|
-
try:
|
|
596
|
-
from lib.context.context_store import get_context as get_ctx, save_state
|
|
597
|
-
state = get_ctx(context_id)
|
|
598
|
-
if state:
|
|
599
|
-
if state.last_session is None:
|
|
600
|
-
state.last_session = {}
|
|
601
|
-
state.last_session["context_remaining_pct"] = context_window_data.get("remaining_percentage")
|
|
602
|
-
save_state(state)
|
|
603
|
-
except Exception:
|
|
604
|
-
pass
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
# ---------------------------------------------------------------------------
|
|
608
|
-
# Main
|
|
609
|
-
# ---------------------------------------------------------------------------
|
|
610
|
-
|
|
611
|
-
def main() -> None:
|
|
612
|
-
"""Read stdin JSON, render status line, optionally persist context data."""
|
|
613
|
-
# Force UTF-8 stdout on Windows to support Unicode symbols
|
|
614
|
-
if sys.platform == "win32" and hasattr(sys.stdout, "reconfigure"):
|
|
615
|
-
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
616
|
-
|
|
617
|
-
# Read JSON from stdin
|
|
618
|
-
try:
|
|
619
|
-
input_data = json.loads(sys.stdin.read())
|
|
620
|
-
except Exception:
|
|
621
|
-
input_data = {}
|
|
622
|
-
|
|
623
|
-
# Terminal width and mode
|
|
624
|
-
term_width = get_terminal_width()
|
|
625
|
-
mode = get_display_mode(term_width)
|
|
626
|
-
|
|
627
|
-
# Extract input fields
|
|
628
|
-
session_id = input_data.get("session_id", "")
|
|
629
|
-
model_name = (input_data.get("model") or {}).get("display_name", "unknown")
|
|
630
|
-
cost = input_data.get("cost") or {}
|
|
631
|
-
duration_ms = cost.get("total_duration_ms", 0)
|
|
632
|
-
workspace = input_data.get("workspace") or {}
|
|
633
|
-
current_dir = workspace.get("project_dir", os.getcwd())
|
|
634
|
-
dir_name = os.path.basename(current_dir)
|
|
635
|
-
|
|
636
|
-
# Context window data
|
|
637
|
-
ctx_win = input_data.get("context_window") or {}
|
|
638
|
-
usage = ctx_win.get("current_usage") or {}
|
|
639
|
-
cache_read = usage.get("cache_read_input_tokens", 0)
|
|
640
|
-
input_tokens = usage.get("input_tokens", 0)
|
|
641
|
-
cache_creation = usage.get("cache_creation_input_tokens", 0)
|
|
642
|
-
output_tokens = usage.get("output_tokens", 0)
|
|
643
|
-
context_max = ctx_win.get("context_window_size", 200000)
|
|
644
|
-
|
|
645
|
-
# Calculate context percentage
|
|
646
|
-
# Use used_percentage if available (pre-calculated), else raw tokens + baseline
|
|
647
|
-
used_pct = ctx_win.get("used_percentage")
|
|
648
|
-
if used_pct is not None:
|
|
649
|
-
context_pct = int(used_pct)
|
|
650
|
-
total_input = cache_read + input_tokens + cache_creation
|
|
651
|
-
context_used = total_input + output_tokens + CONTEXT_BASELINE_TOKENS
|
|
652
|
-
else:
|
|
653
|
-
total_input = cache_read + input_tokens + cache_creation
|
|
654
|
-
context_used = total_input + output_tokens + CONTEXT_BASELINE_TOKENS
|
|
655
|
-
context_pct = (context_used * 100) // context_max if context_max > 0 else 0
|
|
656
|
-
|
|
657
|
-
context_k = context_used // 1000
|
|
658
|
-
max_k = context_max // 1000
|
|
659
|
-
|
|
660
|
-
# Format duration
|
|
661
|
-
duration_sec = duration_ms // 1000
|
|
662
|
-
if duration_sec >= 3600:
|
|
663
|
-
time_display = f"{duration_sec // 3600}h{(duration_sec % 3600) // 60}m"
|
|
664
|
-
elif duration_sec >= 60:
|
|
665
|
-
time_display = f"{duration_sec // 60}m{duration_sec % 60}s"
|
|
666
|
-
else:
|
|
667
|
-
time_display = f"{duration_sec}s"
|
|
668
|
-
|
|
669
|
-
# Resolve context ID for display and persistence
|
|
670
|
-
context_id = _resolve_context_id(session_id)
|
|
671
|
-
|
|
672
|
-
# Render context section
|
|
673
|
-
render_context(mode, context_pct, context_k, max_k, time_display, model_name)
|
|
674
|
-
|
|
675
|
-
# Render git section
|
|
676
|
-
git = get_git_status(current_dir)
|
|
677
|
-
if git:
|
|
678
|
-
render_git(mode, git, dir_name)
|
|
679
|
-
|
|
680
|
-
# Render context manager line (line 3) with separator
|
|
681
|
-
if context_id:
|
|
682
|
-
print(SEPARATOR)
|
|
683
|
-
context_state = _load_context_state(context_id)
|
|
684
|
-
render_context_manager(mode, context_id, context_state)
|
|
685
|
-
|
|
686
|
-
# Persist context_window to state.json
|
|
687
|
-
if context_id:
|
|
688
|
-
_write_context_window(context_id, {
|
|
689
|
-
"used_percentage": context_pct,
|
|
690
|
-
"remaining_percentage": 100 - context_pct,
|
|
691
|
-
"context_window_size": context_max,
|
|
692
|
-
"tokens_used": context_used,
|
|
693
|
-
"total_input_tokens": total_input,
|
|
694
|
-
"total_output_tokens": output_tokens,
|
|
695
|
-
"model": model_name,
|
|
696
|
-
"last_updated": datetime.now().isoformat(timespec="seconds"),
|
|
697
|
-
})
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
if __name__ == "__main__":
|
|
701
|
-
main()
|