cc-session-tools 3.7.0__tar.gz → 3.7.1__tar.gz

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 (291) hide show
  1. {cc_session_tools-3.7.0/src/cc_session_tools.egg-info → cc_session_tools-3.7.1}/PKG-INFO +1 -1
  2. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/pyproject.toml +1 -1
  3. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/cli/ccst.py +33 -17
  4. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/doctor.py +11 -6
  5. cc_session_tools-3.7.1/src/cc_session_tools/lib/text_wrap.py +35 -0
  6. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1/src/cc_session_tools.egg-info}/PKG-INFO +1 -1
  7. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools.egg-info/SOURCES.txt +3 -0
  8. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccst_doctor.py +23 -0
  9. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccst_hooks_install_zero_arg.py +47 -11
  10. cc_session_tools-3.7.1/tests/test_hooks_install_records.py +104 -0
  11. cc_session_tools-3.7.1/tests/test_text_wrap.py +70 -0
  12. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/LICENSE +0 -0
  13. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/README.md +0 -0
  14. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/setup.cfg +0 -0
  15. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/__init__.py +0 -0
  16. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/cli/__init__.py +0 -0
  17. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/cli/ccd.py +0 -0
  18. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/cli/ccmsg.py +0 -0
  19. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/cli/ccr.py +0 -0
  20. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/cli/ccs.py +0 -0
  21. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/cli/ccsched.py +0 -0
  22. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/cli/migrate_ccmsg.py +0 -0
  23. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/cli/migrate_ccsched.py +0 -0
  24. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/cli/migrate_sessions_db.py +0 -0
  25. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/cli/migrate_telemetry.py +0 -0
  26. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/config/hooks-bundle.json +0 -0
  27. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/config/session-tag-hook.json +0 -0
  28. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/hooks_install.py +0 -0
  29. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/__init__.py +0 -0
  30. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/bun_tools.py +0 -0
  31. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/claude_flags.py +0 -0
  32. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/claude_md_install.py +0 -0
  33. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/context_overrides.py +0 -0
  34. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/db.py +0 -0
  35. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/debug.py +0 -0
  36. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/doctor_mutes.py +0 -0
  37. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/hook_registry.py +0 -0
  38. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/install_sync.py +0 -0
  39. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/levenshtein.py +0 -0
  40. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/machine_identity.py +0 -0
  41. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/messaging/__init__.py +0 -0
  42. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/messaging/addressing.py +0 -0
  43. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/messaging/cursor.py +0 -0
  44. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/messaging/lock.py +0 -0
  45. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/messaging/message.py +0 -0
  46. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/messaging/move_safety.py +0 -0
  47. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/messaging/repository.py +0 -0
  48. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/messaging/retention.py +0 -0
  49. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/messaging/service.py +0 -0
  50. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/messaging/store.py +0 -0
  51. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/occupancy.py +0 -0
  52. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/paths.py +0 -0
  53. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/__init__.py +0 -0
  54. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/backup.py +0 -0
  55. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/classify.py +0 -0
  56. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/cutover.py +0 -0
  57. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/dump.py +0 -0
  58. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/formatting.py +0 -0
  59. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/importers.py +0 -0
  60. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/init_paths.py +0 -0
  61. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/init_service.py +0 -0
  62. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/manifest.py +0 -0
  63. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/naming.py +0 -0
  64. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/rehydrate.py +0 -0
  65. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/rename_group.py +0 -0
  66. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/reorganize.py +0 -0
  67. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/repository.py +0 -0
  68. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/resolve.py +0 -0
  69. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/service.py +0 -0
  70. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/session_output.py +0 -0
  71. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/store.py +0 -0
  72. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/sync_check.py +0 -0
  73. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/sync_lock.py +0 -0
  74. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/sync_notify.py +0 -0
  75. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/vector_clock.py +0 -0
  76. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/vector_clock_store.py +0 -0
  77. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/verify.py +0 -0
  78. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/write_log.py +0 -0
  79. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/picker.py +0 -0
  80. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/proc_lock.py +0 -0
  81. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/prompts.py +0 -0
  82. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/roots.py +0 -0
  83. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/rules.py +0 -0
  84. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/__init__.py +0 -0
  85. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/bundled_jobs.py +0 -0
  86. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/cadence.py +0 -0
  87. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/cursor.py +0 -0
  88. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/digest.py +0 -0
  89. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/due.py +0 -0
  90. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/duration.py +0 -0
  91. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/jobspec.py +0 -0
  92. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/ledger.py +0 -0
  93. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/lock.py +0 -0
  94. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/notify.py +0 -0
  95. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/reconcile.py +0 -0
  96. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/registry.py +0 -0
  97. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/runner.py +0 -0
  98. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/state.py +0 -0
  99. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/store.py +0 -0
  100. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/surface.py +0 -0
  101. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/throttle.py +0 -0
  102. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/worker.py +0 -0
  103. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/session_gc.py +0 -0
  104. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/sessions.py +0 -0
  105. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/sessions_db.py +0 -0
  106. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/sessions_repair.py +0 -0
  107. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/shell_install.py +0 -0
  108. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/tasklist.py +0 -0
  109. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/telemetry_store.py +0 -0
  110. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/prompts/pdata-migration-claude-md-update.md +0 -0
  111. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/prompts/pdata-migration-skills-update.md +0 -0
  112. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/analyse-cc-usage/SKILL.md +0 -0
  113. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/clean-hook-sessions/SKILL.md +0 -0
  114. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/clean-hook-sessions/scripts/clean-hook-sessions.py +0 -0
  115. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/compact-smart/SKILL.md +0 -0
  116. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/context-override/SKILL.md +0 -0
  117. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/delete-sessions/SKILL.md +0 -0
  118. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/delete-sessions/scripts/delete_sessions.py +0 -0
  119. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/do-executor-critic-assessor-loop/SKILL.md +0 -0
  120. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/extract-pptx/SKILL.md +0 -0
  121. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/extract-pptx/scripts/extract_pptx.py +0 -0
  122. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/extract-to-text/SKILL.md +0 -0
  123. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/extract-to-text/scripts/extract_to_text.py +0 -0
  124. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/find-claude-code-session/SKILL.md +0 -0
  125. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/generate-8digit-code/SKILL.md +0 -0
  126. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/generate-8digit-code/scripts/generate_8digit_code.py +0 -0
  127. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/list-empty-sessions/SKILL.md +0 -0
  128. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/list-empty-sessions/scripts/list_empty_sessions.py +0 -0
  129. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/manage-recurring-cc-jobs-using-ccsched/SKILL.md +0 -0
  130. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/move-session/SKILL.md +0 -0
  131. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/move-session/hooks/sessionstart-pending-rename.sh +0 -0
  132. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/move-session/scripts/cc_session_rules.py +0 -0
  133. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/move-session/scripts/move_session.py +0 -0
  134. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/pm-pdata-design-schema/SKILL.md +0 -0
  135. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/pm-pdata-do-audit-and-prepare-to-migrate/SKILL.md +0 -0
  136. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/pm-pdata-do-init/SKILL.md +0 -0
  137. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/pm-pdata-do-manual-migration/SKILL.md +0 -0
  138. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/pm-pdata-resolve-conflicts/SKILL.md +0 -0
  139. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/pm-project-layout-reference/SKILL.md +0 -0
  140. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/pm-update-central-files/SKILL.md +0 -0
  141. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/reduce-persistent-context/CALIBRATION.md +0 -0
  142. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/reduce-persistent-context/SKILL.md +0 -0
  143. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/reduce-persistent-context/scripts/analyze_context.py +0 -0
  144. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/reduce-persistent-context/scripts/duplicates.py +0 -0
  145. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/reduce-persistent-context/scripts/measure.py +0 -0
  146. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/reduce-persistent-context/scripts/report.py +0 -0
  147. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/reduce-persistent-context/scripts/tokens.py +0 -0
  148. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/reduce-persistent-context/scripts/usage.py +0 -0
  149. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/select-agent-model/SKILL.md +0 -0
  150. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/send-session-message/SKILL.md +0 -0
  151. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/update-command-cache/SKILL.md +0 -0
  152. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/update-command-cache/scripts/update_command_cache.py +0 -0
  153. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools.egg-info/dependency_links.txt +0 -0
  154. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools.egg-info/entry_points.txt +0 -0
  155. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools.egg-info/requires.txt +0 -0
  156. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/cc_session_tools.egg-info/top_level.txt +0 -0
  157. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/claude_code_usage/__init__.py +0 -0
  158. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/claude_code_usage/attribution.py +0 -0
  159. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/claude_code_usage/cache.py +0 -0
  160. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/claude_code_usage/ccusage_wrapper.py +0 -0
  161. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/claude_code_usage/cli.py +0 -0
  162. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/claude_code_usage/data/pricing.json +0 -0
  163. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/claude_code_usage/parent_inference.py +0 -0
  164. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/claude_code_usage/parser.py +0 -0
  165. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/claude_code_usage/pricing.py +0 -0
  166. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/claude_code_usage/query.py +0 -0
  167. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/claude_code_usage/report.py +0 -0
  168. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/claude_code_usage/schema.py +0 -0
  169. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/claude_code_usage/session_names.py +0 -0
  170. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/hooks/__init__.py +0 -0
  171. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/hooks/after_response.py +0 -0
  172. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/hooks/bash_hard_deny.py +0 -0
  173. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/hooks/bash_security_review.py +0 -0
  174. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/hooks/cache.py +0 -0
  175. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/hooks/catchup.py +0 -0
  176. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/hooks/confirm_8digit.py +0 -0
  177. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/hooks/context_window_warning.py +0 -0
  178. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/hooks/last_screenshot.py +0 -0
  179. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/hooks/marker_allow.py +0 -0
  180. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/hooks/markers.py +0 -0
  181. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/hooks/messaging_deliver.py +0 -0
  182. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/hooks/model_info.py +0 -0
  183. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/hooks/normalise.py +0 -0
  184. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/hooks/pdata_sync.py +0 -0
  185. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/hooks/pending_migration.py +0 -0
  186. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/hooks/pending_rename.py +0 -0
  187. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/hooks/script_allowlist.py +0 -0
  188. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/hooks/session_tag.py +0 -0
  189. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/hooks/stats.py +0 -0
  190. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/hooks/telemetry.py +0 -0
  191. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/hooks/telemetry_query.py +0 -0
  192. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/hooks/telemetry_trim.py +0 -0
  193. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/hooks/transcript.py +0 -0
  194. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/src/hooks/worklog_guard.py +0 -0
  195. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_after_response.py +0 -0
  196. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_attribution.py +0 -0
  197. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_bash_hard_deny.py +0 -0
  198. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_bash_security_review.py +0 -0
  199. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_bash_security_review_allowlist.py +0 -0
  200. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_bun_tools.py +0 -0
  201. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_cache.py +0 -0
  202. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_cache_sqlite.py +0 -0
  203. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccr_orphans.py +0 -0
  204. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccs_emptiness.py +0 -0
  205. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccs_eta.py +0 -0
  206. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccs_list_mode.py +0 -0
  207. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccs_scope_flags.py +0 -0
  208. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccs_sentinel_sort.py +0 -0
  209. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccs_session_counts.py +0 -0
  210. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccst_bundle_discovery.py +0 -0
  211. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccst_ccsched_jobs_cli.py +0 -0
  212. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccst_cli.py +0 -0
  213. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccst_context_override_cli.py +0 -0
  214. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccst_gc_prune.py +0 -0
  215. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccst_gc_report.py +0 -0
  216. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccst_hook_dispatcher.py +0 -0
  217. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccst_hooks_allowlist.py +0 -0
  218. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccst_hooks_install_prune.py +0 -0
  219. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccst_hooks_uninstall.py +0 -0
  220. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccst_install_everything.py +0 -0
  221. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccst_machine_identity_cli.py +0 -0
  222. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccst_main_gate.py +0 -0
  223. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccst_pdata_cli.py +0 -0
  224. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccst_pdata_init_cli.py +0 -0
  225. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccst_pdata_reconcile_cli.py +0 -0
  226. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccst_pdata_rename_group_cli.py +0 -0
  227. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccst_pdata_reorganize_cli.py +0 -0
  228. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccst_pdata_sync_cli.py +0 -0
  229. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccst_pdata_verify_cli.py +0 -0
  230. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccst_repair_sessions_cli.py +0 -0
  231. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccst_sessions_cli.py +0 -0
  232. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccst_shell_install.py +0 -0
  233. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccst_skills_install.py +0 -0
  234. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccst_skills_uninstall.py +0 -0
  235. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccst_telemetry_query.py +0 -0
  236. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccst_telemetry_trim.py +0 -0
  237. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_ccusage_wrapper.py +0 -0
  238. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_claude_flags.py +0 -0
  239. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_claude_md_install.py +0 -0
  240. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_cli.py +0 -0
  241. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_cli_ccd.py +0 -0
  242. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_cli_ccd_dry_run.py +0 -0
  243. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_cli_ccr.py +0 -0
  244. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_cli_ccs.py +0 -0
  245. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_cli_version.py +0 -0
  246. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_confirm_8digit.py +0 -0
  247. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_context_overrides.py +0 -0
  248. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_context_window_warning.py +0 -0
  249. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_db.py +0 -0
  250. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_debug.py +0 -0
  251. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_doctor_mutes.py +0 -0
  252. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_empty_session.py +0 -0
  253. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_hook_cache.py +0 -0
  254. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_hooks_install.py +0 -0
  255. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_install_sync.py +0 -0
  256. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_last_screenshot.py +0 -0
  257. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_levenshtein.py +0 -0
  258. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_machine_identity.py +0 -0
  259. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_marker_allow.py +0 -0
  260. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_migrate_ccmsg.py +0 -0
  261. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_migrate_ccsched.py +0 -0
  262. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_migrate_sessions_db.py +0 -0
  263. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_migrate_telemetry.py +0 -0
  264. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_model_info.py +0 -0
  265. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_normalise.py +0 -0
  266. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_occupancy.py +0 -0
  267. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_parent_inference.py +0 -0
  268. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_parser.py +0 -0
  269. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_paths.py +0 -0
  270. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_pending_migration_hook.py +0 -0
  271. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_picker.py +0 -0
  272. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_pricing.py +0 -0
  273. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_proc_lock.py +0 -0
  274. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_prompts.py +0 -0
  275. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_query.py +0 -0
  276. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_report.py +0 -0
  277. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_roots.py +0 -0
  278. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_rules.py +0 -0
  279. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_scheduler_bundled_jobs.py +0 -0
  280. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_schema.py +0 -0
  281. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_script_allowlist.py +0 -0
  282. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_session_names.py +0 -0
  283. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_session_tag.py +0 -0
  284. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_sessions.py +0 -0
  285. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_sessions_db.py +0 -0
  286. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_sessions_repair.py +0 -0
  287. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_tasklist.py +0 -0
  288. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_telemetry.py +0 -0
  289. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_telemetry_store.py +0 -0
  290. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_transcript.py +0 -0
  291. {cc_session_tools-3.7.0 → cc_session_tools-3.7.1}/tests/test_worklog_guard.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cc-session-tools
