cc-session-tools 3.6.1__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.
- {cc_session_tools-3.6.1/src/cc_session_tools.egg-info → cc_session_tools-3.7.1}/PKG-INFO +3 -3
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/README.md +2 -2
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/pyproject.toml +1 -1
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/cli/ccst.py +114 -17
- cc_session_tools-3.7.1/src/cc_session_tools/lib/bun_tools.py +39 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/doctor.py +79 -6
- cc_session_tools-3.7.1/src/cc_session_tools/lib/text_wrap.py +35 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/analyse-cc-usage/SKILL.md +22 -2
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1/src/cc_session_tools.egg-info}/PKG-INFO +3 -3
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools.egg-info/SOURCES.txt +9 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/claude_code_usage/ccusage_wrapper.py +12 -6
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/hooks/bash_security_review.py +166 -13
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/hooks/cache.py +94 -0
- cc_session_tools-3.7.1/src/hooks/script_allowlist.py +145 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_bash_security_review.py +78 -4
- cc_session_tools-3.7.1/tests/test_bash_security_review_allowlist.py +330 -0
- cc_session_tools-3.7.1/tests/test_bun_tools.py +67 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccst_doctor.py +124 -0
- cc_session_tools-3.7.1/tests/test_ccst_hooks_allowlist.py +114 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccst_hooks_install_zero_arg.py +47 -11
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccusage_wrapper.py +55 -0
- cc_session_tools-3.7.1/tests/test_hooks_install_records.py +104 -0
- cc_session_tools-3.7.1/tests/test_script_allowlist.py +288 -0
- cc_session_tools-3.7.1/tests/test_text_wrap.py +70 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/LICENSE +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/setup.cfg +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/__init__.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/cli/__init__.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/cli/ccd.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/cli/ccmsg.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/cli/ccr.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/cli/ccs.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/cli/ccsched.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/cli/migrate_ccmsg.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/cli/migrate_ccsched.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/cli/migrate_sessions_db.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/cli/migrate_telemetry.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/config/hooks-bundle.json +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/config/session-tag-hook.json +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/hooks_install.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/__init__.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/claude_flags.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/claude_md_install.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/context_overrides.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/db.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/debug.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/doctor_mutes.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/hook_registry.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/install_sync.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/levenshtein.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/machine_identity.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/messaging/__init__.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/messaging/addressing.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/messaging/cursor.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/messaging/lock.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/messaging/message.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/messaging/move_safety.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/messaging/repository.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/messaging/retention.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/messaging/service.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/messaging/store.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/occupancy.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/paths.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/__init__.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/backup.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/classify.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/cutover.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/dump.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/formatting.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/importers.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/init_paths.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/init_service.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/manifest.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/naming.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/rehydrate.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/rename_group.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/reorganize.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/repository.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/resolve.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/service.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/session_output.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/store.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/sync_check.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/sync_lock.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/sync_notify.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/vector_clock.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/vector_clock_store.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/verify.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/pdata/write_log.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/picker.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/proc_lock.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/prompts.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/roots.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/rules.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/__init__.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/bundled_jobs.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/cadence.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/cursor.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/digest.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/due.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/duration.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/jobspec.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/ledger.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/lock.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/notify.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/reconcile.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/registry.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/runner.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/state.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/store.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/surface.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/throttle.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/scheduler/worker.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/session_gc.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/sessions.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/sessions_db.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/sessions_repair.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/shell_install.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/tasklist.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/lib/telemetry_store.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/prompts/pdata-migration-claude-md-update.md +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/prompts/pdata-migration-skills-update.md +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/clean-hook-sessions/SKILL.md +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/clean-hook-sessions/scripts/clean-hook-sessions.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/compact-smart/SKILL.md +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/context-override/SKILL.md +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/delete-sessions/SKILL.md +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/delete-sessions/scripts/delete_sessions.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/do-executor-critic-assessor-loop/SKILL.md +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/extract-pptx/SKILL.md +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/extract-pptx/scripts/extract_pptx.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/extract-to-text/SKILL.md +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/extract-to-text/scripts/extract_to_text.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/find-claude-code-session/SKILL.md +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/generate-8digit-code/SKILL.md +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/generate-8digit-code/scripts/generate_8digit_code.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/list-empty-sessions/SKILL.md +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/list-empty-sessions/scripts/list_empty_sessions.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/manage-recurring-cc-jobs-using-ccsched/SKILL.md +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/move-session/SKILL.md +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/move-session/hooks/sessionstart-pending-rename.sh +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/move-session/scripts/cc_session_rules.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/move-session/scripts/move_session.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/pm-pdata-design-schema/SKILL.md +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/pm-pdata-do-audit-and-prepare-to-migrate/SKILL.md +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/pm-pdata-do-init/SKILL.md +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/pm-pdata-do-manual-migration/SKILL.md +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/pm-pdata-resolve-conflicts/SKILL.md +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/pm-project-layout-reference/SKILL.md +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/pm-update-central-files/SKILL.md +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/reduce-persistent-context/CALIBRATION.md +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/reduce-persistent-context/SKILL.md +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/reduce-persistent-context/scripts/analyze_context.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/reduce-persistent-context/scripts/duplicates.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/reduce-persistent-context/scripts/measure.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/reduce-persistent-context/scripts/report.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/reduce-persistent-context/scripts/tokens.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/reduce-persistent-context/scripts/usage.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/select-agent-model/SKILL.md +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/send-session-message/SKILL.md +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/update-command-cache/SKILL.md +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools/skills/update-command-cache/scripts/update_command_cache.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools.egg-info/dependency_links.txt +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools.egg-info/entry_points.txt +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools.egg-info/requires.txt +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/cc_session_tools.egg-info/top_level.txt +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/claude_code_usage/__init__.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/claude_code_usage/attribution.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/claude_code_usage/cache.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/claude_code_usage/cli.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/claude_code_usage/data/pricing.json +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/claude_code_usage/parent_inference.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/claude_code_usage/parser.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/claude_code_usage/pricing.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/claude_code_usage/query.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/claude_code_usage/report.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/claude_code_usage/schema.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/claude_code_usage/session_names.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/hooks/__init__.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/hooks/after_response.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/hooks/bash_hard_deny.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/hooks/catchup.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/hooks/confirm_8digit.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/hooks/context_window_warning.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/hooks/last_screenshot.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/hooks/marker_allow.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/hooks/markers.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/hooks/messaging_deliver.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/hooks/model_info.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/hooks/normalise.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/hooks/pdata_sync.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/hooks/pending_migration.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/hooks/pending_rename.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/hooks/session_tag.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/hooks/stats.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/hooks/telemetry.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/hooks/telemetry_query.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/hooks/telemetry_trim.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/hooks/transcript.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/src/hooks/worklog_guard.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_after_response.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_attribution.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_bash_hard_deny.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_cache.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_cache_sqlite.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccr_orphans.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccs_emptiness.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccs_eta.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccs_list_mode.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccs_scope_flags.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccs_sentinel_sort.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccs_session_counts.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccst_bundle_discovery.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccst_ccsched_jobs_cli.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccst_cli.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccst_context_override_cli.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccst_gc_prune.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccst_gc_report.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccst_hook_dispatcher.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccst_hooks_install_prune.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccst_hooks_uninstall.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccst_install_everything.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccst_machine_identity_cli.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccst_main_gate.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccst_pdata_cli.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccst_pdata_init_cli.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccst_pdata_reconcile_cli.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccst_pdata_rename_group_cli.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccst_pdata_reorganize_cli.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccst_pdata_sync_cli.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccst_pdata_verify_cli.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccst_repair_sessions_cli.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccst_sessions_cli.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccst_shell_install.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccst_skills_install.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccst_skills_uninstall.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccst_telemetry_query.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_ccst_telemetry_trim.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_claude_flags.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_claude_md_install.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_cli.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_cli_ccd.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_cli_ccd_dry_run.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_cli_ccr.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_cli_ccs.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_cli_version.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_confirm_8digit.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_context_overrides.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_context_window_warning.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_db.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_debug.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_doctor_mutes.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_empty_session.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_hook_cache.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_hooks_install.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_install_sync.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_last_screenshot.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_levenshtein.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_machine_identity.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_marker_allow.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_migrate_ccmsg.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_migrate_ccsched.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_migrate_sessions_db.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_migrate_telemetry.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_model_info.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_normalise.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_occupancy.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_parent_inference.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_parser.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_paths.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_pending_migration_hook.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_picker.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_pricing.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_proc_lock.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_prompts.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_query.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_report.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_roots.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_rules.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_scheduler_bundled_jobs.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_schema.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_session_names.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_session_tag.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_sessions.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_sessions_db.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_sessions_repair.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_tasklist.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_telemetry.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_telemetry_store.py +0 -0
- {cc_session_tools-3.6.1 → cc_session_tools-3.7.1}/tests/test_transcript.py +0 -0
- {cc_session_tools-3.6.1 → 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.
|
|
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
|
|
@@ -105,7 +105,7 @@ bundled scheduled jobs:
|
|
|
105
105
|
| **`session-tag`** (SessionStart) | Writes a `<uuid>.tag` file so `claude-code-usage` can map session UUIDs to human-readable names. |
|
|
106
106
|
| **`last-screenshot`** (UserPromptSubmit) | Resolves your newest screenshot for the `>lss` token and injects its path. Requires `CCST_SCREENSHOT_DIR`. |
|
|
107
107
|
| **`bash-hard-deny`** (PreToolUse) | Categorical hard-deny gate for Bash: destructive file ops (`rm`/`rmdir`/`unlink`/`shred`, incl. inline python/node and script-file/heredoc forms), delete-by-move to a tmp-like location, `git branch`/`git push` branch deletion, `gh api`/`gh release` delete, destructive curl/wget methods, `sudo`, and direct telemetry-log reads. Runs before every other Bash hook; a match here can never be overridden in-session (the message tells you to run the command yourself in a separate terminal). |
|
|
108
|
-
| **`bash-security-review`** (PreToolUse) | Tiered Bash command security review with
|
|
108
|
+
| **`bash-security-review`** (PreToolUse) | Tiered Bash command security review: trivial/read-only pre-filters, heuristic escalation, a reviewed-script allowlist (script path + content hash, managed with `ccst hooks allowlist list\|add\|remove\|verify`), a command cache, and a slimmed `claude -p` LLM fallback (~770 tokens per review instead of ~50k+). |
|
|
109
109
|
| **`marker-allow`** (PreToolUse) | Auto-approves a bare `touch` of a skill marker under `~/.cache/claude/markers/` (and nothing else), so marker-gated skills can refresh their TTL marker without a permission prompt. |
|
|
110
110
|
| **`confirm-8digit`** (PreToolUse) | Blocks a configurable set of high-stakes tool calls unless the user repeats back an 8-digit confirmation code. |
|
|
111
111
|
| **`after-response`** (Stop) | Touches a `.last-active` sentinel so `ccs --order-by active` can sort sessions by recency of Claude activity. |
|
|
@@ -151,7 +151,7 @@ If you've ever tried to remember which `1f4a8b3c-...` UUID is the session where
|
|
|
151
151
|
|
|
152
152
|
- **Python 3.11+** (3.12+ recommended)
|
|
153
153
|
- **The `claude` CLI on your `$PATH`.** Install it first via [the official Claude Code instructions](https://docs.anthropic.com/en/docs/claude-code/setup) and verify with `claude --version`.
|
|
154
|
-
- **`ccusage` (optional)** - if on `$PATH
|
|
154
|
+
- **`ccusage` (optional)** - if on `$PATH` (or in bun's global bin dir, `~/.bun/bin`), `claude-code-usage reconcile` cross-checks dollar totals against it. Skipped gracefully if missing. Needs `bun` to install; `ccst doctor` reports both under `deps:bun` / `deps:ccusage` and prints the install command. See the `analyse-cc-usage` skill's setup section.
|
|
155
155
|
- **`ripgrep` (optional)** - `ccs --contents` prefers `rg`; falls back to threaded Python `grep` if missing.
|
|
156
156
|
|
|
157
157
|
### Install and set up
|
|
@@ -48,7 +48,7 @@ bundled scheduled jobs:
|
|
|
48
48
|
| **`session-tag`** (SessionStart) | Writes a `<uuid>.tag` file so `claude-code-usage` can map session UUIDs to human-readable names. |
|
|
49
49
|
| **`last-screenshot`** (UserPromptSubmit) | Resolves your newest screenshot for the `>lss` token and injects its path. Requires `CCST_SCREENSHOT_DIR`. |
|
|
50
50
|
| **`bash-hard-deny`** (PreToolUse) | Categorical hard-deny gate for Bash: destructive file ops (`rm`/`rmdir`/`unlink`/`shred`, incl. inline python/node and script-file/heredoc forms), delete-by-move to a tmp-like location, `git branch`/`git push` branch deletion, `gh api`/`gh release` delete, destructive curl/wget methods, `sudo`, and direct telemetry-log reads. Runs before every other Bash hook; a match here can never be overridden in-session (the message tells you to run the command yourself in a separate terminal). |
|
|
51
|
-
| **`bash-security-review`** (PreToolUse) | Tiered Bash command security review with
|
|
51
|
+
| **`bash-security-review`** (PreToolUse) | Tiered Bash command security review: trivial/read-only pre-filters, heuristic escalation, a reviewed-script allowlist (script path + content hash, managed with `ccst hooks allowlist list\|add\|remove\|verify`), a command cache, and a slimmed `claude -p` LLM fallback (~770 tokens per review instead of ~50k+). |
|
|
52
52
|
| **`marker-allow`** (PreToolUse) | Auto-approves a bare `touch` of a skill marker under `~/.cache/claude/markers/` (and nothing else), so marker-gated skills can refresh their TTL marker without a permission prompt. |
|
|
53
53
|
| **`confirm-8digit`** (PreToolUse) | Blocks a configurable set of high-stakes tool calls unless the user repeats back an 8-digit confirmation code. |
|
|
54
54
|
| **`after-response`** (Stop) | Touches a `.last-active` sentinel so `ccs --order-by active` can sort sessions by recency of Claude activity. |
|
|
@@ -94,7 +94,7 @@ If you've ever tried to remember which `1f4a8b3c-...` UUID is the session where
|
|
|
94
94
|
|
|
95
95
|
- **Python 3.11+** (3.12+ recommended)
|
|
96
96
|
- **The `claude` CLI on your `$PATH`.** Install it first via [the official Claude Code instructions](https://docs.anthropic.com/en/docs/claude-code/setup) and verify with `claude --version`.
|
|
97
|
-
- **`ccusage` (optional)** - if on `$PATH
|
|
97
|
+
- **`ccusage` (optional)** - if on `$PATH` (or in bun's global bin dir, `~/.bun/bin`), `claude-code-usage reconcile` cross-checks dollar totals against it. Skipped gracefully if missing. Needs `bun` to install; `ccst doctor` reports both under `deps:bun` / `deps:ccusage` and prints the install command. See the `analyse-cc-usage` skill's setup section.
|
|
98
98
|
- **`ripgrep` (optional)** - `ccs --contents` prefers `rg`; falls back to threaded Python `grep` if missing.
|
|
99
99
|
|
|
100
100
|
### Install and set up
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cc-session-tools"
|
|
7
|
-
version = "3.
|
|
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" }
|
|
@@ -6,6 +6,8 @@ Current subcommands:
|
|
|
6
6
|
hooks install [--hook <name>] Merge hook entries from the bundled set (or a
|
|
7
7
|
custom --source) into a target settings.json.
|
|
8
8
|
hooks uninstall [--hook <name>] Remove hook entries from a target settings.json.
|
|
9
|
+
hooks allowlist list|add|remove|verify
|
|
10
|
+
Manage the bash-security-review reviewed-script allowlist.
|
|
9
11
|
hooks run <name> Run a Claude Code hook by name.
|
|
10
12
|
Available hooks: bash-hard-deny,
|
|
11
13
|
bash-security-review, marker-allow,
|
|
@@ -110,6 +112,7 @@ from cc_session_tools.hooks_install import (
|
|
|
110
112
|
)
|
|
111
113
|
from cc_session_tools.lib import machine_identity
|
|
112
114
|
from cc_session_tools.lib.hook_registry import HOOK_DESCRIPTIONS, HOOK_VERBS
|
|
115
|
+
from cc_session_tools.lib.text_wrap import wrap_block
|
|
113
116
|
|
|
114
117
|
if TYPE_CHECKING:
|
|
115
118
|
# Type-only, so the handler's own lazy `from ... import sync_check` (this file's convention:
|
|
@@ -536,30 +539,45 @@ def _bundle_inventory(bundle: dict[str, Any]) -> list[tuple[str, str, str | None
|
|
|
536
539
|
return out
|
|
537
540
|
|
|
538
541
|
|
|
539
|
-
def
|
|
542
|
+
def _format_hooks_install_records(
|
|
540
543
|
inventory: list[tuple[str, str, str | None, str]],
|
|
541
544
|
added_keys: set[tuple[str, str | None, str]],
|
|
542
|
-
) ->
|
|
543
|
-
"""
|
|
544
|
-
|
|
545
|
-
if not inventory:
|
|
546
|
-
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.
|
|
547
548
|
|
|
548
|
-
|
|
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]]] = {}
|
|
549
554
|
for name, event, matcher, cmd in inventory:
|
|
550
|
-
status = "install" if (event, matcher, cmd) in added_keys else "already-installed"
|
|
551
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}")]
|
|
552
567
|
description = HOOK_DESCRIPTIONS.get(name, "")
|
|
553
|
-
|
|
568
|
+
if description:
|
|
569
|
+
lines.append(wrap_block(description))
|
|
570
|
+
records.append("\n".join(lines))
|
|
571
|
+
return "\n\n".join(records)
|
|
554
572
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
]
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
print(
|
|
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))
|
|
563
581
|
|
|
564
582
|
|
|
565
583
|
def _list_bundle_hook_names(bundle: dict[str, Any]) -> list[str]:
|
|
@@ -1949,6 +1967,65 @@ def _cmd_machine_identity_confirm(args: argparse.Namespace) -> int:
|
|
|
1949
1967
|
# ---------- hooks run ----------
|
|
1950
1968
|
|
|
1951
1969
|
|
|
1970
|
+
def _cmd_hooks_allowlist(args: argparse.Namespace) -> int:
|
|
1971
|
+
"""`ccst hooks allowlist list|add|remove|verify` - the reviewed-script allowlist store."""
|
|
1972
|
+
import hashlib
|
|
1973
|
+
|
|
1974
|
+
from hooks import cache as cache_mod
|
|
1975
|
+
from hooks import script_allowlist
|
|
1976
|
+
|
|
1977
|
+
verb = args.allowlist_verb
|
|
1978
|
+
if verb == "list":
|
|
1979
|
+
entries = cache_mod.allowlist_list()
|
|
1980
|
+
if not entries:
|
|
1981
|
+
print("No allowlisted scripts.")
|
|
1982
|
+
return 0
|
|
1983
|
+
for e in entries:
|
|
1984
|
+
print(
|
|
1985
|
+
f"{e.script_path} source={e.source} sha256={e.sha256[:8]} added={e.added_at} "
|
|
1986
|
+
f"last_used={e.last_used or '-'} uses={e.use_count}"
|
|
1987
|
+
)
|
|
1988
|
+
return 0
|
|
1989
|
+
if verb == "add":
|
|
1990
|
+
path = Path(args.script).expanduser().resolve()
|
|
1991
|
+
if not path.is_file():
|
|
1992
|
+
print(f"error: {args.script}: not a file", file=sys.stderr)
|
|
1993
|
+
return 2
|
|
1994
|
+
root = script_allowlist.find_project_root(path.parent)
|
|
1995
|
+
digest = hashlib.sha256(path.read_bytes()).hexdigest()
|
|
1996
|
+
cache_mod.allowlist_upsert(
|
|
1997
|
+
str(path), digest, str(root), source="manual", overwrite_source=True
|
|
1998
|
+
)
|
|
1999
|
+
print(f"Allowlisted {path} (sha256 {digest[:8]}, project {root})")
|
|
2000
|
+
return 0
|
|
2001
|
+
if verb == "remove":
|
|
2002
|
+
path = Path(args.script).expanduser().resolve()
|
|
2003
|
+
if not cache_mod.allowlist_remove(str(path)):
|
|
2004
|
+
print(f"error: {path} is not in the allowlist", file=sys.stderr)
|
|
2005
|
+
return 1
|
|
2006
|
+
print(f"Removed {path}")
|
|
2007
|
+
return 0
|
|
2008
|
+
# verify
|
|
2009
|
+
drift = 0
|
|
2010
|
+
entries = cache_mod.allowlist_list()
|
|
2011
|
+
if not entries:
|
|
2012
|
+
print("No allowlisted scripts.")
|
|
2013
|
+
return 0
|
|
2014
|
+
for e in entries:
|
|
2015
|
+
p = Path(e.script_path)
|
|
2016
|
+
if not p.is_file():
|
|
2017
|
+
print(f"MISSING {e.script_path}")
|
|
2018
|
+
drift += 1
|
|
2019
|
+
continue
|
|
2020
|
+
current = hashlib.sha256(p.read_bytes()).hexdigest()
|
|
2021
|
+
if current != e.sha256:
|
|
2022
|
+
print(f"CHANGED {e.script_path} {e.sha256[:8]} -> {current[:8]}")
|
|
2023
|
+
drift += 1
|
|
2024
|
+
else:
|
|
2025
|
+
print(f"OK {e.script_path}")
|
|
2026
|
+
return 1 if drift else 0
|
|
2027
|
+
|
|
2028
|
+
|
|
1952
2029
|
def _cmd_hooks_run(args: argparse.Namespace) -> int:
|
|
1953
2030
|
"""Dispatch to the named hook, or exit 1 (never 2) if it is unknown.
|
|
1954
2031
|
|
|
@@ -2598,6 +2675,24 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
2598
2675
|
help="Write changes (default: dry run)",
|
|
2599
2676
|
)
|
|
2600
2677
|
|
|
2678
|
+
# hooks allowlist
|
|
2679
|
+
allowlist_parser = hooks_sub.add_parser(
|
|
2680
|
+
"allowlist",
|
|
2681
|
+
help="Manage the bash-security-review reviewed-script allowlist",
|
|
2682
|
+
)
|
|
2683
|
+
allowlist_sub = allowlist_parser.add_subparsers(dest="allowlist_verb", metavar="<action>")
|
|
2684
|
+
allowlist_sub.required = True
|
|
2685
|
+
allowlist_sub.add_parser("list", help="List allowlisted scripts")
|
|
2686
|
+
allowlist_add = allowlist_sub.add_parser(
|
|
2687
|
+
"add", help="Allowlist a script at its current content hash (you vouch for it)"
|
|
2688
|
+
)
|
|
2689
|
+
allowlist_add.add_argument("script", metavar="<script>", help="Path to the script")
|
|
2690
|
+
allowlist_remove = allowlist_sub.add_parser("remove", help="Remove a script from the allowlist")
|
|
2691
|
+
allowlist_remove.add_argument("script", metavar="<script>", help="Path to the script")
|
|
2692
|
+
allowlist_sub.add_parser(
|
|
2693
|
+
"verify", help="Report allowlisted scripts that changed or no longer exist (exit 1 on drift)"
|
|
2694
|
+
)
|
|
2695
|
+
|
|
2601
2696
|
# hooks run
|
|
2602
2697
|
run_parser = hooks_sub.add_parser(
|
|
2603
2698
|
"run",
|
|
@@ -3500,6 +3595,8 @@ def main() -> None:
|
|
|
3500
3595
|
sys.exit(_cmd_hooks_install(args))
|
|
3501
3596
|
if args.verb == "uninstall":
|
|
3502
3597
|
sys.exit(_cmd_hooks_uninstall(args))
|
|
3598
|
+
if args.verb == "allowlist":
|
|
3599
|
+
sys.exit(_cmd_hooks_allowlist(args))
|
|
3503
3600
|
if args.verb == "run":
|
|
3504
3601
|
sys.exit(_cmd_hooks_run(args))
|
|
3505
3602
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Locate tools installed through bun (bun itself, ccusage).
|
|
2
|
+
|
|
3
|
+
`bun add -g` installs into ``$BUN_INSTALL/bin`` (default ``~/.bun/bin``). That directory is
|
|
4
|
+
only on ``PATH`` if the user edited a shell rc file that the current shell type reads, which
|
|
5
|
+
non-interactive shells (hooks, scheduled jobs, subagent Bash calls) often do not. Resolving the
|
|
6
|
+
directory directly keeps a correct install findable from every shell type.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import dataclasses
|
|
11
|
+
import os
|
|
12
|
+
import shutil
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
# Upstream's installer works on macOS and on Linux/WSL (which also needs `unzip` installed).
|
|
16
|
+
BUN_INSTALL_COMMAND = "curl -fsSL https://bun.sh/install | bash"
|
|
17
|
+
CCUSAGE_INSTALL_COMMAND = "bun add -g ccusage"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclasses.dataclass(frozen=True, slots=True)
|
|
21
|
+
class Resolved:
|
|
22
|
+
path: str
|
|
23
|
+
on_path: bool
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def bun_bin_dir() -> Path:
|
|
27
|
+
install = os.environ.get("BUN_INSTALL")
|
|
28
|
+
return (Path(install) if install else Path.home() / ".bun") / "bin"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def resolve(name: str, *, bin_dir: Path | None = None) -> Resolved | None:
|
|
32
|
+
"""Find *name* on ``PATH`` first, then in bun's global bin directory."""
|
|
33
|
+
found = shutil.which(name)
|
|
34
|
+
if found is not None:
|
|
35
|
+
return Resolved(path=found, on_path=True)
|
|
36
|
+
candidate = (bin_dir if bin_dir is not None else bun_bin_dir()) / name
|
|
37
|
+
if candidate.is_file() and os.access(candidate, os.X_OK):
|
|
38
|
+
return Resolved(path=str(candidate), on_path=False)
|
|
39
|
+
return None
|
|
@@ -14,13 +14,13 @@ 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
|
|
21
20
|
from pathlib import Path
|
|
22
21
|
from typing import TYPE_CHECKING, Any
|
|
23
22
|
|
|
23
|
+
from cc_session_tools.lib import bun_tools
|
|
24
24
|
from cc_session_tools.lib.db import connect as _db_connect
|
|
25
25
|
from cc_session_tools.lib.db import migration_applied as _migration_applied
|
|
26
26
|
from cc_session_tools.lib.hook_registry import HOOK_VERBS, hook_name_from_command
|
|
@@ -28,6 +28,7 @@ from cc_session_tools.lib.pdata.init_paths import (
|
|
|
28
28
|
MIGRATED_ARCHIVE_DIRNAME,
|
|
29
29
|
MIGRATED_MANIFEST_FILENAME,
|
|
30
30
|
)
|
|
31
|
+
from cc_session_tools.lib.text_wrap import wrap_block
|
|
31
32
|
|
|
32
33
|
if TYPE_CHECKING:
|
|
33
34
|
from cc_session_tools.lib.install_sync import FailedAttempt
|
|
@@ -121,6 +122,69 @@ def check_env_dir(
|
|
|
121
122
|
return CheckResult(name=name, status=Status.OK, reason=str(p))
|
|
122
123
|
|
|
123
124
|
|
|
125
|
+
def _check_bun_tool(
|
|
126
|
+
name: str, *, install_hint: str, bin_dir: Path | None, timeout: float
|
|
127
|
+
) -> CheckResult:
|
|
128
|
+
"""WARN-only check for an optional bun-installed tool (see `bun_tools.resolve`)."""
|
|
129
|
+
check_name = f"deps:{name}"
|
|
130
|
+
resolved = bun_tools.resolve(name, bin_dir=bin_dir)
|
|
131
|
+
if resolved is None:
|
|
132
|
+
return CheckResult(name=check_name, status=Status.WARN, reason=f"{name!r} not found; {install_hint}")
|
|
133
|
+
if not resolved.on_path:
|
|
134
|
+
found_dir = Path(resolved.path).parent
|
|
135
|
+
return CheckResult(
|
|
136
|
+
name=check_name,
|
|
137
|
+
status=Status.WARN,
|
|
138
|
+
reason=(
|
|
139
|
+
f"installed at {resolved.path} but {found_dir} is not on PATH; add it to PATH "
|
|
140
|
+
f"(for non-interactive shells too: ~/.zshenv for zsh, ~/.profile for bash)"
|
|
141
|
+
),
|
|
142
|
+
)
|
|
143
|
+
try:
|
|
144
|
+
result = subprocess.run(
|
|
145
|
+
[resolved.path, "--version"], capture_output=True, text=True, timeout=timeout
|
|
146
|
+
)
|
|
147
|
+
except (OSError, subprocess.TimeoutExpired) as e:
|
|
148
|
+
return CheckResult(
|
|
149
|
+
name=check_name, status=Status.WARN, reason=f"could not run {name} --version: {e}"
|
|
150
|
+
)
|
|
151
|
+
if result.returncode != 0:
|
|
152
|
+
return CheckResult(
|
|
153
|
+
name=check_name, status=Status.WARN, reason=f"{name} --version exited {result.returncode}"
|
|
154
|
+
)
|
|
155
|
+
return CheckResult(
|
|
156
|
+
name=check_name,
|
|
157
|
+
status=Status.OK,
|
|
158
|
+
reason=(result.stdout + result.stderr).strip().split("\n")[0],
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def check_bun(bin_dir: Path | None = None, timeout: float = 5.0) -> CheckResult:
|
|
163
|
+
"""WARN (never FAIL) if bun, needed to install ccusage, is missing or not on PATH."""
|
|
164
|
+
return _check_bun_tool(
|
|
165
|
+
"bun",
|
|
166
|
+
install_hint=(
|
|
167
|
+
f"install with `{bun_tools.BUN_INSTALL_COMMAND}` (macOS and Linux/WSL; "
|
|
168
|
+
"Linux/WSL also needs `unzip`)"
|
|
169
|
+
),
|
|
170
|
+
bin_dir=bin_dir,
|
|
171
|
+
timeout=timeout,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def check_ccusage(bin_dir: Path | None = None, timeout: float = 5.0) -> CheckResult:
|
|
176
|
+
"""WARN (never FAIL) if ccusage, used by `claude-code-usage reconcile`, is missing.
|
|
177
|
+
|
|
178
|
+
ccusage is optional: claude-code-usage works without it apart from `reconcile`.
|
|
179
|
+
"""
|
|
180
|
+
return _check_bun_tool(
|
|
181
|
+
"ccusage",
|
|
182
|
+
install_hint=f"install bun first (see deps:bun), then `{bun_tools.CCUSAGE_INSTALL_COMMAND}`",
|
|
183
|
+
bin_dir=bin_dir,
|
|
184
|
+
timeout=timeout,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
124
188
|
def check_settings_json(settings_path: Path) -> CheckResult:
|
|
125
189
|
"""Verify settings.json exists and is valid JSON."""
|
|
126
190
|
name = "settings.json"
|
|
@@ -1111,6 +1175,10 @@ def run_all_checks(
|
|
|
1111
1175
|
results.extend(check_sessions_uuid_migration(sessions_db_path))
|
|
1112
1176
|
results.extend(check_sessions_project_dir_absolute(sessions_db_path))
|
|
1113
1177
|
|
|
1178
|
+
# Optional dependencies for claude-code-usage's reconcile / ccusage blocks
|
|
1179
|
+
results.append(check_bun())
|
|
1180
|
+
results.append(check_ccusage())
|
|
1181
|
+
|
|
1114
1182
|
# PyPI version check
|
|
1115
1183
|
if not skip_pypi:
|
|
1116
1184
|
results.append(check_pypi_version(installed_version))
|
|
@@ -1156,12 +1224,11 @@ def _wrap_reason(reason: str, *, indent: str = " ") -> str:
|
|
|
1156
1224
|
embedded paths and remediation commands); printed on a single line they
|
|
1157
1225
|
hard-wrap at the terminal edge with no continuation indent, making a
|
|
1158
1226
|
wrapped reason indistinguishable from a new, unrelated result. Wrapping
|
|
1159
|
-
to the real terminal width
|
|
1227
|
+
to the real terminal width - falling back to 80 columns for non-tty
|
|
1160
1228
|
output (piped, redirected, or the scheduled drift-monitor job whose
|
|
1161
|
-
stdout is forwarded to Telegram)
|
|
1229
|
+
stdout is forwarded to Telegram) - and indenting every line fixes that.
|
|
1162
1230
|
"""
|
|
1163
|
-
|
|
1164
|
-
return textwrap.fill(reason, width=width, initial_indent=indent, subsequent_indent=indent)
|
|
1231
|
+
return wrap_block(reason, indent=indent)
|
|
1165
1232
|
|
|
1166
1233
|
|
|
1167
1234
|
def format_results(results: list[CheckResult], *, show_all: bool = False) -> str:
|
|
@@ -1197,7 +1264,13 @@ def format_results(results: list[CheckResult], *, show_all: bool = False) -> str
|
|
|
1197
1264
|
lines.append(f"All {len(results)} checks OK.")
|
|
1198
1265
|
has_issues = any(r.status in (Status.WARN, Status.FAIL) for r in results)
|
|
1199
1266
|
if has_issues:
|
|
1200
|
-
lines.append("
|
|
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
|
+
)
|
|
1201
1274
|
if not show_all:
|
|
1202
1275
|
lines.append("\nTo see full doctor output use --all argument")
|
|
1203
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
|
+
)
|
|
@@ -206,8 +206,23 @@ claude-code-usage --version # should print the version
|
|
|
206
206
|
uv sync --extra dev
|
|
207
207
|
uv run pytest # all 350+ tests should pass
|
|
208
208
|
|
|
209
|
-
#
|
|
210
|
-
#
|
|
209
|
+
# 4a. Install bun (needed to install ccusage). Same command on macOS and Linux/WSL.
|
|
210
|
+
# Linux/WSL: install `unzip` first (e.g. `sudo apt install unzip`), and install bun
|
|
211
|
+
# inside the Linux environment - a Windows-side bun is not visible to WSL tools.
|
|
212
|
+
# macOS alternative: `brew install oven-sh/bun/bun`.
|
|
213
|
+
curl -fsSL https://bun.sh/install | bash
|
|
214
|
+
bun --version
|
|
215
|
+
|
|
216
|
+
# 4b. Make bun's global bin dir (default ~/.bun/bin) resolvable on PATH, including in
|
|
217
|
+
# non-interactive shells (hooks, scheduled jobs). The installer edits the rc file of the
|
|
218
|
+
# shell you ran it from, which non-interactive shells often skip; also add it to
|
|
219
|
+
# ~/.zshenv (zsh) or ~/.profile (bash):
|
|
220
|
+
# export PATH="$HOME/.bun/bin:$PATH"
|
|
221
|
+
# claude-code-usage also falls back to ~/.bun/bin ($BUN_INSTALL/bin) on its own, so
|
|
222
|
+
# reconcile works without this; the PATH edit is what makes the `ccusage` command itself
|
|
223
|
+
# (e.g. `ccusage blocks`) available.
|
|
224
|
+
|
|
225
|
+
# 4c. Install ccusage globally so reconciliation works
|
|
211
226
|
bun add -g ccusage
|
|
212
227
|
ccusage --version
|
|
213
228
|
|
|
@@ -223,6 +238,11 @@ claude-code-usage warm-cache
|
|
|
223
238
|
claude-code-usage reconcile
|
|
224
239
|
```
|
|
225
240
|
|
|
241
|
+
**Existing machine that already has CCST:** `ccst doctor` reports `deps:bun` and `deps:ccusage`
|
|
242
|
+
(WARN when missing or not on `PATH`, with the exact fix). `ccst install-everything` runs doctor at
|
|
243
|
+
its end, so upgrading surfaces a missing install; CCST never installs bun or ccusage itself. Mute
|
|
244
|
+
with `ccst doctor --mute deps:ccusage` if you deliberately skip it.
|
|
245
|
+
|
|
226
246
|
The repo can also be sourced from GitHub directly (via the upstream
|
|
227
247
|
remote) if you don't want to clone it - see the README's "Install"
|
|
228
248
|
section for that path.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cc-session-tools
|
|
3
|
-
Version: 3.
|
|
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
|
|
@@ -105,7 +105,7 @@ bundled scheduled jobs:
|
|
|
105
105
|
| **`session-tag`** (SessionStart) | Writes a `<uuid>.tag` file so `claude-code-usage` can map session UUIDs to human-readable names. |
|
|
106
106
|
| **`last-screenshot`** (UserPromptSubmit) | Resolves your newest screenshot for the `>lss` token and injects its path. Requires `CCST_SCREENSHOT_DIR`. |
|
|
107
107
|
| **`bash-hard-deny`** (PreToolUse) | Categorical hard-deny gate for Bash: destructive file ops (`rm`/`rmdir`/`unlink`/`shred`, incl. inline python/node and script-file/heredoc forms), delete-by-move to a tmp-like location, `git branch`/`git push` branch deletion, `gh api`/`gh release` delete, destructive curl/wget methods, `sudo`, and direct telemetry-log reads. Runs before every other Bash hook; a match here can never be overridden in-session (the message tells you to run the command yourself in a separate terminal). |
|
|
108
|
-
| **`bash-security-review`** (PreToolUse) | Tiered Bash command security review with
|
|
108
|
+
| **`bash-security-review`** (PreToolUse) | Tiered Bash command security review: trivial/read-only pre-filters, heuristic escalation, a reviewed-script allowlist (script path + content hash, managed with `ccst hooks allowlist list\|add\|remove\|verify`), a command cache, and a slimmed `claude -p` LLM fallback (~770 tokens per review instead of ~50k+). |
|
|
109
109
|
| **`marker-allow`** (PreToolUse) | Auto-approves a bare `touch` of a skill marker under `~/.cache/claude/markers/` (and nothing else), so marker-gated skills can refresh their TTL marker without a permission prompt. |
|
|
110
110
|
| **`confirm-8digit`** (PreToolUse) | Blocks a configurable set of high-stakes tool calls unless the user repeats back an 8-digit confirmation code. |
|
|
111
111
|
| **`after-response`** (Stop) | Touches a `.last-active` sentinel so `ccs --order-by active` can sort sessions by recency of Claude activity. |
|
|
@@ -151,7 +151,7 @@ If you've ever tried to remember which `1f4a8b3c-...` UUID is the session where
|
|
|
151
151
|
|
|
152
152
|
- **Python 3.11+** (3.12+ recommended)
|
|
153
153
|
- **The `claude` CLI on your `$PATH`.** Install it first via [the official Claude Code instructions](https://docs.anthropic.com/en/docs/claude-code/setup) and verify with `claude --version`.
|
|
154
|
-
- **`ccusage` (optional)** - if on `$PATH
|
|
154
|
+
- **`ccusage` (optional)** - if on `$PATH` (or in bun's global bin dir, `~/.bun/bin`), `claude-code-usage reconcile` cross-checks dollar totals against it. Skipped gracefully if missing. Needs `bun` to install; `ccst doctor` reports both under `deps:bun` / `deps:ccusage` and prints the install command. See the `analyse-cc-usage` skill's setup section.
|
|
155
155
|
- **`ripgrep` (optional)** - `ccs --contents` prefers `rg`; falls back to threaded Python `grep` if missing.
|
|
156
156
|
|
|
157
157
|
### Install and set up
|
|
@@ -23,6 +23,7 @@ src/cc_session_tools/cli/migrate_telemetry.py
|
|
|
23
23
|
src/cc_session_tools/config/hooks-bundle.json
|
|
24
24
|
src/cc_session_tools/config/session-tag-hook.json
|
|
25
25
|
src/cc_session_tools/lib/__init__.py
|
|
26
|
+
src/cc_session_tools/lib/bun_tools.py
|
|
26
27
|
src/cc_session_tools/lib/claude_flags.py
|
|
27
28
|
src/cc_session_tools/lib/claude_md_install.py
|
|
28
29
|
src/cc_session_tools/lib/context_overrides.py
|
|
@@ -48,6 +49,7 @@ src/cc_session_tools/lib/sessions_repair.py
|
|
|
48
49
|
src/cc_session_tools/lib/shell_install.py
|
|
49
50
|
src/cc_session_tools/lib/tasklist.py
|
|
50
51
|
src/cc_session_tools/lib/telemetry_store.py
|
|
52
|
+
src/cc_session_tools/lib/text_wrap.py
|
|
51
53
|
src/cc_session_tools/lib/messaging/__init__.py
|
|
52
54
|
src/cc_session_tools/lib/messaging/addressing.py
|
|
53
55
|
src/cc_session_tools/lib/messaging/cursor.py
|
|
@@ -176,6 +178,7 @@ src/hooks/normalise.py
|
|
|
176
178
|
src/hooks/pdata_sync.py
|
|
177
179
|
src/hooks/pending_migration.py
|
|
178
180
|
src/hooks/pending_rename.py
|
|
181
|
+
src/hooks/script_allowlist.py
|
|
179
182
|
src/hooks/session_tag.py
|
|
180
183
|
src/hooks/stats.py
|
|
181
184
|
src/hooks/telemetry.py
|
|
@@ -187,6 +190,8 @@ tests/test_after_response.py
|
|
|
187
190
|
tests/test_attribution.py
|
|
188
191
|
tests/test_bash_hard_deny.py
|
|
189
192
|
tests/test_bash_security_review.py
|
|
193
|
+
tests/test_bash_security_review_allowlist.py
|
|
194
|
+
tests/test_bun_tools.py
|
|
190
195
|
tests/test_cache.py
|
|
191
196
|
tests/test_cache_sqlite.py
|
|
192
197
|
tests/test_ccr_orphans.py
|
|
@@ -204,6 +209,7 @@ tests/test_ccst_doctor.py
|
|
|
204
209
|
tests/test_ccst_gc_prune.py
|
|
205
210
|
tests/test_ccst_gc_report.py
|
|
206
211
|
tests/test_ccst_hook_dispatcher.py
|
|
212
|
+
tests/test_ccst_hooks_allowlist.py
|
|
207
213
|
tests/test_ccst_hooks_install_prune.py
|
|
208
214
|
tests/test_ccst_hooks_install_zero_arg.py
|
|
209
215
|
tests/test_ccst_hooks_uninstall.py
|
|
@@ -242,6 +248,7 @@ tests/test_doctor_mutes.py
|
|
|
242
248
|
tests/test_empty_session.py
|
|
243
249
|
tests/test_hook_cache.py
|
|
244
250
|
tests/test_hooks_install.py
|
|
251
|
+
tests/test_hooks_install_records.py
|
|
245
252
|
tests/test_install_sync.py
|
|
246
253
|
tests/test_last_screenshot.py
|
|
247
254
|
tests/test_levenshtein.py
|
|
@@ -268,6 +275,7 @@ tests/test_roots.py
|
|
|
268
275
|
tests/test_rules.py
|
|
269
276
|
tests/test_scheduler_bundled_jobs.py
|
|
270
277
|
tests/test_schema.py
|
|
278
|
+
tests/test_script_allowlist.py
|
|
271
279
|
tests/test_session_names.py
|
|
272
280
|
tests/test_session_tag.py
|
|
273
281
|
tests/test_sessions.py
|
|
@@ -276,5 +284,6 @@ tests/test_sessions_repair.py
|
|
|
276
284
|
tests/test_tasklist.py
|
|
277
285
|
tests/test_telemetry.py
|
|
278
286
|
tests/test_telemetry_store.py
|
|
287
|
+
tests/test_text_wrap.py
|
|
279
288
|
tests/test_transcript.py
|
|
280
289
|
tests/test_worklog_guard.py
|
|
@@ -10,11 +10,12 @@ that's our domain.
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
12
|
import json
|
|
13
|
-
import shutil
|
|
14
13
|
import subprocess
|
|
15
14
|
from dataclasses import dataclass, field
|
|
16
15
|
from typing import Any
|
|
17
16
|
|
|
17
|
+
from cc_session_tools.lib import bun_tools
|
|
18
|
+
|
|
18
19
|
|
|
19
20
|
@dataclass
|
|
20
21
|
class DailyEntry:
|
|
@@ -52,11 +53,11 @@ class ReconcileDiff:
|
|
|
52
53
|
|
|
53
54
|
|
|
54
55
|
class CcusageNotInstalled(RuntimeError):
|
|
55
|
-
"""Raised when ccusage is
|
|
56
|
+
"""Raised when ccusage is neither on PATH nor in bun's global bin directory."""
|
|
56
57
|
|
|
57
58
|
|
|
58
59
|
def is_available() -> bool:
|
|
59
|
-
return
|
|
60
|
+
return bun_tools.resolve("ccusage") is not None
|
|
60
61
|
|
|
61
62
|
|
|
62
63
|
def run_daily(since: str | None = None, until: str | None = None,
|
|
@@ -65,9 +66,14 @@ def run_daily(since: str | None = None, until: str | None = None,
|
|
|
65
66
|
|
|
66
67
|
Dates are formatted as YYYYMMDD per ccusage's CLI.
|
|
67
68
|
"""
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
resolved = bun_tools.resolve("ccusage")
|
|
70
|
+
if resolved is None:
|
|
71
|
+
raise CcusageNotInstalled(
|
|
72
|
+
"ccusage not found on PATH or in bun's global bin directory. Follow the "
|
|
73
|
+
"analyse-cc-usage skill's setup step 4 (install bun, then "
|
|
74
|
+
f"`{bun_tools.CCUSAGE_INSTALL_COMMAND}`); `ccst doctor` shows the current state."
|
|
75
|
+
)
|
|
76
|
+
cmd = [resolved.path, "daily", "--json"]
|
|
71
77
|
if offline:
|
|
72
78
|
cmd.append("--offline")
|
|
73
79
|
if since:
|