erk 0.4.5__py3-none-any.whl
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.
- erk/__init__.py +12 -0
- erk/__main__.py +6 -0
- erk/agent_docs/__init__.py +5 -0
- erk/agent_docs/models.py +123 -0
- erk/agent_docs/operations.py +666 -0
- erk/artifacts/__init__.py +5 -0
- erk/artifacts/artifact_health.py +623 -0
- erk/artifacts/detection.py +16 -0
- erk/artifacts/discovery.py +343 -0
- erk/artifacts/models.py +63 -0
- erk/artifacts/staleness.py +56 -0
- erk/artifacts/state.py +100 -0
- erk/artifacts/sync.py +624 -0
- erk/cli/__init__.py +0 -0
- erk/cli/activation.py +132 -0
- erk/cli/alias.py +53 -0
- erk/cli/cli.py +221 -0
- erk/cli/commands/__init__.py +0 -0
- erk/cli/commands/admin.py +153 -0
- erk/cli/commands/artifact/__init__.py +1 -0
- erk/cli/commands/artifact/check.py +260 -0
- erk/cli/commands/artifact/group.py +31 -0
- erk/cli/commands/artifact/list_cmd.py +89 -0
- erk/cli/commands/artifact/show.py +62 -0
- erk/cli/commands/artifact/sync_cmd.py +39 -0
- erk/cli/commands/branch/__init__.py +26 -0
- erk/cli/commands/branch/assign_cmd.py +152 -0
- erk/cli/commands/branch/checkout_cmd.py +357 -0
- erk/cli/commands/branch/create_cmd.py +161 -0
- erk/cli/commands/branch/list_cmd.py +82 -0
- erk/cli/commands/branch/unassign_cmd.py +197 -0
- erk/cli/commands/cc/__init__.py +15 -0
- erk/cli/commands/cc/jsonl_cmd.py +20 -0
- erk/cli/commands/cc/session/AGENTS.md +30 -0
- erk/cli/commands/cc/session/CLAUDE.md +1 -0
- erk/cli/commands/cc/session/__init__.py +15 -0
- erk/cli/commands/cc/session/list_cmd.py +167 -0
- erk/cli/commands/cc/session/show_cmd.py +175 -0
- erk/cli/commands/completion.py +89 -0
- erk/cli/commands/completions.py +165 -0
- erk/cli/commands/config.py +327 -0
- erk/cli/commands/docs/__init__.py +1 -0
- erk/cli/commands/docs/group.py +16 -0
- erk/cli/commands/docs/sync.py +121 -0
- erk/cli/commands/docs/validate.py +102 -0
- erk/cli/commands/doctor.py +243 -0
- erk/cli/commands/down.py +171 -0
- erk/cli/commands/exec/__init__.py +1 -0
- erk/cli/commands/exec/group.py +164 -0
- erk/cli/commands/exec/scripts/AGENTS.md +79 -0
- erk/cli/commands/exec/scripts/CLAUDE.md +1 -0
- erk/cli/commands/exec/scripts/__init__.py +5 -0
- erk/cli/commands/exec/scripts/add_reaction_to_comment.py +69 -0
- erk/cli/commands/exec/scripts/add_remote_execution_note.py +68 -0
- erk/cli/commands/exec/scripts/check_impl.py +152 -0
- erk/cli/commands/exec/scripts/ci_update_pr_body.py +294 -0
- erk/cli/commands/exec/scripts/create_extraction_branch.py +138 -0
- erk/cli/commands/exec/scripts/create_extraction_plan.py +242 -0
- erk/cli/commands/exec/scripts/create_issue_from_session.py +103 -0
- erk/cli/commands/exec/scripts/create_plan_from_context.py +103 -0
- erk/cli/commands/exec/scripts/create_worker_impl_from_issue.py +93 -0
- erk/cli/commands/exec/scripts/detect_trunk_branch.py +121 -0
- erk/cli/commands/exec/scripts/exit_plan_mode_hook.py +777 -0
- erk/cli/commands/exec/scripts/extract_latest_plan.py +49 -0
- erk/cli/commands/exec/scripts/extract_session_from_issue.py +150 -0
- erk/cli/commands/exec/scripts/find_project_dir.py +214 -0
- erk/cli/commands/exec/scripts/generate_pr_summary.py +112 -0
- erk/cli/commands/exec/scripts/get_closing_text.py +98 -0
- erk/cli/commands/exec/scripts/get_embedded_prompt.py +62 -0
- erk/cli/commands/exec/scripts/get_plan_metadata.py +95 -0
- erk/cli/commands/exec/scripts/get_pr_body_footer.py +70 -0
- erk/cli/commands/exec/scripts/get_pr_discussion_comments.py +149 -0
- erk/cli/commands/exec/scripts/get_pr_review_comments.py +155 -0
- erk/cli/commands/exec/scripts/impl_init.py +158 -0
- erk/cli/commands/exec/scripts/impl_signal.py +375 -0
- erk/cli/commands/exec/scripts/impl_verify.py +49 -0
- erk/cli/commands/exec/scripts/issue_title_to_filename.py +34 -0
- erk/cli/commands/exec/scripts/list_sessions.py +296 -0
- erk/cli/commands/exec/scripts/mark_impl_ended.py +188 -0
- erk/cli/commands/exec/scripts/mark_impl_started.py +188 -0
- erk/cli/commands/exec/scripts/marker.py +163 -0
- erk/cli/commands/exec/scripts/objective_save_to_issue.py +109 -0
- erk/cli/commands/exec/scripts/plan_save_to_issue.py +269 -0
- erk/cli/commands/exec/scripts/plan_update_issue.py +147 -0
- erk/cli/commands/exec/scripts/post_extraction_comment.py +237 -0
- erk/cli/commands/exec/scripts/post_or_update_pr_summary.py +133 -0
- erk/cli/commands/exec/scripts/post_pr_inline_comment.py +143 -0
- erk/cli/commands/exec/scripts/post_workflow_started_comment.py +168 -0
- erk/cli/commands/exec/scripts/preprocess_session.py +777 -0
- erk/cli/commands/exec/scripts/quick_submit.py +32 -0
- erk/cli/commands/exec/scripts/rebase_with_conflict_resolution.py +260 -0
- erk/cli/commands/exec/scripts/reply_to_discussion_comment.py +173 -0
- erk/cli/commands/exec/scripts/resolve_review_thread.py +170 -0
- erk/cli/commands/exec/scripts/session_id_injector_hook.py +52 -0
- erk/cli/commands/exec/scripts/setup_impl_from_issue.py +159 -0
- erk/cli/commands/exec/scripts/slot_objective.py +102 -0
- erk/cli/commands/exec/scripts/tripwires_reminder_hook.py +20 -0
- erk/cli/commands/exec/scripts/update_dispatch_info.py +116 -0
- erk/cli/commands/exec/scripts/user_prompt_hook.py +113 -0
- erk/cli/commands/exec/scripts/validate_plan_content.py +98 -0
- erk/cli/commands/exec/scripts/wrap_plan_in_metadata_block.py +34 -0
- erk/cli/commands/implement.py +695 -0
- erk/cli/commands/implement_shared.py +649 -0
- erk/cli/commands/info/__init__.py +14 -0
- erk/cli/commands/info/release_notes_cmd.py +128 -0
- erk/cli/commands/init.py +801 -0
- erk/cli/commands/land_cmd.py +690 -0
- erk/cli/commands/log_cmd.py +137 -0
- erk/cli/commands/md/__init__.py +5 -0
- erk/cli/commands/md/check.py +118 -0
- erk/cli/commands/md/group.py +14 -0
- erk/cli/commands/navigation_helpers.py +430 -0
- erk/cli/commands/objective/__init__.py +16 -0
- erk/cli/commands/objective/list_cmd.py +47 -0
- erk/cli/commands/objective_helpers.py +132 -0
- erk/cli/commands/plan/__init__.py +32 -0
- erk/cli/commands/plan/check_cmd.py +174 -0
- erk/cli/commands/plan/close_cmd.py +69 -0
- erk/cli/commands/plan/create_cmd.py +120 -0
- erk/cli/commands/plan/docs/__init__.py +18 -0
- erk/cli/commands/plan/docs/extract_cmd.py +53 -0
- erk/cli/commands/plan/docs/unextract_cmd.py +38 -0
- erk/cli/commands/plan/docs/unextracted_cmd.py +72 -0
- erk/cli/commands/plan/extraction/__init__.py +16 -0
- erk/cli/commands/plan/extraction/complete_cmd.py +101 -0
- erk/cli/commands/plan/extraction/create_raw_cmd.py +63 -0
- erk/cli/commands/plan/get.py +71 -0
- erk/cli/commands/plan/list_cmd.py +754 -0
- erk/cli/commands/plan/log_cmd.py +440 -0
- erk/cli/commands/plan/start_cmd.py +459 -0
- erk/cli/commands/planner/__init__.py +40 -0
- erk/cli/commands/planner/configure_cmd.py +73 -0
- erk/cli/commands/planner/connect_cmd.py +96 -0
- erk/cli/commands/planner/create_cmd.py +148 -0
- erk/cli/commands/planner/list_cmd.py +51 -0
- erk/cli/commands/planner/register_cmd.py +105 -0
- erk/cli/commands/planner/set_default_cmd.py +23 -0
- erk/cli/commands/planner/unregister_cmd.py +43 -0
- erk/cli/commands/pr/__init__.py +23 -0
- erk/cli/commands/pr/check_cmd.py +112 -0
- erk/cli/commands/pr/checkout_cmd.py +165 -0
- erk/cli/commands/pr/fix_conflicts_cmd.py +82 -0
- erk/cli/commands/pr/parse_pr_reference.py +10 -0
- erk/cli/commands/pr/submit_cmd.py +360 -0
- erk/cli/commands/pr/sync_cmd.py +181 -0
- erk/cli/commands/prepare_cwd_recovery.py +60 -0
- erk/cli/commands/project/__init__.py +16 -0
- erk/cli/commands/project/init_cmd.py +91 -0
- erk/cli/commands/run/__init__.py +17 -0
- erk/cli/commands/run/list_cmd.py +189 -0
- erk/cli/commands/run/logs_cmd.py +54 -0
- erk/cli/commands/run/shared.py +19 -0
- erk/cli/commands/shell_integration.py +29 -0
- erk/cli/commands/slot/__init__.py +23 -0
- erk/cli/commands/slot/check_cmd.py +277 -0
- erk/cli/commands/slot/common.py +314 -0
- erk/cli/commands/slot/init_pool_cmd.py +157 -0
- erk/cli/commands/slot/list_cmd.py +228 -0
- erk/cli/commands/slot/repair_cmd.py +190 -0
- erk/cli/commands/stack/__init__.py +23 -0
- erk/cli/commands/stack/consolidate_cmd.py +470 -0
- erk/cli/commands/stack/list_cmd.py +79 -0
- erk/cli/commands/stack/move_cmd.py +309 -0
- erk/cli/commands/stack/split_old/README.md +64 -0
- erk/cli/commands/stack/split_old/__init__.py +5 -0
- erk/cli/commands/stack/split_old/command.py +233 -0
- erk/cli/commands/stack/split_old/display.py +116 -0
- erk/cli/commands/stack/split_old/plan.py +216 -0
- erk/cli/commands/status.py +58 -0
- erk/cli/commands/submit.py +768 -0
- erk/cli/commands/up.py +154 -0
- erk/cli/commands/upgrade.py +82 -0
- erk/cli/commands/wt/__init__.py +29 -0
- erk/cli/commands/wt/checkout_cmd.py +110 -0
- erk/cli/commands/wt/create_cmd.py +998 -0
- erk/cli/commands/wt/current_cmd.py +35 -0
- erk/cli/commands/wt/delete_cmd.py +573 -0
- erk/cli/commands/wt/list_cmd.py +332 -0
- erk/cli/commands/wt/rename_cmd.py +66 -0
- erk/cli/config.py +242 -0
- erk/cli/constants.py +29 -0
- erk/cli/core.py +65 -0
- erk/cli/debug.py +9 -0
- erk/cli/ensure-conversion-tasks.md +288 -0
- erk/cli/ensure.py +628 -0
- erk/cli/github_parsing.py +96 -0
- erk/cli/graphite.py +81 -0
- erk/cli/graphite_command.py +80 -0
- erk/cli/help_formatter.py +345 -0
- erk/cli/output.py +361 -0
- erk/cli/presets/dagster.toml +12 -0
- erk/cli/presets/generic.toml +12 -0
- erk/cli/prompt_hooks_templates/README.md +68 -0
- erk/cli/script_output.py +32 -0
- erk/cli/shell_integration/bash_wrapper.sh +32 -0
- erk/cli/shell_integration/fish_wrapper.fish +39 -0
- erk/cli/shell_integration/handler.py +338 -0
- erk/cli/shell_integration/zsh_wrapper.sh +32 -0
- erk/cli/shell_utils.py +171 -0
- erk/cli/subprocess_utils.py +92 -0
- erk/cli/uvx_detection.py +59 -0
- erk/core/__init__.py +0 -0
- erk/core/claude_executor.py +511 -0
- erk/core/claude_settings.py +317 -0
- erk/core/command_log.py +406 -0
- erk/core/commit_message_generator.py +234 -0
- erk/core/completion.py +10 -0
- erk/core/consolidation_utils.py +177 -0
- erk/core/context.py +570 -0
- erk/core/display/__init__.py +4 -0
- erk/core/display/abc.py +24 -0
- erk/core/display/real.py +30 -0
- erk/core/display_utils.py +526 -0
- erk/core/file_utils.py +87 -0
- erk/core/health_checks.py +1315 -0
- erk/core/health_checks_dogfooder/__init__.py +85 -0
- erk/core/health_checks_dogfooder/deprecated_dot_agent_config.py +64 -0
- erk/core/health_checks_dogfooder/legacy_claude_docs.py +69 -0
- erk/core/health_checks_dogfooder/legacy_config_locations.py +122 -0
- erk/core/health_checks_dogfooder/legacy_erk_docs_agent.py +61 -0
- erk/core/health_checks_dogfooder/legacy_erk_kits_folder.py +60 -0
- erk/core/health_checks_dogfooder/legacy_hook_settings.py +104 -0
- erk/core/health_checks_dogfooder/legacy_kit_yaml.py +78 -0
- erk/core/health_checks_dogfooder/legacy_kits_toml.py +43 -0
- erk/core/health_checks_dogfooder/outdated_erk_skill.py +43 -0
- erk/core/implementation_queue/__init__.py +1 -0
- erk/core/implementation_queue/github/__init__.py +8 -0
- erk/core/implementation_queue/github/abc.py +7 -0
- erk/core/implementation_queue/github/noop.py +38 -0
- erk/core/implementation_queue/github/printing.py +43 -0
- erk/core/implementation_queue/github/real.py +119 -0
- erk/core/init_utils.py +227 -0
- erk/core/output_filter.py +338 -0
- erk/core/plan_store/__init__.py +6 -0
- erk/core/planner/__init__.py +1 -0
- erk/core/planner/registry_abc.py +8 -0
- erk/core/planner/registry_fake.py +129 -0
- erk/core/planner/registry_real.py +195 -0
- erk/core/planner/types.py +7 -0
- erk/core/pr_utils.py +30 -0
- erk/core/release_notes.py +263 -0
- erk/core/repo_discovery.py +126 -0
- erk/core/script_writer.py +41 -0
- erk/core/services/__init__.py +1 -0
- erk/core/services/plan_list_service.py +94 -0
- erk/core/shell.py +51 -0
- erk/core/user_feedback.py +11 -0
- erk/core/version_check.py +55 -0
- erk/core/workflow_display.py +75 -0
- erk/core/worktree_pool.py +190 -0
- erk/core/worktree_utils.py +300 -0
- erk/data/CHANGELOG.md +438 -0
- erk/data/__init__.py +1 -0
- erk/data/claude/agents/devrun.md +180 -0
- erk/data/claude/commands/erk/__init__.py +0 -0
- erk/data/claude/commands/erk/create-extraction-plan.md +360 -0
- erk/data/claude/commands/erk/fix-conflicts.md +25 -0
- erk/data/claude/commands/erk/git-pr-push.md +345 -0
- erk/data/claude/commands/erk/implement-stacked-plan.md +96 -0
- erk/data/claude/commands/erk/land.md +193 -0
- erk/data/claude/commands/erk/objective-create.md +370 -0
- erk/data/claude/commands/erk/objective-list.md +34 -0
- erk/data/claude/commands/erk/objective-next-plan.md +220 -0
- erk/data/claude/commands/erk/objective-update-with-landed-pr.md +216 -0
- erk/data/claude/commands/erk/plan-implement.md +202 -0
- erk/data/claude/commands/erk/plan-save.md +45 -0
- erk/data/claude/commands/erk/plan-submit.md +39 -0
- erk/data/claude/commands/erk/pr-address.md +367 -0
- erk/data/claude/commands/erk/pr-submit.md +58 -0
- erk/data/claude/skills/dignified-python/SKILL.md +48 -0
- erk/data/claude/skills/dignified-python/cli-patterns.md +155 -0
- erk/data/claude/skills/dignified-python/dignified-python-core.md +1190 -0
- erk/data/claude/skills/dignified-python/subprocess.md +99 -0
- erk/data/claude/skills/dignified-python/versions/python-3.10.md +517 -0
- erk/data/claude/skills/dignified-python/versions/python-3.11.md +536 -0
- erk/data/claude/skills/dignified-python/versions/python-3.12.md +662 -0
- erk/data/claude/skills/dignified-python/versions/python-3.13.md +653 -0
- erk/data/claude/skills/erk-diff-analysis/SKILL.md +27 -0
- erk/data/claude/skills/erk-diff-analysis/references/commit-message-prompt.md +78 -0
- erk/data/claude/skills/learned-docs/SKILL.md +362 -0
- erk/data/github/actions/setup-claude-erk/action.yml +11 -0
- erk/data/github/prompts/dignified-python-review.md +125 -0
- erk/data/github/workflows/dignified-python-review.yml +61 -0
- erk/data/github/workflows/erk-impl.yml +251 -0
- erk/hooks/__init__.py +1 -0
- erk/hooks/decorators.py +319 -0
- erk/status/__init__.py +8 -0
- erk/status/collectors/__init__.py +9 -0
- erk/status/collectors/base.py +52 -0
- erk/status/collectors/git.py +76 -0
- erk/status/collectors/github.py +81 -0
- erk/status/collectors/graphite.py +80 -0
- erk/status/collectors/impl.py +145 -0
- erk/status/models/__init__.py +4 -0
- erk/status/models/status_data.py +404 -0
- erk/status/orchestrator.py +169 -0
- erk/status/renderers/__init__.py +5 -0
- erk/status/renderers/simple.py +322 -0
- erk/tui/AGENTS.md +193 -0
- erk/tui/CLAUDE.md +1 -0
- erk/tui/__init__.py +1 -0
- erk/tui/app.py +1404 -0
- erk/tui/commands/__init__.py +1 -0
- erk/tui/commands/executor.py +66 -0
- erk/tui/commands/provider.py +165 -0
- erk/tui/commands/real_executor.py +63 -0
- erk/tui/commands/registry.py +121 -0
- erk/tui/commands/types.py +36 -0
- erk/tui/data/__init__.py +1 -0
- erk/tui/data/provider.py +492 -0
- erk/tui/data/types.py +104 -0
- erk/tui/filtering/__init__.py +1 -0
- erk/tui/filtering/logic.py +43 -0
- erk/tui/filtering/types.py +55 -0
- erk/tui/jsonl_viewer/__init__.py +1 -0
- erk/tui/jsonl_viewer/app.py +61 -0
- erk/tui/jsonl_viewer/models.py +208 -0
- erk/tui/jsonl_viewer/widgets.py +204 -0
- erk/tui/sorting/__init__.py +6 -0
- erk/tui/sorting/logic.py +55 -0
- erk/tui/sorting/types.py +68 -0
- erk/tui/styles/dash.tcss +95 -0
- erk/tui/widgets/__init__.py +1 -0
- erk/tui/widgets/command_output.py +112 -0
- erk/tui/widgets/plan_table.py +276 -0
- erk/tui/widgets/status_bar.py +116 -0
- erk-0.4.5.dist-info/METADATA +376 -0
- erk-0.4.5.dist-info/RECORD +331 -0
- erk-0.4.5.dist-info/WHEEL +4 -0
- erk-0.4.5.dist-info/entry_points.txt +2 -0
- erk-0.4.5.dist-info/licenses/LICENSE.md +3 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""Branch unassign command - remove a branch assignment from a pool slot."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
from erk.cli.commands.slot.common import get_placeholder_branch_name
|
|
9
|
+
from erk.cli.core import discover_repo_context
|
|
10
|
+
from erk.core.context import ErkContext
|
|
11
|
+
from erk.core.repo_discovery import RepoContext
|
|
12
|
+
from erk.core.worktree_pool import (
|
|
13
|
+
PoolState,
|
|
14
|
+
SlotAssignment,
|
|
15
|
+
load_pool_state,
|
|
16
|
+
save_pool_state,
|
|
17
|
+
)
|
|
18
|
+
from erk_shared.output.output import user_output
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class UnassignResult:
|
|
23
|
+
"""Result of an unassign operation."""
|
|
24
|
+
|
|
25
|
+
branch_name: str
|
|
26
|
+
slot_name: str
|
|
27
|
+
trunk_branch: str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def execute_unassign(
|
|
31
|
+
ctx: ErkContext,
|
|
32
|
+
repo: RepoContext,
|
|
33
|
+
state: PoolState,
|
|
34
|
+
assignment: SlotAssignment,
|
|
35
|
+
) -> UnassignResult:
|
|
36
|
+
"""Execute the unassign operation for a pool slot.
|
|
37
|
+
|
|
38
|
+
This function handles:
|
|
39
|
+
- Checking for uncommitted changes
|
|
40
|
+
- Getting or creating placeholder branch
|
|
41
|
+
- Checking out placeholder branch
|
|
42
|
+
- Removing assignment from pool state
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
ctx: ErkContext with git operations
|
|
46
|
+
repo: Repository context
|
|
47
|
+
state: Current pool state
|
|
48
|
+
assignment: The assignment to remove
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
UnassignResult with branch name, slot name, and trunk branch
|
|
52
|
+
|
|
53
|
+
Raises:
|
|
54
|
+
SystemExit: If worktree has uncommitted changes or placeholder branch cannot be determined
|
|
55
|
+
"""
|
|
56
|
+
# Check for uncommitted changes before switching branches
|
|
57
|
+
if ctx.git.has_uncommitted_changes(assignment.worktree_path):
|
|
58
|
+
user_output(
|
|
59
|
+
f"Error: Worktree has uncommitted changes at {assignment.worktree_path}.\n"
|
|
60
|
+
"Commit or stash your changes before unassigning."
|
|
61
|
+
)
|
|
62
|
+
raise SystemExit(1) from None
|
|
63
|
+
|
|
64
|
+
# Get or create placeholder branch
|
|
65
|
+
placeholder_branch = get_placeholder_branch_name(assignment.slot_name)
|
|
66
|
+
if placeholder_branch is None:
|
|
67
|
+
user_output(
|
|
68
|
+
f"Error: Could not determine placeholder branch for slot {assignment.slot_name}."
|
|
69
|
+
)
|
|
70
|
+
raise SystemExit(1) from None
|
|
71
|
+
|
|
72
|
+
trunk_branch = ctx.git.detect_trunk_branch(repo.root)
|
|
73
|
+
local_branches = ctx.git.list_local_branches(repo.root)
|
|
74
|
+
|
|
75
|
+
if placeholder_branch not in local_branches:
|
|
76
|
+
ctx.git.create_branch(repo.root, placeholder_branch, trunk_branch)
|
|
77
|
+
|
|
78
|
+
# Checkout placeholder branch in the worktree
|
|
79
|
+
ctx.git.checkout_branch(assignment.worktree_path, placeholder_branch)
|
|
80
|
+
|
|
81
|
+
# Remove assignment from state (immutable update)
|
|
82
|
+
new_assignments = tuple(a for a in state.assignments if a.slot_name != assignment.slot_name)
|
|
83
|
+
new_state = PoolState(
|
|
84
|
+
version=state.version,
|
|
85
|
+
pool_size=state.pool_size,
|
|
86
|
+
slots=state.slots,
|
|
87
|
+
assignments=new_assignments,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Save updated state (guard for dry-run mode)
|
|
91
|
+
if ctx.dry_run:
|
|
92
|
+
user_output("[DRY RUN] Would save pool state")
|
|
93
|
+
else:
|
|
94
|
+
save_pool_state(repo.pool_json_path, new_state)
|
|
95
|
+
|
|
96
|
+
return UnassignResult(
|
|
97
|
+
branch_name=assignment.branch_name,
|
|
98
|
+
slot_name=assignment.slot_name,
|
|
99
|
+
trunk_branch=trunk_branch,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _find_assignment_by_slot(state: PoolState, slot_name: str) -> SlotAssignment | None:
|
|
104
|
+
"""Find an assignment by slot name.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
state: Current pool state
|
|
108
|
+
slot_name: A slot name (e.g., "erk-managed-wt-01")
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
SlotAssignment if found, None otherwise
|
|
112
|
+
"""
|
|
113
|
+
for assignment in state.assignments:
|
|
114
|
+
if assignment.slot_name == slot_name:
|
|
115
|
+
return assignment
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _find_assignment_by_cwd(state: PoolState, cwd: Path) -> SlotAssignment | None:
|
|
120
|
+
"""Find an assignment by checking if cwd is within a pool slot's worktree.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
state: Current pool state
|
|
124
|
+
cwd: Current working directory
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
SlotAssignment if cwd is within a pool slot, None otherwise
|
|
128
|
+
"""
|
|
129
|
+
if not cwd.exists():
|
|
130
|
+
return None
|
|
131
|
+
resolved_cwd = cwd.resolve()
|
|
132
|
+
for assignment in state.assignments:
|
|
133
|
+
if not assignment.worktree_path.exists():
|
|
134
|
+
continue
|
|
135
|
+
wt_path = assignment.worktree_path.resolve()
|
|
136
|
+
if resolved_cwd == wt_path or wt_path in resolved_cwd.parents:
|
|
137
|
+
return assignment
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@click.command("unassign")
|
|
142
|
+
@click.argument("worktree", metavar="WORKTREE", required=False)
|
|
143
|
+
@click.pass_obj
|
|
144
|
+
def branch_unassign(ctx: ErkContext, worktree: str | None) -> None:
|
|
145
|
+
"""Remove a branch assignment from a pool slot.
|
|
146
|
+
|
|
147
|
+
WORKTREE is the slot name (e.g., erk-managed-wt-01).
|
|
148
|
+
|
|
149
|
+
If no argument is provided, the current pool slot is detected from the
|
|
150
|
+
working directory.
|
|
151
|
+
|
|
152
|
+
The worktree directory is kept for reuse with future assignments.
|
|
153
|
+
|
|
154
|
+
Examples:
|
|
155
|
+
erk br unassign erk-managed-wt-01 # Unassign by worktree name
|
|
156
|
+
erk br unassign # Unassign current slot (from within pool worktree)
|
|
157
|
+
"""
|
|
158
|
+
repo = discover_repo_context(ctx, ctx.cwd)
|
|
159
|
+
|
|
160
|
+
# Load pool state
|
|
161
|
+
state = load_pool_state(repo.pool_json_path)
|
|
162
|
+
if state is None:
|
|
163
|
+
user_output("Error: No pool configured. Run `erk br create` first.")
|
|
164
|
+
raise SystemExit(1) from None
|
|
165
|
+
|
|
166
|
+
# Find the assignment to remove
|
|
167
|
+
assignment: SlotAssignment | None = None
|
|
168
|
+
|
|
169
|
+
if worktree is not None:
|
|
170
|
+
# Find by slot name
|
|
171
|
+
assignment = _find_assignment_by_slot(state, worktree)
|
|
172
|
+
if assignment is None:
|
|
173
|
+
user_output(
|
|
174
|
+
f"Error: No worktree found for '{worktree}'.\n"
|
|
175
|
+
"Run `erk slot list` to see current assignments."
|
|
176
|
+
)
|
|
177
|
+
raise SystemExit(1) from None
|
|
178
|
+
else:
|
|
179
|
+
# Detect current slot from cwd
|
|
180
|
+
assignment = _find_assignment_by_cwd(state, ctx.cwd)
|
|
181
|
+
if assignment is None:
|
|
182
|
+
user_output(
|
|
183
|
+
"Error: Not inside a pool slot. Specify worktree name.\n"
|
|
184
|
+
"Usage: erk br unassign WORKTREE"
|
|
185
|
+
)
|
|
186
|
+
raise SystemExit(1) from None
|
|
187
|
+
|
|
188
|
+
# Execute the unassign operation
|
|
189
|
+
result = execute_unassign(ctx, repo, state, assignment)
|
|
190
|
+
|
|
191
|
+
user_output(
|
|
192
|
+
click.style("✓ ", fg="green")
|
|
193
|
+
+ f"Unassigned {click.style(result.branch_name, fg='yellow')} "
|
|
194
|
+
+ f"from {click.style(result.slot_name, fg='cyan')}"
|
|
195
|
+
)
|
|
196
|
+
user_output(" Switched to placeholder branch")
|
|
197
|
+
user_output(" Tip: Use 'erk wt co root' to return to root worktree")
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Claude Code session tools command group."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from erk.cli.commands.cc.jsonl_cmd import jsonl_viewer
|
|
6
|
+
from erk.cli.commands.cc.session import session_group
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.group("cc")
|
|
10
|
+
def cc_group() -> None:
|
|
11
|
+
"""Claude Code session tools."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
cc_group.add_command(session_group)
|
|
15
|
+
cc_group.add_command(jsonl_viewer)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""JSONL viewer CLI command."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from erk.tui.jsonl_viewer.app import JsonlViewerApp
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.command("jsonl")
|
|
11
|
+
@click.argument("path", type=click.Path(exists=True, path_type=Path))
|
|
12
|
+
def jsonl_viewer(path: Path) -> None:
|
|
13
|
+
"""View a Claude Code session JSONL file in an interactive TUI.
|
|
14
|
+
|
|
15
|
+
PATH is the path to a .jsonl file to view.
|
|
16
|
+
|
|
17
|
+
Use Enter to expand/collapse entries, q or Esc to quit.
|
|
18
|
+
"""
|
|
19
|
+
app = JsonlViewerApp(path)
|
|
20
|
+
app.run()
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Claude Code Session Commands
|
|
2
|
+
|
|
3
|
+
User-facing CLI commands for working with Claude Code sessions.
|
|
4
|
+
|
|
5
|
+
## Related Documentation
|
|
6
|
+
|
|
7
|
+
**Before modifying this code, read the session documentation:**
|
|
8
|
+
|
|
9
|
+
@docs/learned/sessions/
|
|
10
|
+
|
|
11
|
+
## Architecture
|
|
12
|
+
|
|
13
|
+
These commands use the `ClaudeCodeSessionStore` abstraction from `erk_shared.extraction.claude_code_session_store`:
|
|
14
|
+
|
|
15
|
+
- **ABC**: `ClaudeCodeSessionStore` - interface for session operations
|
|
16
|
+
- **Real**: `RealClaudeCodeSessionStore` - reads from `~/.claude/projects/`
|
|
17
|
+
- **Fake**: `FakeClaudeCodeSessionStore` - in-memory for testing
|
|
18
|
+
|
|
19
|
+
## Commands
|
|
20
|
+
|
|
21
|
+
### `erk cc session list`
|
|
22
|
+
|
|
23
|
+
Lists sessions for the current worktree with session ID, time, size, and summary.
|
|
24
|
+
|
|
25
|
+
**Key behaviors:**
|
|
26
|
+
|
|
27
|
+
- By default, excludes agent sessions (files starting with `agent-`)
|
|
28
|
+
- `--include-agents` flag includes agent sessions with parent linkage
|
|
29
|
+
- Sessions sorted by modification time (newest first)
|
|
30
|
+
- Summary extracted from first user message
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Session management command group for Claude Code."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from erk.cli.commands.cc.session.list_cmd import list_sessions
|
|
6
|
+
from erk.cli.commands.cc.session.show_cmd import show_session
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.group("session")
|
|
10
|
+
def session_group() -> None:
|
|
11
|
+
"""Manage Claude Code sessions."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
session_group.add_command(list_sessions)
|
|
15
|
+
session_group.add_command(show_session)
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""List Claude Code sessions for the current worktree."""
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
import time
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
from erk.core.context import ErkContext
|
|
12
|
+
from erk_shared.extraction.claude_installation import ClaudeInstallation
|
|
13
|
+
from erk_shared.extraction.session_schema import extract_first_user_message_text
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def format_relative_time(mtime: float) -> str:
|
|
17
|
+
"""Format modification time as human-readable relative time.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
mtime: Unix timestamp (seconds since epoch)
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Human-readable relative time string
|
|
24
|
+
"""
|
|
25
|
+
now = time.time()
|
|
26
|
+
delta = now - mtime
|
|
27
|
+
|
|
28
|
+
if delta < 30:
|
|
29
|
+
return "just now"
|
|
30
|
+
if delta < 3600: # < 1 hour
|
|
31
|
+
minutes = int(delta / 60)
|
|
32
|
+
return f"{minutes}m ago"
|
|
33
|
+
if delta < 86400: # < 24 hours
|
|
34
|
+
hours = int(delta / 3600)
|
|
35
|
+
return f"{hours}h ago"
|
|
36
|
+
if delta < 604800: # < 7 days
|
|
37
|
+
days = int(delta / 86400)
|
|
38
|
+
return f"{days}d ago"
|
|
39
|
+
# >= 7 days: show absolute date
|
|
40
|
+
return format_display_time(mtime)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def format_display_time(mtime: float) -> str:
|
|
44
|
+
"""Format modification time as display string.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
mtime: Unix timestamp (seconds since epoch)
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Formatted date string like "Dec 3, 11:38 AM"
|
|
51
|
+
"""
|
|
52
|
+
dt = datetime.datetime.fromtimestamp(mtime)
|
|
53
|
+
return dt.strftime("%b %-d, %-I:%M %p")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def format_size(size_bytes: int) -> str:
|
|
57
|
+
"""Format size in bytes as human-readable string.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
size_bytes: Size in bytes
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Human-readable size string like "45KB"
|
|
64
|
+
"""
|
|
65
|
+
if size_bytes < 1024:
|
|
66
|
+
return f"{size_bytes}B"
|
|
67
|
+
if size_bytes < 1024 * 1024:
|
|
68
|
+
return f"{size_bytes // 1024}KB"
|
|
69
|
+
return f"{size_bytes // (1024 * 1024)}MB"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _list_sessions_impl(
|
|
73
|
+
claude_installation: ClaudeInstallation,
|
|
74
|
+
cwd: Path,
|
|
75
|
+
limit: int,
|
|
76
|
+
include_agents: bool,
|
|
77
|
+
) -> None:
|
|
78
|
+
"""Implementation of session listing logic.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
claude_installation: Claude installation to query
|
|
82
|
+
cwd: Current working directory (project identifier)
|
|
83
|
+
limit: Maximum number of sessions to show
|
|
84
|
+
include_agents: Whether to include agent sessions in the listing
|
|
85
|
+
"""
|
|
86
|
+
# Check if project exists
|
|
87
|
+
if not claude_installation.has_project(cwd):
|
|
88
|
+
click.echo(f"No Claude Code sessions found for: {cwd}", err=True)
|
|
89
|
+
raise SystemExit(1)
|
|
90
|
+
|
|
91
|
+
# Get sessions
|
|
92
|
+
sessions = claude_installation.find_sessions(
|
|
93
|
+
cwd,
|
|
94
|
+
current_session_id=None,
|
|
95
|
+
min_size=0,
|
|
96
|
+
limit=limit,
|
|
97
|
+
include_agents=include_agents,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if not sessions:
|
|
101
|
+
click.echo("No sessions found.", err=True)
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
# Create Rich table
|
|
105
|
+
table = Table(show_header=True, header_style="bold", box=None)
|
|
106
|
+
table.add_column("id", style="cyan", no_wrap=True)
|
|
107
|
+
if include_agents:
|
|
108
|
+
table.add_column("parent", no_wrap=True)
|
|
109
|
+
table.add_column("time", no_wrap=True)
|
|
110
|
+
table.add_column("size", no_wrap=True, justify="right")
|
|
111
|
+
table.add_column("summary", no_wrap=False)
|
|
112
|
+
|
|
113
|
+
for session in sessions:
|
|
114
|
+
# Read session content for summary extraction
|
|
115
|
+
content = claude_installation.read_session(cwd, session.session_id, include_agents=False)
|
|
116
|
+
summary = ""
|
|
117
|
+
if content is not None:
|
|
118
|
+
summary = extract_first_user_message_text(content.main_content, max_length=50)
|
|
119
|
+
|
|
120
|
+
if include_agents:
|
|
121
|
+
# Show first 8 chars of parent_session_id for agents, empty for main sessions
|
|
122
|
+
parent_short = session.parent_session_id[:8] if session.parent_session_id else ""
|
|
123
|
+
table.add_row(
|
|
124
|
+
session.session_id,
|
|
125
|
+
parent_short,
|
|
126
|
+
format_relative_time(session.modified_at),
|
|
127
|
+
format_size(session.size_bytes),
|
|
128
|
+
summary,
|
|
129
|
+
)
|
|
130
|
+
else:
|
|
131
|
+
table.add_row(
|
|
132
|
+
session.session_id,
|
|
133
|
+
format_relative_time(session.modified_at),
|
|
134
|
+
format_size(session.size_bytes),
|
|
135
|
+
summary,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Output table to stderr (consistent with user_output convention)
|
|
139
|
+
console = Console(stderr=True, force_terminal=True)
|
|
140
|
+
console.print(table)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@click.command("list")
|
|
144
|
+
@click.option(
|
|
145
|
+
"--limit",
|
|
146
|
+
default=10,
|
|
147
|
+
type=int,
|
|
148
|
+
help="Maximum number of sessions to list",
|
|
149
|
+
)
|
|
150
|
+
@click.option(
|
|
151
|
+
"--include-agents",
|
|
152
|
+
is_flag=True,
|
|
153
|
+
default=False,
|
|
154
|
+
help="Include agent sessions in the listing",
|
|
155
|
+
)
|
|
156
|
+
@click.pass_obj
|
|
157
|
+
def list_sessions(ctx: ErkContext, limit: int, include_agents: bool) -> None:
|
|
158
|
+
"""List Claude Code sessions for the current worktree.
|
|
159
|
+
|
|
160
|
+
Shows a table with session ID, time, size, and summary (first user message).
|
|
161
|
+
"""
|
|
162
|
+
_list_sessions_impl(
|
|
163
|
+
ctx.claude_installation,
|
|
164
|
+
ctx.cwd,
|
|
165
|
+
limit,
|
|
166
|
+
include_agents,
|
|
167
|
+
)
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""Show details for a specific Claude Code session."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
from erk.cli.commands.cc.session.list_cmd import (
|
|
9
|
+
format_display_time,
|
|
10
|
+
format_size,
|
|
11
|
+
)
|
|
12
|
+
from erk.cli.ensure import Ensure
|
|
13
|
+
from erk.core.context import ErkContext
|
|
14
|
+
from erk_shared.extraction.claude_installation import ClaudeInstallation
|
|
15
|
+
from erk_shared.extraction.session_schema import (
|
|
16
|
+
AgentInfo,
|
|
17
|
+
extract_agent_info_from_jsonl,
|
|
18
|
+
extract_first_user_message_text,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def format_duration(seconds: float) -> str:
|
|
23
|
+
"""Format a duration in seconds to a human-readable string.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
seconds: Duration in seconds
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Formatted string like "42s", "1m 30s", or "1h 15m"
|
|
30
|
+
"""
|
|
31
|
+
if seconds < 60:
|
|
32
|
+
return f"{int(seconds)}s"
|
|
33
|
+
elif seconds < 3600:
|
|
34
|
+
minutes = int(seconds // 60)
|
|
35
|
+
secs = int(seconds % 60)
|
|
36
|
+
return f"{minutes}m {secs}s"
|
|
37
|
+
else:
|
|
38
|
+
hours = int(seconds // 3600)
|
|
39
|
+
minutes = int((seconds % 3600) // 60)
|
|
40
|
+
return f"{hours}h {minutes}m"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _show_session_impl(
|
|
44
|
+
claude_installation: ClaudeInstallation,
|
|
45
|
+
cwd: Path,
|
|
46
|
+
session_id: str | None,
|
|
47
|
+
) -> None:
|
|
48
|
+
"""Implementation of session show logic.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
claude_installation: Claude installation to query
|
|
52
|
+
cwd: Current working directory (project identifier)
|
|
53
|
+
session_id: Session ID to show details for, or None to use most recent
|
|
54
|
+
"""
|
|
55
|
+
console = Console(stderr=True, force_terminal=True)
|
|
56
|
+
|
|
57
|
+
# Check if project exists
|
|
58
|
+
Ensure.invariant(
|
|
59
|
+
claude_installation.has_project(cwd),
|
|
60
|
+
f"No Claude Code sessions found for: {cwd}",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# If no session_id provided, use the most recent session
|
|
64
|
+
inferred = False
|
|
65
|
+
if session_id is None:
|
|
66
|
+
sessions = claude_installation.find_sessions(
|
|
67
|
+
cwd,
|
|
68
|
+
current_session_id=None,
|
|
69
|
+
min_size=0,
|
|
70
|
+
include_agents=False,
|
|
71
|
+
limit=1,
|
|
72
|
+
)
|
|
73
|
+
Ensure.invariant(len(sessions) > 0, "No sessions found.")
|
|
74
|
+
session_id = sessions[0].session_id
|
|
75
|
+
inferred = True
|
|
76
|
+
|
|
77
|
+
# Get the session
|
|
78
|
+
session = Ensure.session(claude_installation.get_session(cwd, session_id))
|
|
79
|
+
|
|
80
|
+
# Check if this is an agent session - provide helpful error
|
|
81
|
+
parent_id = session.parent_session_id
|
|
82
|
+
Ensure.invariant(
|
|
83
|
+
parent_id is None,
|
|
84
|
+
f"Cannot show agent session directly. Use parent session instead: {parent_id}",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Get the session path
|
|
88
|
+
session_path = claude_installation.get_session_path(cwd, session_id)
|
|
89
|
+
|
|
90
|
+
# Read session content for summary and agent info extraction
|
|
91
|
+
content = claude_installation.read_session(cwd, session_id, include_agents=False)
|
|
92
|
+
summary = ""
|
|
93
|
+
agent_infos: dict[str, AgentInfo] = {}
|
|
94
|
+
if content is not None:
|
|
95
|
+
summary = extract_first_user_message_text(content.main_content, max_length=100)
|
|
96
|
+
agent_infos = extract_agent_info_from_jsonl(content.main_content)
|
|
97
|
+
|
|
98
|
+
# Print inferred message if applicable
|
|
99
|
+
if inferred:
|
|
100
|
+
msg = f"Using most recent session for this worktree: {session.session_id}"
|
|
101
|
+
console.print(f"[dim]{msg}[/dim]")
|
|
102
|
+
console.print()
|
|
103
|
+
|
|
104
|
+
# Display metadata as key-value pairs
|
|
105
|
+
console.print(f"[bold]ID:[/bold] {session.session_id}")
|
|
106
|
+
console.print(f"[bold]Size:[/bold] {format_size(session.size_bytes)}")
|
|
107
|
+
console.print(f"[bold]Modified:[/bold] {format_display_time(session.modified_at)}")
|
|
108
|
+
if summary:
|
|
109
|
+
console.print(f"[bold]Summary:[/bold] {summary}")
|
|
110
|
+
if session_path is not None:
|
|
111
|
+
console.print(f"[bold]Path:[/bold] {session_path}")
|
|
112
|
+
|
|
113
|
+
# Find and display child agent sessions
|
|
114
|
+
all_sessions = claude_installation.find_sessions(
|
|
115
|
+
cwd,
|
|
116
|
+
current_session_id=None,
|
|
117
|
+
min_size=0,
|
|
118
|
+
include_agents=True,
|
|
119
|
+
limit=1000,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Filter to only agent sessions with this parent
|
|
123
|
+
child_agents = [s for s in all_sessions if s.parent_session_id == session_id]
|
|
124
|
+
|
|
125
|
+
if child_agents:
|
|
126
|
+
console.print()
|
|
127
|
+
console.print("[bold]Agent Sessions:[/bold]")
|
|
128
|
+
|
|
129
|
+
for agent in child_agents:
|
|
130
|
+
info = agent_infos.get(agent.session_id)
|
|
131
|
+
agent_path = claude_installation.get_session_path(cwd, agent.session_id)
|
|
132
|
+
|
|
133
|
+
console.print()
|
|
134
|
+
# Format: type("prompt") or just session_id if no info
|
|
135
|
+
if info and info.agent_type:
|
|
136
|
+
# Clean up prompt: collapse whitespace, truncate
|
|
137
|
+
prompt_clean = " ".join(info.prompt.split())
|
|
138
|
+
if len(prompt_clean) > 80:
|
|
139
|
+
prompt_preview = prompt_clean[:80] + "..."
|
|
140
|
+
else:
|
|
141
|
+
prompt_preview = prompt_clean
|
|
142
|
+
console.print(f' [cyan]{info.agent_type}[/cyan]("{prompt_preview}")')
|
|
143
|
+
else:
|
|
144
|
+
console.print(f" [cyan]{agent.session_id}[/cyan]")
|
|
145
|
+
# Build metadata line: time, size, and optional duration
|
|
146
|
+
metadata_parts = [
|
|
147
|
+
format_display_time(agent.modified_at),
|
|
148
|
+
format_size(agent.size_bytes),
|
|
149
|
+
]
|
|
150
|
+
if info and info.duration_secs is not None:
|
|
151
|
+
metadata_parts.append(format_duration(info.duration_secs))
|
|
152
|
+
console.print(f" {' '.join(metadata_parts)}")
|
|
153
|
+
if agent_path:
|
|
154
|
+
console.print(f" {agent_path}")
|
|
155
|
+
else:
|
|
156
|
+
console.print()
|
|
157
|
+
console.print("[dim]No agent sessions[/dim]")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@click.command("show")
|
|
161
|
+
@click.argument("session_id", required=False, default=None)
|
|
162
|
+
@click.pass_obj
|
|
163
|
+
def show_session(ctx: ErkContext, session_id: str | None) -> None:
|
|
164
|
+
"""Show details for a specific Claude Code session.
|
|
165
|
+
|
|
166
|
+
Displays session metadata (ID, size, modified time, path, summary)
|
|
167
|
+
and lists any child agent sessions.
|
|
168
|
+
|
|
169
|
+
If SESSION_ID is not provided, shows the most recent session.
|
|
170
|
+
"""
|
|
171
|
+
_show_session_impl(
|
|
172
|
+
ctx.claude_installation,
|
|
173
|
+
ctx.cwd,
|
|
174
|
+
session_id,
|
|
175
|
+
)
|