3
- Version: 3.7.0
3
+ Version: 3.7.1
4
4
  Summary: Claude Code session management CLI tools (ccd, ccr, ccs, claude-code-usage, ccst) and hook library (hooks package)
5
5
  Author: raffishquartan
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cc-session-tools"
7
- version = "3.7.0"
7
+ version = "3.7.1"
8
8
  description = "Claude Code session management CLI tools (ccd, ccr, ccs, claude-code-usage, ccst) and hook library (hooks package)"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -112,6 +112,7 @@ from cc_session_tools.hooks_install import (
112
112
  )
113
113
  from cc_session_tools.lib import machine_identity
114
114
  from cc_session_tools.lib.hook_registry import HOOK_DESCRIPTIONS, HOOK_VERBS
115
+ from cc_session_tools.lib.text_wrap import wrap_block
115
116
 
116
117
  if TYPE_CHECKING:
117
118
  # Type-only, so the handler's own lazy `from ... import sync_check` (this file's convention:
@@ -538,30 +539,45 @@ def _bundle_inventory(bundle: dict[str, Any]) -> list[tuple[str, str, str | None
538
539
  return out
539
540
 
540
541
 
541
- def _print_hooks_install_table(
542
+ def _format_hooks_install_records(
542
543
  inventory: list[tuple[str, str, str | None, str]],
543
544
  added_keys: set[tuple[str, str | None, str]],
544
- ) -> None:
545
- """Print a Hook | Status | Event | Description table to stdout."""
546
- headers = ("Hook", "Status", "Event", "Description")
547
- if not inventory:
548
- return
545
+ ) -> str:
546
+ """One record per hook: a `name status` header, an indented `Events:` line, then the
547
+ indented, terminal-width-wrapped description. Records are separated by a blank line.
549
548
 
550
- rows: list[tuple[str, str, str, str]] = []
549
+ A hook registered for several events is one record; when only some of its registrations
550
+ would be added, those events carry a `(new)` marker. Order follows the inventory (sorted by
551
+ hook name).
552
+ """
553
+ by_hook: dict[str, list[tuple[bool, str]]] = {}
551
554
  for name, event, matcher, cmd in inventory:
552
- status = "install" if (event, matcher, cmd) in added_keys else "already-installed"
553
555
  event_label = f"{event}[{matcher}]" if matcher else event
556
+ by_hook.setdefault(name, []).append(((event, matcher, cmd) in added_keys, event_label))
557
+
558
+ records: list[str] = []
559
+ for name, registrations in by_hook.items():
560
+ any_new = any(is_new for is_new, _ in registrations)
561
+ mixed = any_new and not all(is_new for is_new, _ in registrations)
562
+ status = "install" if any_new else "already-installed"
563
+ events = ", ".join(
564
+ f"{label} (new)" if mixed and is_new else label for is_new, label in registrations
565
+ )
566
+ lines = [f"{name} {status}", wrap_block(f"Events: {events}")]
554
567
  description = HOOK_DESCRIPTIONS.get(name, "")
555
- rows.append((name, status, event_label, description))
568
+ if description:
569
+ lines.append(wrap_block(description))
570
+ records.append("\n".join(lines))
571
+ return "\n\n".join(records)
556
572
 
557
- widths = [
558
- max([len(headers[i])] + [len(r[i]) for r in rows]) for i in range(4)
559
- ]
560
- fmt = f"{{:<{widths[0]}}} {{:<{widths[1]}}} {{:<{widths[2]}}} {{:<{widths[3]}}}"
561
- print(fmt.format(*headers))
562
- print(fmt.format(*("-" * w for w in widths)))
563
- for row in rows:
564
- print(fmt.format(*row))
573
+
574
+ def _print_hooks_install_table(
575
+ inventory: list[tuple[str, str, str | None, str]],
576
+ added_keys: set[tuple[str, str | None, str]],
577
+ ) -> None:
578
+ """Print the per-hook install records to stdout."""
579
+ if inventory:
580
+ print(_format_hooks_install_records(inventory, added_keys))
565
581
 
566
582
 
567
583
  def _list_bundle_hook_names(bundle: dict[str, Any]) -> list[str]:
@@ -14,7 +14,6 @@ import os
14
14
  import shutil
15
15
  import sqlite3
16
16
  import subprocess
17
- import textwrap
18
17
  import time
19
18
  from dataclasses import dataclass
20
19
  from enum import Enum
@@ -29,6 +28,7 @@ from cc_session_tools.lib.pdata.init_paths import (
29
28
  MIGRATED_ARCHIVE_DIRNAME,
30
29
  MIGRATED_MANIFEST_FILENAME,
31
30
  )
31
+ from cc_session_tools.lib.text_wrap import wrap_block
32
32
 
33
33
  if TYPE_CHECKING:
34
34
  from cc_session_tools.lib.install_sync import FailedAttempt
@@ -1224,12 +1224,11 @@ def _wrap_reason(reason: str, *, indent: str = " ") -> str:
1224
1224
  embedded paths and remediation commands); printed on a single line they
1225
1225
  hard-wrap at the terminal edge with no continuation indent, making a
1226
1226
  wrapped reason indistinguishable from a new, unrelated result. Wrapping
1227
- to the real terminal width — falling back to 80 columns for non-tty
1227
+ to the real terminal width - falling back to 80 columns for non-tty
1228
1228
  output (piped, redirected, or the scheduled drift-monitor job whose
1229
- stdout is forwarded to Telegram) — and indenting every line fixes that.
1229
+ stdout is forwarded to Telegram) - and indenting every line fixes that.
1230
1230
  """
1231
- width = shutil.get_terminal_size(fallback=(80, 24)).columns
1232
- return textwrap.fill(reason, width=width, initial_indent=indent, subsequent_indent=indent)
1231
+ return wrap_block(reason, indent=indent)
1233
1232
 
1234
1233
 
1235
1234
  def format_results(results: list[CheckResult], *, show_all: bool = False) -> str:
@@ -1265,7 +1264,13 @@ def format_results(results: list[CheckResult], *, show_all: bool = False) -> str
1265
1264
  lines.append(f"All {len(results)} checks OK.")
1266
1265
  has_issues = any(r.status in (Status.WARN, Status.FAIL) for r in results)
1267
1266
  if has_issues:
1268
- lines.append("\nTip: run `ccst install-everything --apply` to sync skills, hooks, shell, and CLAUDE.md")
1267
+ lines.append("")
1268
+ lines.append(
1269
+ wrap_block(
1270
+ "Tip: run `ccst install-everything --apply` to sync skills, hooks, shell, and CLAUDE.md",
1271
+ indent="",
1272
+ )
1273
+ )
1269
1274
  if not show_all:
1270
1275
  lines.append("\nTo see full doctor output use --all argument")
1271
1276
  return "\n".join(lines)
@@ -0,0 +1,35 @@
1
+ """Terminal-width word wrapping shared by `ccst doctor` and the hooks install output."""
2
+ from __future__ import annotations
3
+
4
+ import shutil
5
+ import textwrap
6
+
7
+ FALLBACK_WIDTH = 80
8
+ MIN_WIDTH = 40
9
+
10
+
11
+ def effective_width() -> int:
12
+ """Width to wrap to: the detected terminal width, never below MIN_WIDTH.
13
+
14
+ `shutil.get_terminal_size` reads `COLUMNS`, then the real terminal via the process's stdout
15
+ file descriptor (so an in-process `redirect_stdout` capture still sees the real terminal),
16
+ and falls back to FALLBACK_WIDTH when neither is available. It is called through the module
17
+ attribute so tests can patch it.
18
+ """
19
+ return max(shutil.get_terminal_size(fallback=(FALLBACK_WIDTH, 24)).columns, MIN_WIDTH)
20
+
21
+
22
+ def wrap_block(text: str, *, indent: str = " ") -> str:
23
+ """Word-wrap `text` to the effective width, indenting every line.
24
+
25
+ Words are never split: a path, hyphenated name or backticked command that has no whitespace
26
+ stays on one line, and a single token longer than the width overflows rather than breaking.
27
+ """
28
+ return textwrap.fill(
29
+ text,
30
+ width=effective_width(),
31
+ initial_indent=indent,
32
+ subsequent_indent=indent,
33
+ break_long_words=False,
34
+ break_on_hyphens=False,
35
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cc-session-tools
3
- Version: 3.7.0
3
+ Version: 3.7.1
4
4
  Summary: Claude Code session management CLI tools (ccd, ccr, ccs, claude-code-usage, ccst) and hook library (hooks package)
5
5
  Author: raffishquartan
6
6
  License: MIT License
@@ -49,6 +49,7 @@ src/cc_session_tools/lib/sessions_repair.py
49
49
  src/cc_session_tools/lib/shell_install.py
50
50
  src/cc_session_tools/lib/tasklist.py
51
51
  src/cc_session_tools/lib/telemetry_store.py
52
+ src/cc_session_tools/lib/text_wrap.py
52
53
  src/cc_session_tools/lib/messaging/__init__.py
53
54
  src/cc_session_tools/lib/messaging/addressing.py
54
55
  src/cc_session_tools/lib/messaging/cursor.py
@@ -247,6 +248,7 @@ tests/test_doctor_mutes.py
247
248
  tests/test_empty_session.py
248
249
  tests/test_hook_cache.py
249
250
  tests/test_hooks_install.py
251
+ tests/test_hooks_install_records.py
250
252
  tests/test_install_sync.py
251
253
  tests/test_last_screenshot.py
252
254
  tests/test_levenshtein.py
@@ -282,5 +284,6 @@ tests/test_sessions_repair.py
282
284
  tests/test_tasklist.py
283
285
  tests/test_telemetry.py
284
286
  tests/test_telemetry_store.py
287
+ tests/test_text_wrap.py
285
288
  tests/test_transcript.py
286
289
  tests/test_worklog_guard.py
@@ -687,6 +687,29 @@ def test_wrap_reason_indents_every_line(monkeypatch: pytest.MonkeyPatch) -> None
687
687
  assert all(line.startswith(" ") for line in lines)
688
688
 
689
689
 
690
+ def test_wrap_reason_does_not_split_paths_or_hyphenated_names(monkeypatch: pytest.MonkeyPatch) -> None:
691
+ monkeypatch.setattr(shutil, "get_terminal_size", lambda fallback=(80, 24): os.terminal_size((60, 24)))
692
+ path = "/home/alice/.local/share/claude/claude-flags.json"
693
+ out = _wrap_reason(f"not yet created; will be created at {path} on first use")
694
+ assert path in out.split()
695
+
696
+
697
+ def test_wrap_reason_width_floor_is_40(monkeypatch: pytest.MonkeyPatch) -> None:
698
+ monkeypatch.setattr(shutil, "get_terminal_size", lambda fallback=(80, 24): os.terminal_size((10, 24)))
699
+ lines = _wrap_reason("word " * 30).splitlines()
700
+ assert all(len(line) <= 40 for line in lines)
701
+ assert max(len(line) for line in lines) > 10
702
+
703
+
704
+ def test_format_results_tip_line_wraps_at_80_columns(monkeypatch: pytest.MonkeyPatch) -> None:
705
+ monkeypatch.setattr(shutil, "get_terminal_size", lambda fallback=(80, 24): os.terminal_size((80, 24)))
706
+ out = format_results([CheckResult("foo", Status.WARN, "bad")])
707
+ tip_lines = out.split("Tip: ", 1)[1].split("\n\n", 1)[0].splitlines()
708
+ assert "Tip: run `ccst install-everything --apply`" in out
709
+ assert len(tip_lines) > 1
710
+ assert all(len(line) <= 80 for line in ["Tip: " + tip_lines[0], *tip_lines[1:]])
711
+
712
+
690
713
  # ---------- format_drift_report ----------
691
714
 
692
715
  def test_format_drift_report_empty_returns_empty_string() -> None:
@@ -2,6 +2,7 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import json
5
+ import os
5
6
  import subprocess
6
7
  import sys
7
8
  from pathlib import Path
@@ -9,15 +10,32 @@ from pathlib import Path
9
10
  import pytest
10
11
 
11
12
 
12
- def _run(*args: str, cwd: str | None = None) -> subprocess.CompletedProcess[str]:
13
+ def _run(
14
+ *args: str, cwd: str | None = None, columns: str | None = "80"
15
+ ) -> subprocess.CompletedProcess[str]:
16
+ """Run ccst with COLUMNS pinned (None removes it) so the developer's shell can't change the width."""
17
+ env = {k: v for k, v in os.environ.items() if k != "COLUMNS"}
18
+ if columns is not None:
19
+ env["COLUMNS"] = columns
13
20
  return subprocess.run(
14
21
  [sys.executable, "-m", "cc_session_tools.cli.ccst", *args],
15
22
  capture_output=True,
16
23
  text=True,
17
24
  cwd=cwd or str(Path(__file__).parent.parent),
25
+ env=env,
18
26
  )
19
27
 
20
28
 
29
+ def _record(stdout: str, name: str) -> list[str]:
30
+ """The lines of `name`'s record: its column-0 header plus the indented lines below it."""
31
+ lines = stdout.splitlines()
32
+ start = next(i for i, ln in enumerate(lines) if ln.startswith(f"{name} "))
33
+ end = start + 1
34
+ while end < len(lines) and lines[end].startswith(" "):
35
+ end += 1
36
+ return lines[start:end]
37
+
38
+
21
39
  def _write(path: Path, data: dict) -> None:
