claude-code-tools 1.16.2__tar.gz → 1.18.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/PKG-INFO +4 -71
  2. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/README.md +3 -70
  3. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/__init__.py +1 -1
  4. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/aichat.py +180 -1
  5. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/codex_server.py +32 -31
  6. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/codex_server_fingerprint.py +205 -15
  7. claude_code_tools-1.18.0/claude_code_tools/codex_server_legacy.py +278 -0
  8. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/codex_server_models.py +10 -32
  9. claude_code_tools-1.18.0/claude_code_tools/codex_server_reservation.py +70 -0
  10. claude_code_tools-1.18.0/claude_code_tools/codex_server_retry.py +45 -0
  11. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/codex_server_reuse.py +21 -0
  12. claude_code_tools-1.18.0/claude_code_tools/codex_server_state.py +65 -0
  13. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/export_session.py +267 -106
  14. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/find_claude_session.py +116 -36
  15. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/find_codex_session.py +163 -113
  16. claude_code_tools-1.18.0/claude_code_tools/port_claude_noise.py +405 -0
  17. claude_code_tools-1.18.0/claude_code_tools/port_claude_to_codex.py +760 -0
  18. claude_code_tools-1.18.0/claude_code_tools/port_codex_flatten.py +518 -0
  19. claude_code_tools-1.18.0/claude_code_tools/port_codex_to_claude.py +714 -0
  20. claude_code_tools-1.18.0/claude_code_tools/port_service.py +384 -0
  21. claude_code_tools-1.18.0/claude_code_tools/resolve_session.py +995 -0
  22. claude_code_tools-1.18.0/claude_code_tools/resolve_session_names.py +60 -0
  23. claude_code_tools-1.18.0/claude_code_tools/resolve_session_render.py +177 -0
  24. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/session_utils.py +304 -84
  25. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/trim_session.py +6 -2
  26. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/pyproject.toml +2 -2
  27. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/.gitignore +0 -0
  28. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/LICENSE +0 -0
  29. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/action_rpc.py +0 -0
  30. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/agent_tunnel/__init__.py +0 -0
  31. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/agent_tunnel/backends.py +0 -0
  32. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/agent_tunnel/cli.py +0 -0
  33. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/agent_tunnel/config.py +0 -0
  34. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/agent_tunnel/convert.py +0 -0
  35. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/agent_tunnel/discord_bot.py +0 -0
  36. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/agent_tunnel/locking.py +0 -0
  37. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/agent_tunnel/paths.py +0 -0
  38. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/agent_tunnel/registry.py +0 -0
  39. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/agent_tunnel/session.py +0 -0
  40. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/agent_tunnel/store.py +0 -0
  41. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/agent_tunnel/tmux.py +0 -0
  42. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/agent_tunnel/trust.py +0 -0
  43. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/claude_continue.py +0 -0
  44. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/codex_continue.py +0 -0
  45. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/codex_server_cli.py +0 -0
  46. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/codex_server_generation.py +0 -0
  47. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/codex_server_process.py +0 -0
  48. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/codex_server_supervisor.py +0 -0
  49. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/codex_server_worker.py +0 -0
  50. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/config.py +0 -0
  51. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/csv2gsheet.py +0 -0
  52. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/delete_session.py +0 -0
  53. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/dotenv_vault.py +0 -0
  54. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/env_safe.py +0 -0
  55. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/export_all.py +0 -0
  56. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/export_claude_session.py +0 -0
  57. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/export_codex_session.py +0 -0
  58. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/find_original_session.py +0 -0
  59. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/find_session.py +0 -0
  60. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/find_trimmed_sessions.py +0 -0
  61. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/fix_session.py +0 -0
  62. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/gdoc2docx.py +0 -0
  63. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/gdoc2md.py +0 -0
  64. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/gsheet2csv.py +0 -0
  65. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/md2gdoc.py +0 -0
  66. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/msg/__init__.py +0 -0
  67. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/msg/cli.py +0 -0
  68. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/msg/hooks.py +0 -0
  69. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/msg/models.py +0 -0
  70. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/msg/prompt_detect.py +0 -0
  71. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/msg/store.py +0 -0
  72. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/msg/watcher.py +0 -0
  73. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/node_menu_ui.py +0 -0
  74. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/search_index.py +0 -0
  75. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/session_lineage.py +0 -0
  76. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/session_menu.py +0 -0
  77. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/session_menu_cli.py +0 -0
  78. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/smart_trim.py +0 -0
  79. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/smart_trim_core.py +0 -0
  80. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/tmux_cli_controller.py +0 -0
  81. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/tmux_execution_helpers.py +0 -0
  82. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/tmux_remote_controller.py +0 -0
  83. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/trim_in_place.py +0 -0
  84. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/trim_session_claude.py +0 -0
  85. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/trim_session_codex.py +0 -0
  86. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/workflow_cli.py +0 -0
  87. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/workflow_cli_contract.py +0 -0
  88. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/workflow_cli_formatting.py +0 -0
  89. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/workflow_cli_identity_policy.py +0 -0
  90. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/workflow_cli_manifest.py +0 -0
  91. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/workflow_cli_projection.py +0 -0
  92. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/workflow_cli_rendering.py +0 -0
  93. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/workflow_cli_snapshots.py +0 -0
  94. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/workflow_cli_store_backends.py +0 -0
  95. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/workflow_processes.py +0 -0
  96. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/workflow_runs.py +0 -0
  97. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/workflow_store_io.py +0 -0
  98. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/claude_code_tools/workflow_validation.py +0 -0
  99. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/docs/agent-tunnel-spec.md +0 -0
  100. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/docs/ai-agent-writing-skills.md +0 -0
  101. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/docs/cc-codex-instructions.md +0 -0
  102. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/docs/claude-code-chutes.md +0 -0
  103. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/docs/claude-code-tmux-tutorials.md +0 -0
  104. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/docs/dot-zshrc.md +0 -0
  105. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/docs/ensemble-ideas.md +0 -0
  106. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/docs/example-with-images.md +0 -0
  107. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/docs/find-claude-session.md +0 -0
  108. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/docs/intercom-spec.md +0 -0
  109. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/docs/linked-in-20260102.md +0 -0
  110. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/docs/lmsh.md +0 -0
  111. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/docs/local-llm-setup.md +0 -0
  112. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/docs/msg-phase-a-plan.md +0 -0
  113. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/docs/msg-plan-v2.md +0 -0
  114. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/docs/paper-writing-style-transfer-tools.md +0 -0
  115. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/docs/reddit-aichat-resume-v2.md +0 -0
  116. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/docs/reddit-aichat-resume.md +0 -0
  117. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/docs/reddit-aichat.md +0 -0
  118. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/docs/reddit-post.md +0 -0
  119. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/docs/rollover-details.md +0 -0
  120. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/docs/tmux-cli-instructions.md +0 -0
  121. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/docs/vault-documentation.md +0 -0
  122. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/hatch_build.py +0 -0
  123. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/node_ui/action_config.js +0 -0
  124. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/node_ui/menu.js +0 -0
  125. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/node_ui/package-lock.json +0 -0
  126. {claude_code_tools-1.16.2 → claude_code_tools-1.18.0}/node_ui/package.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-code-tools