22
40
  path.write_text(json.dumps(data, indent=2))
23
41
 
@@ -225,15 +243,30 @@ ALL_HOOK_NAMES = (
225
243
  )
226
244
 
227
245
 
228
- def test_hooks_install_table_has_expected_headers(tmp_path: Path) -> None:
229
- """The table has Hook/Status/Event/Description column headers."""
246
+ def test_hooks_install_records_have_events_line_and_no_column_titles(tmp_path: Path) -> None:
247
+ """Each hook is a record: header line, indented `Events:` line, indented description."""
230
248
  tgt = tmp_path / "settings.json"
231
249
  _write(tgt, {})
232
250
  result = _run("hooks", "install", "--target", str(tgt))
233
251
  assert result.returncode == 0, result.stderr
234
- out = result.stdout
235
- for header in ("Hook", "Status", "Event", "Description"):
236
- assert header in out, f"missing header {header!r} in:\n{out}"
252
+ for name in ALL_HOOK_NAMES:
253
+ record = _record(result.stdout, name)
254
+ assert record[1].startswith(" Events: "), record
255
+ assert len(record) >= 3, record
256
+ assert "Description" not in result.stdout
257
+ assert "-----" not in result.stdout
258
+
259
+
260
+ def test_hooks_install_output_respects_terminal_width(tmp_path: Path) -> None:
261
+ """No indented record line exceeds COLUMNS; with COLUMNS unset the fallback is 80."""
262
+ tgt = tmp_path / "settings.json"
263
+ _write(tgt, {})
264
+ for columns, limit in (("80", 80), ("120", 120), (None, 80)):
265
+ result = _run("hooks", "install", "--target", str(tgt), columns=columns)
266
+ assert result.returncode == 0, result.stderr
267
+ for ln in result.stdout.splitlines():
268
+ if ln.startswith(" "):
269
+ assert len(ln) <= limit, f"COLUMNS={columns}: {ln!r}"
237
270
 
238
271
 
239
272
  def test_hooks_install_table_lists_all_bundled_hooks(tmp_path: Path) -> None:
@@ -291,11 +324,14 @@ def test_hooks_install_table_shows_descriptions(tmp_path: Path) -> None:
291
324
  tgt = tmp_path / "settings.json"
292
325
  _write(tgt, {})
293
326
  result = _run("hooks", "install", "--target", str(tgt))
294
- # Spot-check that the description column carries meaningful text per hook
295
- assert "shell commands" in result.stdout # bash-security-review
296
- assert "8-digit confirmation" in result.stdout
297
- assert "last-active" in result.stdout # after-response
298
- assert "session tag" in result.stdout
327
+ # Descriptions wrap by width, so compare against each record's lines joined together
328
+ def text(name: str) -> str:
329
+ return " ".join(" ".join(_record(result.stdout, name)[2:]).split())
330
+
331
+ assert "shell commands" in text("bash-security-review")
332
+ assert "8-digit confirmation" in text("confirm-8digit")
333
+ assert "last-active" in text("after-response")
334
+ assert "session tag" in text("session-tag")
299
335
 
300
336
 
301
337
  def test_hooks_install_hook_selector_table_only_named_row(tmp_path: Path) -> None:
@@ -0,0 +1,104 @@
1
+ """Record layout of the hooks install output (`_format_hooks_install_records`)."""
2
+ from __future__ import annotations
3
+
4
+ import os
5
+ import shutil
6
+
7
+ import pytest
8
+
9
+ from cc_session_tools.cli.ccst import _format_hooks_install_records
10
+
11
+ Inv = list[tuple[str, str, str | None, str]]
12
+
13
+
14
+ def _width(monkeypatch: pytest.MonkeyPatch, columns: int) -> None:
15
+ monkeypatch.setattr(
16
+ shutil, "get_terminal_size", lambda fallback=(80, 24): os.terminal_size((columns, 24))
17
+ )
18
+
19
+
20
+ def _inv() -> Inv:
21
+ return [
22
+ ("catchup", "SessionStart", None, "ccst hooks run catchup"),
23
+ ("catchup", "UserPromptSubmit", None, "ccst hooks run catchup"),
24
+ ("confirm-8digit", "PreToolUse", None, "ccst hooks run confirm-8digit"),
25
+ ]
26
+
27
+
28
+ def test_one_record_per_hook_name(monkeypatch: pytest.MonkeyPatch) -> None:
29
+ _width(monkeypatch, 80)
30
+ out = _format_hooks_install_records(_inv(), set())
31
+ headers = [ln for ln in out.splitlines() if ln and not ln.startswith(" ")]
32
+ assert [h.split()[0] for h in headers] == ["catchup", "confirm-8digit"]
33
+
34
+
35
+ def test_record_shape_and_blank_line_separation(monkeypatch: pytest.MonkeyPatch) -> None:
36
+ _width(monkeypatch, 80)
37
+ out = _format_hooks_install_records(_inv(), set())
38
+ assert not out.endswith("\n")
39
+ first, second = out.split("\n\n")
40
+ lines = first.splitlines()
41
+ assert lines[0].startswith("catchup ")
42
+ assert lines[1] == " Events: SessionStart, UserPromptSubmit"
43
+ assert all(ln.startswith(" ") for ln in lines[1:])
44
+ assert second.splitlines()[0].startswith("confirm-8digit ")
45
+ assert "Description" not in out and "---" not in out
46
+
47
+
48
+ def test_description_printed_once_for_multi_event_hook(monkeypatch: pytest.MonkeyPatch) -> None:
49
+ _width(monkeypatch, 200)
50
+ out = _format_hooks_install_records(_inv(), set())
51
+ assert out.count("Reconciles+launches") == 1
52
+
53
+
54
+ def test_status_all_new_and_all_installed(monkeypatch: pytest.MonkeyPatch) -> None:
55
+ _width(monkeypatch, 80)
56
+ inv = _inv()
57
+ added = {(e, m, c) for _, e, m, c in inv}
58
+ new = _format_hooks_install_records(inv, added)
59
+ assert new.splitlines()[0].split()[1] == "install"
60
+ assert "(new)" not in new
61
+ old = _format_hooks_install_records(inv, set())
62
+ assert old.splitlines()[0].split()[1] == "already-installed"
63
+
64
+
65
+ def test_partly_installed_multi_event_hook_marks_new_events(
66
+ monkeypatch: pytest.MonkeyPatch,
67
+ ) -> None:
68
+ _width(monkeypatch, 80)
69
+ added: set[tuple[str, str | None, str]] = {("UserPromptSubmit", None, "ccst hooks run catchup")}
70
+ out = _format_hooks_install_records(_inv(), added)
71
+ lines = out.splitlines()
72
+ assert lines[0].split()[1] == "install"
73
+ assert lines[1] == " Events: SessionStart, UserPromptSubmit (new)"
74
+
75
+
76
+ def test_no_events_or_description_line_exceeds_width(monkeypatch: pytest.MonkeyPatch) -> None:
77
+ _width(monkeypatch, 80)
78
+ from cc_session_tools.lib.hook_registry import HOOK_DESCRIPTIONS
79
+
80
+ inv: Inv = [(n, "SessionStart", None, f"ccst hooks run {n}") for n in HOOK_DESCRIPTIONS]
81
+ out = _format_hooks_install_records(inv, set())
82
+ for ln in out.splitlines():
83
+ if ln.startswith(" "):
84
+ assert len(ln) <= 80, ln
85
+
86
+
87
+ def test_no_description_prints_header_and_events_only(monkeypatch: pytest.MonkeyPatch) -> None:
88
+ _width(monkeypatch, 80)
89
+ cmd = "/opt/tools/run-thing.sh --flag value"
90
+ out = _format_hooks_install_records([(cmd, "Stop", None, cmd)], set())
91
+ lines = out.splitlines()
92
+ assert lines[0].startswith(cmd)
93
+ assert lines[1] == " Events: Stop"
94
+ assert len(lines) == 2
95
+
96
+
97
+ def test_matcher_in_event_label(monkeypatch: pytest.MonkeyPatch) -> None:
98
+ _width(monkeypatch, 80)
99
+ inv: Inv = [("marker-allow", "PreToolUse", "Bash", "ccst hooks run marker-allow")]
100
+ assert "Events: PreToolUse[Bash]" in _format_hooks_install_records(inv, set())
101
+
102
+
103
+ def test_empty_inventory_is_empty_string() -> None:
104
+ assert _format_hooks_install_records([], set()) == ""
@@ -0,0 +1,70 @@
1
+ """Shared terminal-width wrapping used by `ccst doctor` and the hooks install output."""
2
+ from __future__ import annotations
3
+
4
+ import os
5
+ import shutil
6
+
7
+ import pytest
8
+
9
+ from cc_session_tools.lib import text_wrap
10
+
11
+
12
+ def _width(monkeypatch: pytest.MonkeyPatch, columns: int) -> None:
13
+ monkeypatch.setattr(
14
+ shutil, "get_terminal_size", lambda fallback=(80, 24): os.terminal_size((columns, 24))
15
+ )
16
+
17
+
18
+ def test_wraps_to_detected_width(monkeypatch: pytest.MonkeyPatch) -> None:
19
+ _width(monkeypatch, 80)
20
+ out = text_wrap.wrap_block("word " * 60)
21
+ lines = out.splitlines()
22
+ assert len(lines) > 1
23
+ assert all(len(line) <= 80 for line in lines)
24
+
25
+
26
+ def test_wide_terminal_wraps_to_fewer_lines(monkeypatch: pytest.MonkeyPatch) -> None:
27
+ text = "word " * 60
28
+ _width(monkeypatch, 80)
29
+ narrow = len(text_wrap.wrap_block(text).splitlines())
30
+ _width(monkeypatch, 200)
31
+ wide = len(text_wrap.wrap_block(text).splitlines())
32
+ assert wide < narrow
33
+
34
+
35
+ def test_every_line_carries_the_indent(monkeypatch: pytest.MonkeyPatch) -> None:
36
+ _width(monkeypatch, 60)
37
+ lines = text_wrap.wrap_block("word " * 40, indent=" ").splitlines()
38
+ assert len(lines) > 1
39
+ assert all(line.startswith(" ") and not line.startswith(" ") for line in lines)
40
+
41
+
42
+ def test_width_floor_is_40_columns(monkeypatch: pytest.MonkeyPatch) -> None:
43
+ _width(monkeypatch, 20)
44
+ assert text_wrap.effective_width() == 40
45
+ lines = text_wrap.wrap_block("word " * 40).splitlines()
46
+ assert max(len(line) for line in lines) > 20
47
+ assert all(len(line) <= 40 for line in lines)
48
+
49
+
50
+ def test_non_tty_fallback_is_80_columns(monkeypatch: pytest.MonkeyPatch) -> None:
51
+ monkeypatch.setattr(
52
+ shutil, "get_terminal_size", lambda fallback=(80, 24): os.terminal_size(fallback)
53
+ )
54
+ assert text_wrap.effective_width() == 80
55
+
56
+
57
+ def test_path_and_hyphenated_name_are_never_split(monkeypatch: pytest.MonkeyPatch) -> None:
58
+ _width(monkeypatch, 40)
59
+ path = "/Users/x/.local/share/claude/claude-flags.json"
60
+ text = "not yet created; will be created at " + path + " on first use"
61
+ out = text_wrap.wrap_block(text)
62
+ assert path in out.split()
63
+ assert "context-window-warning" in text_wrap.wrap_block("nudge from " * 3 + "context-window-warning")
64
+
65
+
66
+ def test_overlong_token_overflows_unsplit(monkeypatch: pytest.MonkeyPatch) -> None:
67
+ _width(monkeypatch, 40)
68
+ token = "x" * 70
69
+ lines = text_wrap.wrap_block(f"see {token} now").splitlines()
70
+ assert any(line.strip() == token for line in lines)