3
- Version: 1.16.2
3
+ Version: 1.18.0
4
4
  Summary: Collection of tools for working with Claude Code
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.11
@@ -66,81 +66,14 @@ separate Rust binary:
66
66
  [Releases](https://github.com/pchalasani/claude-code-tools/releases)
67
67
  (look for `rust-v*`)
68
68
 
69
- Install the Claude Code workflow plugin:
69
+ Install the Claude Code
70
+ [plugins](https://pchalasani.github.io/claude-code-tools/getting-started/plugins/)
71
+ for hooks, skills, and agents:
70
72
 
71
73
  ```bash
72
74
  claude plugin marketplace add pchalasani/claude-code-tools
73
- claude plugin install workflow@cctools-plugins
74
75
  ```
75
76
 
76
- Install the equivalent dynamic workflow plugin for Codex:
77
-
78
- ```bash
79
- codex plugin marketplace add pchalasani/claude-code-tools
80
- codex plugin add dynamic-workflow@cctools-codex-plugins
81
- ```
82
-
83
- The Codex plugin installs independently: it does not require the Python
84
- `claude-code-tools` package or an npm install. Node.js 20 or newer and Codex
85
- CLI 0.136.0 or newer are required for callbacks. Version 0.136.0 is the first
86
- compatible CLI release for this callback protocol. Codex still marks the app
87
- server and remote TUI interfaces as experimental, so those surfaces may evolve.
88
- The plugin can notify the originating Codex thread through Codex's shared app
89
- server after a detached workflow finishes. Callback preflight also rejects a
90
- stale connected server.
91
-
92
- The optional Python package makes that callback setup a single command:
93
-
94
- ```bash
95
- codex-dynamic
96
- # Or continue the latest conversation:
97
- codex-dynamic resume --last
98
- ```
99
-
100
- `codex-dynamic` starts or reuses the local app server and then hands the
101
- terminal directly to Codex. After a plugin or Codex upgrade, the next launch
102
- automatically starts a fresh server generation; existing sessions and workflow
103
- callbacks stay connected to their old generation. No manual restart is needed.
104
- Use `codex-server status` or `logs` for nondisruptive inspection. A forced stop
105
- or restart cleans up every retained generation and disconnects its attached
106
- TUIs, so use those commands only after exiting the sessions normally.
107
-
108
- The Python package also installs `codex-workflows`, an observational dashboard
109
- for durable workflow runs:
110
-
111
- ```bash
112
- codex-workflows
113
- codex-workflows watch --limit 20
114
- codex-workflows show RUN_ID
115
- codex-workflows --all
116
- ```
117
-
118
- The default list contains only active workflows and identifies the project
119
- folder from which each workflow was launched. Use `show RUN_ID` for its full
120
- working directory, or `--all` to include completed and diagnostic history. The
121
- command reads durable workflow state and process metadata. It never changes a
122
- run, sends a signal, repairs state, or launches an agent. See the
123
- [codex-workflows reference][codex-workflows-reference] for filters, JSON output,
124
- and the complete versioned schema.
125
-
126
- [codex-workflows-reference]:
127
- //pchalasani.github.io/claude-code-tools/tools/codex-dynamic/#observe-workflow-runs
128
-
129
- Server management and passive supervision add no model calls. Completion
130
- reporting starts a new turn only while the thread is idle; otherwise it steers
131
- and extends the active turn. Either reporting path consumes model tokens.
132
-
133
- Each workflow `run` or `resume` uses narrow host execution for that exact
134
- reviewed supervisor command. The workflow JavaScript remains restricted, and
135
- every headless Codex worker keeps its declared sandbox.
136
-
137
- See the
138
- [Claude → Codex migration guide][claude-to-codex-guide]
139
- for usage and workflow-porting instructions.
140
-
141
- [claude-to-codex-guide]:
142
- https://pchalasani.github.io/claude-code-tools/guides/claude-to-codex/
143
-
144
77
  ---
145
78
 
146
79
  Click a card to jump to that feature, or
@@ -40,81 +40,14 @@ separate Rust binary:
40
40
  [Releases](https://github.com/pchalasani/claude-code-tools/releases)
41
41
  (look for `rust-v*`)
42
42
 
43
- Install the Claude Code workflow plugin:
43
+ Install the Claude Code
44
+ [plugins](https://pchalasani.github.io/claude-code-tools/getting-started/plugins/)
45
+ for hooks, skills, and agents:
44
46
 
45
47
  ```bash
46
48
  claude plugin marketplace add pchalasani/claude-code-tools
47
- claude plugin install workflow@cctools-plugins
48
49
  ```
49
50
 
50
- Install the equivalent dynamic workflow plugin for Codex:
51
-
52
- ```bash
53
- codex plugin marketplace add pchalasani/claude-code-tools
54
- codex plugin add dynamic-workflow@cctools-codex-plugins
55
- ```
56
-
57
- The Codex plugin installs independently: it does not require the Python
58
- `claude-code-tools` package or an npm install. Node.js 20 or newer and Codex
59
- CLI 0.136.0 or newer are required for callbacks. Version 0.136.0 is the first
60
- compatible CLI release for this callback protocol. Codex still marks the app
61
- server and remote TUI interfaces as experimental, so those surfaces may evolve.
62
- The plugin can notify the originating Codex thread through Codex's shared app
63
- server after a detached workflow finishes. Callback preflight also rejects a
64
- stale connected server.
65
-
66
- The optional Python package makes that callback setup a single command:
67
-
68
- ```bash
69
- codex-dynamic
70
- # Or continue the latest conversation:
71
- codex-dynamic resume --last
72
- ```
73
-
74
- `codex-dynamic` starts or reuses the local app server and then hands the
75
- terminal directly to Codex. After a plugin or Codex upgrade, the next launch
76
- automatically starts a fresh server generation; existing sessions and workflow
77
- callbacks stay connected to their old generation. No manual restart is needed.
78
- Use `codex-server status` or `logs` for nondisruptive inspection. A forced stop
79
- or restart cleans up every retained generation and disconnects its attached
80
- TUIs, so use those commands only after exiting the sessions normally.
81
-
82
- The Python package also installs `codex-workflows`, an observational dashboard
83
- for durable workflow runs:
84
-
85
- ```bash
86
- codex-workflows
87
- codex-workflows watch --limit 20
88
- codex-workflows show RUN_ID
89
- codex-workflows --all
90
- ```
91
-
92
- The default list contains only active workflows and identifies the project
93
- folder from which each workflow was launched. Use `show RUN_ID` for its full
94
- working directory, or `--all` to include completed and diagnostic history. The
95
- command reads durable workflow state and process metadata. It never changes a
96
- run, sends a signal, repairs state, or launches an agent. See the
97
- [codex-workflows reference][codex-workflows-reference] for filters, JSON output,
98
- and the complete versioned schema.
99
-
100
- [codex-workflows-reference]:
101
- //pchalasani.github.io/claude-code-tools/tools/codex-dynamic/#observe-workflow-runs
102
-
103
- Server management and passive supervision add no model calls. Completion
104
- reporting starts a new turn only while the thread is idle; otherwise it steers
105
- and extends the active turn. Either reporting path consumes model tokens.
106
-
107
- Each workflow `run` or `resume` uses narrow host execution for that exact
108
- reviewed supervisor command. The workflow JavaScript remains restricted, and
109
- every headless Codex worker keeps its declared sandbox.
110
-
111
- See the
112
- [Claude → Codex migration guide][claude-to-codex-guide]
113
- for usage and workflow-porting instructions.
114
-
115
- [claude-to-codex-guide]:
116
- https://pchalasani.github.io/claude-code-tools/guides/claude-to-codex/
117
-
118
51
  ---
119
52
 
120
53
  Click a card to jump to that feature, or
@@ -1,3 +1,3 @@
1
1
  """Claude Code Tools - Collection of utilities for Claude Code."""
2
2
 
3
- __version__ = "1.16.2"
3
+ __version__ = "1.18.0"
@@ -30,6 +30,26 @@ class SessionIDGroup(click.Group):
30
30
  return super().parse_args(ctx, args)
31
31
 
32
32
 
33
+ class ResolveCommand(click.Command):
34
+ """Click command that renders resolver usage errors without usage text."""
35
+
36
+ def parse_args(self, ctx: click.Context, args: list[str]) -> list[str]:
37
+ """Parse arguments, converting Click usage failures to resolver errors."""
38
+ import sys
39
+
40
+ from claude_code_tools.resolve_session_render import _render_error
41
+
42
+ raw_args = tuple(args)
43
+ try:
44
+ return super().parse_args(ctx, args)
45
+ except click.UsageError as error:
46
+ pretty = "--pretty" in raw_args or (
47
+ "--json" not in raw_args and sys.stdout.isatty()
48
+ )
49
+ _render_error("invalid_input", error.format_message(), pretty)
50
+ ctx.exit(1)
51
+
52
+
33
53
  @click.group(cls=SessionIDGroup, invoke_without_command=True)
34
54
  @click.version_option()
35
55
  @click.option(
@@ -95,8 +115,15 @@ def main(ctx, claude_home, codex_home):
95
115
  # Auto-index sessions on every aichat command (incremental, fast if up-to-date)
96
116
  # Skip for build-index/clear-index to avoid double-indexing or state conflicts
97
117
  # In JSON mode (-j/--json), suppress all output for clean parsing
118
+ # 'port' is also skipped: its contract requires the detected
119
+ # direction line to be the FIRST output, so the indexer's progress
120
+ # bar must never print before it.
98
121
  skip_auto_index_cmds = ['build-index', 'clear-index', 'index-stats']
99
- should_skip = any(cmd in sys.argv for cmd in skip_auto_index_cmds)
122
+ should_skip = (
123
+ ctx.invoked_subcommand in ('port', 'resolve')
124
+ or ctx.invoked_subcommand in skip_auto_index_cmds
125
+ or any(cmd in sys.argv for cmd in skip_auto_index_cmds)
126
+ )
100
127
  json_mode = any(arg in sys.argv for arg in ['-j', '--json'])
101
128
  if not should_skip:
102
129
  try:
@@ -128,6 +155,60 @@ def main(ctx, claude_home, codex_home):
128
155
  )
129
156
 
130
157
 
158
+ @main.command("resolve", cls=ResolveCommand)
159
+ @click.argument("query")
160
+ @click.option(
161
+ "--agent",
162
+ type=str,
163
+ metavar="claude|codex",
164
+ default="claude",
165
+ show_default=True,
166
+ )
167
+ @click.option(
168
+ "--home",
169
+ type=click.Path(readable=False),
170
+ help="Claude or Codex home directory to search.",
171
+ )
172
+ @click.option("--json", "json_output", is_flag=True, help="Force JSON output.")
173
+ @click.option(
174
+ "--pretty",
175
+ "pretty_output",
176
+ is_flag=True,
177
+ help="Force human-readable output.",
178
+ )
179
+ @click.pass_context
180
+ def resolve_session_cmd(
181
+ ctx: click.Context,
182
+ query: str,
183
+ agent: str,
184
+ home: str | None,
185
+ json_output: bool,
186
+ pretty_output: bool,
187
+ ) -> None:
188
+ """Resolve a session name, full ID, ID fragment, or filename fragment.
189
+
190
+ Ordered tiers (first non-empty wins): exact ID, exact name, ID
191
+ prefix, ID substring, name substring, session-file name
192
+ substring (e.g. a codex rollout timestamp like
193
+ 2026-03-25T14-50).
194
+ """
195
+ import sys
196
+
197
+ from claude_code_tools.resolve_session_render import run
198
+
199
+ if json_output and pretty_output:
200
+ click.echo(
201
+ '{"error":"invalid_format",'
202
+ '"detail":"Choose only one of --json or --pretty."}'
203
+ )
204
+ sys.exit(1)
205
+ if home is None:
206
+ home_key = "codex_home" if agent.casefold() == "codex" else "claude_home"
207
+ home = ctx.ensure_object(dict).get(home_key)
208
+ fmt = "json" if json_output else "pretty" if pretty_output else "auto"
209
+ sys.exit(run(query, agent.lower(), home, fmt))
210
+
211
+
131
212
  # Shared help text for find commands
132
213
  _FIND_OPTIONS_COMMON = """
133
214
  Options:
@@ -1370,6 +1451,104 @@ def move_session(session, new_project, agent):
1370
1451
  console.print(f" [cyan]{resume_cmd}[/cyan]")
1371
1452
 
1372
1453
 
1454
+ @main.command("port")
1455
+ @click.argument("session", required=True)
1456
+ @click.option("--claude-home", help="Path to Claude home directory")
1457
+ @click.option("--codex-home", help="Path to Codex home directory")
1458
+ @click.pass_context
1459
+ def port_session(
1460
+ ctx: click.Context,
1461
+ session: str,
1462
+ claude_home: "str | None",
1463
+ codex_home: "str | None",
1464
+ ) -> None:
1465
+ """Port a session to the OTHER agent (codex <-> claude).
1466
+
1467
+ Auto-detects which agent the session belongs to. Codex sessions
1468
+ are converted into a flattened, resumable Claude Code session;
1469
+ Claude sessions are converted into a flattened, resumable Codex
1470
+ rollout.
1471
+
1472
+ SESSION may be a full session id, a partial id (prefix, middle,
1473
+ or suffix fragment), a session name (set with /rename), a
1474
+ rollout filename fragment (e.g. 2026-03-25T14-50), or a session
1475
+ file path. Lookup is global across all projects in both homes.
1476
+
1477
+ \b
1478
+ Examples:
1479
+ aichat port 019f6d85-df3c-7c83-84f6-b97e73305fcf
1480
+ aichat port abc123 --claude-home ~/.claude-alt
1481
+ aichat port my-session-name
1482
+ aichat port 2026-03-25T14-50
1483
+ """
1484
+ import sys
1485
+
1486
+ from claude_code_tools.port_service import (
1487
+ PortSessionError,
1488
+ port_claude_session,
1489
+ port_codex_session,
1490
+ resolve_port_session,
1491
+ )
1492
+
1493
+ # Group-level --claude-home/--codex-home (given BEFORE the
1494
+ # subcommand) are stored on the root context; command-level
1495
+ # options take precedence.
1496
+ root_obj = ctx.find_root().obj or {}
1497
+ claude_home = claude_home or root_obj.get("claude_home")
1498
+ codex_home = codex_home or root_obj.get("codex_home")
1499
+
1500
+ try:
1501
+ resolved = resolve_port_session(
1502
+ session, claude_home=claude_home, codex_home=codex_home
1503
+ )
1504
+ except PortSessionError as e:
1505
+ print(f"Error: {e}", file=sys.stderr)
1506
+ sys.exit(1)
1507
+
1508
+ if resolved.agent == "claude":
1509
+ print("Detected source agent: claude — porting to Codex")
1510
+ try:
1511
+ result = port_claude_session(
1512
+ resolved.session_file, codex_home=codex_home
1513
+ )
1514
+ except PortSessionError as e:
1515
+ print(f"Error: {e}", file=sys.stderr)
1516
+ sys.exit(1)
1517
+
1518
+ print()
1519
+ print(f"New Codex session id: {result.new_session_id}")
1520
+ print(f"Output file: {result.output_file}")
1521
+ print(f"Session cwd: {result.cwd}")
1522
+ print()
1523
+ print("To resume:")
1524
+ print(f" {result.resume_hint}")
1525
+ print()
1526
+ print(
1527
+ "Tip: codex's /import can also import Claude sessions "
1528
+ "natively (interactive alternative)."
1529
+ )
1530
+ return
1531
+
1532
+ print("Detected source agent: codex — porting to Claude Code")
1533
+ try:
1534
+ result = port_codex_session(
1535
+ resolved.session_file, claude_home=claude_home
1536
+ )
1537
+ except PortSessionError as e:
1538
+ # Expected failure modes (missing/empty session, filesystem
1539
+ # or encoding errors) become a clean error, not a traceback.
1540
+ print(f"Error: {e}", file=sys.stderr)
1541
+ sys.exit(1)
1542
+
1543
+ print()
1544
+ print(f"New Claude session id: {result.new_session_id}")
1545
+ print(f"Output file: {result.output_file}")
1546
+ print(f"Session cwd: {result.cwd}")
1547
+ print()
1548
+ print("To resume:")
1549
+ print(f" {result.resume_hint}")
1550
+
1551
+
1373
1552
  @main.command("query")
1374
1553
  @click.argument("session", required=False)
1375
1554
  @click.argument("question", required=False)
@@ -14,11 +14,13 @@ import struct
14
14
  import subprocess
15
15
  import sys
16
16
  import time
17
- from contextlib import contextmanager
17
+ from contextlib import ExitStack, contextmanager
18
18
  from dataclasses import replace
19
19
  from pathlib import Path
20
20
  from typing import Iterator, Mapping, Sequence
21
21
 
22
+ from claude_code_tools import codex_server_retry
23
+ from claude_code_tools.codex_server_legacy import reauthenticate_legacy_state
22
24
  from claude_code_tools.codex_server_fingerprint import (
23
25
  PluginSnapshot as _PluginSnapshot,
24
26
  hash_plugin_tree as _hash_plugin_tree, # noqa: F401
@@ -78,6 +80,7 @@ from claude_code_tools.codex_server_reuse import (
78
80
  disconnect_refusal as _disconnect_refusal,
79
81
  helper_restart_reason as _reuse_restart_reason,
80
82
  require_external_compatible as _external_compatible,
83
+ same_server_launch as _same_server_launch,
81
84
  )
82
85
 
83
86
 
@@ -150,6 +153,22 @@ def _lifecycle_lock(paths: ServerPaths) -> Iterator[None]:
150
153
  os.close(fd)
151
154
 
152
155
 
156
+ @contextmanager
157
+ def _reserved_generation_lifecycle(
158
+ base: ServerPaths,
159
+ paths: ServerPaths,
160
+ generation: str,
161
+ ) -> Iterator[None]:
162
+ """Reserve capacity before releasing the base lifecycle lock."""
163
+ generation_lock = ExitStack()
164
+ with _lifecycle_lock(base):
165
+ require_generation_capacity(base, generation)
166
+ prepare_runtime(paths)
167
+ generation_lock.enter_context(_lifecycle_lock(paths))
168
+ with generation_lock:
169
+ yield
170
+
171
+
153
172
  def _resolve_codex(env: Mapping[str, str]) -> str:
154
173
  """Resolve a canonical executable used by both server and TUI."""
155
174
  configured = env.get("CCTOOLS_CODEX_BIN")
@@ -519,6 +538,7 @@ def get_status(env: Mapping[str, str] | None = None) -> ServerStatus:
519
538
  except StateFileError as exc:
520
539
  state = None
521
540
  state_error = str(exc)
541
+ state = reauthenticate_legacy_state(paths, state, _socket_peer_pid)
522
542
  probe = _probe_server(codex_path, child_env, paths)
523
543
  return _status_from(paths, state, probe, state_error)
524
544
 
@@ -557,33 +577,12 @@ def _require_unchanged_plugin_snapshot(
557
577
  ) -> None:
558
578
  """Reject a lifecycle decision made across a plugin input change."""
559
579
  if _plugin_configuration_snapshot(paths, codex_options) != expected:
560
- raise CodexServerError(
580
+ raise codex_server_retry.PluginSnapshotChangedError(
561
581
  "the Codex plugin or marketplace snapshot changed during "
562
582
  f"{operation}; retry after plugin updates finish"
563
583
  )
564
584
 
565
585
 
566
- def _same_server_launch(expected: OwnedServer, current: OwnedServer) -> bool:
567
- """Return whether two states name the same exact supervised launch."""
568
- return (
569
- current.pid,
570
- current.pgid,
571
- current.process_started_at,
572
- current.launch_token,
573
- current.worker_pid,
574
- current.worker_pgid,
575
- current.worker_started_at,
576
- ) == (
577
- expected.pid,
578
- expected.pgid,
579
- expected.process_started_at,
580
- expected.launch_token,
581
- expected.worker_pid,
582
- expected.worker_pgid,
583
- expected.worker_started_at,
584
- )
585
-
586
-
587
586
  def _certify_helper_boundary(
588
587
  expected: OwnedServer,
589
588
  active_env: Mapping[str, str],
@@ -724,6 +723,7 @@ def _wait_for_process_group_exit(
724
723
  )
725
724
 
726
725
 
726
+ @codex_server_retry.retry_plugin_snapshot_changes
727
727
  def ensure_server(
728
728
  env: Mapping[str, str] | None = None,
729
729
  *,
@@ -745,16 +745,15 @@ def ensure_server(
745
745
  codex_options,
746
746
  )
747
747
  paths = paths_for_generation(base_paths, generation)
748
- require_generation_capacity(base_paths, generation)
749
- child_env = _command_env(active_env, paths)
750
- child_env[CODEX_SERVER_OPTIONS_ENV] = json.dumps(
751
- list(codex_options),
752
- separators=(",", ":"),
753
- )
754
- with _lifecycle_lock(paths):
748
+ with _reserved_generation_lifecycle(base_paths, paths, generation):
749
+ child_env = _command_env(active_env, paths)
750
+ child_env[CODEX_SERVER_OPTIONS_ENV] = json.dumps(
751
+ list(codex_options),
752
+ separators=(",", ":"),
753
+ )
755
754
  locked_snapshot = _plugin_configuration_snapshot(paths, codex_options)
756
755
  if locked_snapshot.fingerprint != plugin_snapshot.fingerprint:
757
- raise CodexServerError(
756
+ raise codex_server_retry.PluginSnapshotChangedError(
758
757
  "the Codex plugin or marketplace snapshot changed during "
759
758
  "app-server generation selection; retry after updates finish"
760
759
  )
@@ -766,6 +765,7 @@ def ensure_server(
766
765
  except StateFileError as exc:
767
766
  state = None
768
767
  state_error = str(exc)
768
+ state = reauthenticate_legacy_state(paths, state, _socket_peer_pid)
769
769
 
770
770
  probe = _probe_server(codex_path, child_env, paths)
771
771
  status = _status_from(paths, state, probe, state_error)
@@ -938,6 +938,7 @@ def _stop_server_at(
938
938
  ) from exc
939
939
  _quarantine_invalid_state(paths)
940
940
  state = None
941
+ state = reauthenticate_legacy_state(paths, state, _socket_peer_pid)
941
942
 
942
943
  if state is not None and _state_is_owned(state):
943
944
  if not allow_disconnect: