claude-code-tools 1.28.0__tar.gz → 1.29.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.
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/PKG-INFO +1 -1
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/__init__.py +1 -1
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/config.py +5 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/http_frontend.py +69 -2
- claude_code_tools-1.29.0/claude_code_tools/agent_tunnel/turn_log.py +164 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/docs/agent-tunnel-spec.md +3 -1
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/pyproject.toml +2 -2
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/.gitignore +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/LICENSE +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/README.md +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/action_rpc.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/__init__.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/backends.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/cli.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/convert.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/discord_bot.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/locking.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/mattermost_bot.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/paths.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/readonly.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/registry.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/relay.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/serve.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/session.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/store.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/tmux.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/trust.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/aichat.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/amux/__init__.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/amux/__main__.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/amux/activity.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/amux/cache.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/amux/cli.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/amux/detect.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/amux/filters.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/amux/model.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/amux/render.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/amux/scan.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/claude_continue.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/cli_passthrough.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_app_server_rpc.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_continue.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server_callback.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server_cli.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server_fingerprint.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server_generation.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server_legacy.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server_models.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server_process.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server_reservation.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server_resume.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server_retry.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server_reuse.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server_state.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server_supervisor.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server_wait.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server_worker.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/config.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/csv2gsheet.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/delete_session.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/dotenv_vault.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/env_safe.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/export_all.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/export_claude_session.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/export_codex_session.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/export_session.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/find_claude_session.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/find_codex_session.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/find_original_session.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/find_session.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/find_trimmed_sessions.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/fix_session.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/gdoc2docx.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/gdoc2md.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/github_wake.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/github_watch_daemon.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/github_watch_store.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/gsheet2csv.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/helper_sessions.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/issue_reply_delivery.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/legacy_session_wrappers.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/md2gdoc.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/move_account.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/msg/__init__.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/msg/cli.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/msg/hooks.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/msg/migrations.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/msg/models.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/msg/prompt_detect.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/msg/store.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/msg/watcher.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/node_menu_ui.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/port_claude_noise.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/port_claude_to_codex.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/port_codex_flatten.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/port_codex_to_claude.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/port_render.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/port_service.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/resolve_session.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/resolve_session_names.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/resolve_session_render.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/search_index.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/session_cli_resolution.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/session_lineage.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/session_menu.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/session_menu_cli.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/session_resolution.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/session_selection.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/session_utils.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/smart_trim.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/smart_trim_core.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/tmux_cli_controller.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/tmux_execution_helpers.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/tmux_remote_controller.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/trim_in_place.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/trim_session.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/trim_session_claude.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/trim_session_codex.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/workflow_cli.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/workflow_cli_contract.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/workflow_cli_formatting.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/workflow_cli_identity_policy.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/workflow_cli_manifest.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/workflow_cli_projection.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/workflow_cli_rendering.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/workflow_cli_snapshots.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/workflow_cli_store_backends.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/workflow_processes.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/workflow_runs.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/workflow_store_io.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/workflow_validation.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/docs/claude-code-tmux-tutorials.md +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/docs/dot-zshrc.md +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/docs/find-claude-session.md +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/docs/intercom-spec.md +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/docs/lmsh.md +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/docs/local-llm-setup.md +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/docs/tmux-cli-instructions.md +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/docs/vault-documentation.md +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/hatch_build.py +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/node_ui/action_config.js +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/node_ui/menu.js +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/node_ui/package-lock.json +0 -0
- {claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/node_ui/package.json +0 -0
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/config.py
RENAMED
|
@@ -176,6 +176,10 @@ class HttpConfig:
|
|
|
176
176
|
token_file: str = ""
|
|
177
177
|
# Name used in the fork's persona for questions arriving this way.
|
|
178
178
|
platform: str = "the web"
|
|
179
|
+
# Append-only JSON-lines log of answered turns, readable at GET /turns.
|
|
180
|
+
# Empty = off. Relative paths sit beside the state file. The sender is
|
|
181
|
+
# never written to it.
|
|
182
|
+
turn_log: str = ""
|
|
179
183
|
|
|
180
184
|
|
|
181
185
|
@dataclass
|
|
@@ -423,6 +427,7 @@ unset_api_key = true
|
|
|
423
427
|
# port = 8766
|
|
424
428
|
# token_file = "~/.config/agent-tunnel/http-token"
|
|
425
429
|
# platform = "the web"
|
|
430
|
+
# turn_log = "http-turns.jsonl" # answered turns, no sender; GET /turns reads it
|
|
426
431
|
|
|
427
432
|
[limits]
|
|
428
433
|
max_concurrent = 2
|
|
@@ -9,7 +9,9 @@ refresh and logging are shared with Discord and Mattermost.
|
|
|
9
9
|
Routes (``[http]`` in the config; off unless ``port`` is set):
|
|
10
10
|
|
|
11
11
|
GET /health
|
|
12
|
-
POST /ask {"handle": ..., "question": ..., "thread": ..., "sender":
|
|
12
|
+
POST /ask {"handle": ..., "question": ..., "thread": ..., "sender": ...,
|
|
13
|
+
"metadata": {...}}
|
|
14
|
+
GET /turns ?handle=...&limit=...&meta.<key>=<value>
|
|
13
15
|
|
|
14
16
|
Every route requires ``X-Ask-Token: <contents of http.token_file>``.
|
|
15
17
|
``thread`` is any caller-chosen id; questions with the same handle and
|
|
@@ -20,6 +22,11 @@ whatever access the handle was shared with, because the caller picks the
|
|
|
20
22
|
handle. The response says whether a turn ran (``ran``) separately from what
|
|
21
23
|
it produced, and a failed turn is an HTTP 502 carrying the relay's own
|
|
22
24
|
error text, never an empty answer.
|
|
25
|
+
|
|
26
|
+
With ``[http] turn_log`` set, each answered turn is appended to that file
|
|
27
|
+
with the caller's flat ``metadata`` (a docs site sends page and heading) and
|
|
28
|
+
without the sender, and ``GET /turns`` returns the matching turns newest
|
|
29
|
+
first. See :mod:`.turn_log`.
|
|
23
30
|
"""
|
|
24
31
|
|
|
25
32
|
from __future__ import annotations
|
|
@@ -37,6 +44,7 @@ from aiohttp import web
|
|
|
37
44
|
from .readonly import HTTP_THREAD_PREFIX
|
|
38
45
|
from .config import TunnelConfig
|
|
39
46
|
from .relay import QUEUE_NOTICE, Relay
|
|
47
|
+
from .turn_log import MAX_LIMIT, TurnLog, resolve_log_path, validate_metadata
|
|
40
48
|
|
|
41
49
|
logger = logging.getLogger("agent_tunnel.http")
|
|
42
50
|
|
|
@@ -148,6 +156,8 @@ async def _turn(
|
|
|
148
156
|
thread: str,
|
|
149
157
|
question: str,
|
|
150
158
|
sender: str,
|
|
159
|
+
metadata: dict[str, str],
|
|
160
|
+
turn_log: Optional[TurnLog],
|
|
151
161
|
) -> web.Response:
|
|
152
162
|
"""Bind if new, run one relay turn, and shape the JSON reply.
|
|
153
163
|
|
|
@@ -166,6 +176,16 @@ async def _turn(
|
|
|
166
176
|
error = problem or "the turn produced no answer"
|
|
167
177
|
return _json(502, {"ran": False, "error": error, "thread": thread})
|
|
168
178
|
record = relay.store.get(thread_key)
|
|
179
|
+
logged = False
|
|
180
|
+
if turn_log is not None:
|
|
181
|
+
try:
|
|
182
|
+
await turn_log.append(handle, thread, question, dest.text, metadata)
|
|
183
|
+
logged = True
|
|
184
|
+
except (OSError, ValueError):
|
|
185
|
+
# The reader still gets the answer; the response says it was not
|
|
186
|
+
# logged, and the daemon log says why.
|
|
187
|
+
logger.exception("Could not append to turn log %s", turn_log.path)
|
|
188
|
+
dest.notices.append("this turn could not be written to the turn log")
|
|
169
189
|
return _json(
|
|
170
190
|
200,
|
|
171
191
|
{
|
|
@@ -176,6 +196,7 @@ async def _turn(
|
|
|
176
196
|
"fork_session_id": record.fork_session_id if record else "",
|
|
177
197
|
"files": [p.name for p in dest.files],
|
|
178
198
|
"notices": dest.notices,
|
|
199
|
+
"logged": logged,
|
|
179
200
|
},
|
|
180
201
|
)
|
|
181
202
|
|
|
@@ -183,6 +204,8 @@ async def _turn(
|
|
|
183
204
|
def make_app(cfg: TunnelConfig, relay: Relay) -> web.Application:
|
|
184
205
|
"""The aiohttp application; separated from serving so tests can drive it."""
|
|
185
206
|
token = resolve_http_token(cfg).encode("utf-8")
|
|
207
|
+
log_path = resolve_log_path(cfg.http.turn_log, cfg.state_path)
|
|
208
|
+
turn_log = TurnLog(log_path) if log_path else None
|
|
186
209
|
|
|
187
210
|
@web.middleware
|
|
188
211
|
async def require_token(request: web.Request, handler: Any) -> web.Response:
|
|
@@ -225,6 +248,10 @@ def make_app(cfg: TunnelConfig, relay: Relay) -> web.Application:
|
|
|
225
248
|
return _json(
|
|
226
249
|
400, {"ran": False, "error": "thread must match [A-Za-z0-9_.:-]{1,80}"}
|
|
227
250
|
)
|
|
251
|
+
metadata, err = validate_metadata(body.get("metadata"))
|
|
252
|
+
if err:
|
|
253
|
+
status = 413 if "longer" in err else 400
|
|
254
|
+
return _json(status, {"ran": False, "error": err})
|
|
228
255
|
# The same per-user cooldown the chat front-ends apply, keyed by the
|
|
229
256
|
# sender the caller vouches for, else by the caller's address.
|
|
230
257
|
caller = sender or (request.remote or "unknown")
|
|
@@ -236,7 +263,17 @@ def make_app(cfg: TunnelConfig, relay: Relay) -> web.Application:
|
|
|
236
263
|
)
|
|
237
264
|
thread_key = f"{HTTP_THREAD_PREFIX}{handle}:{thread}"
|
|
238
265
|
try:
|
|
239
|
-
return await _turn(
|
|
266
|
+
return await _turn(
|
|
267
|
+
cfg,
|
|
268
|
+
relay,
|
|
269
|
+
thread_key,
|
|
270
|
+
handle,
|
|
271
|
+
thread,
|
|
272
|
+
question,
|
|
273
|
+
sender,
|
|
274
|
+
metadata,
|
|
275
|
+
turn_log,
|
|
276
|
+
)
|
|
240
277
|
except Exception: # noqa: BLE001 - keep the JSON contract on any failure
|
|
241
278
|
logger.exception("HTTP turn failed [%s]", thread_key)
|
|
242
279
|
return _json(
|
|
@@ -248,9 +285,39 @@ def make_app(cfg: TunnelConfig, relay: Relay) -> web.Application:
|
|
|
248
285
|
},
|
|
249
286
|
)
|
|
250
287
|
|
|
288
|
+
async def turns(request: web.Request) -> web.Response:
|
|
289
|
+
if turn_log is None:
|
|
290
|
+
return _json(404, {"ran": False, "error": "no [http] turn_log configured"})
|
|
291
|
+
query = request.query
|
|
292
|
+
handle = query.get("handle", "").strip().lower()
|
|
293
|
+
if not handle:
|
|
294
|
+
return _json(400, {"ran": False, "error": "handle is required"})
|
|
295
|
+
try:
|
|
296
|
+
limit = int(query.get("limit", "50"))
|
|
297
|
+
except ValueError:
|
|
298
|
+
return _json(400, {"ran": False, "error": "limit must be an integer"})
|
|
299
|
+
if not 1 <= limit <= MAX_LIMIT:
|
|
300
|
+
return _json(
|
|
301
|
+
400, {"ran": False, "error": f"limit must be 1 to {MAX_LIMIT}"}
|
|
302
|
+
)
|
|
303
|
+
filters = {
|
|
304
|
+
key[len("meta.") :]: value
|
|
305
|
+
for key, value in query.items()
|
|
306
|
+
if key.startswith("meta.")
|
|
307
|
+
}
|
|
308
|
+
try:
|
|
309
|
+
found, skipped = await asyncio.to_thread(
|
|
310
|
+
turn_log.read, handle, filters, limit
|
|
311
|
+
)
|
|
312
|
+
except OSError:
|
|
313
|
+
logger.exception("Could not read turn log %s", turn_log.path)
|
|
314
|
+
return _json(502, {"ran": False, "error": "could not read the turn log"})
|
|
315
|
+
return _json(200, {"ran": True, "turns": found, "skipped": skipped})
|
|
316
|
+
|
|
251
317
|
app = web.Application(client_max_size=256 * 1024, middlewares=[require_token])
|
|
252
318
|
app.router.add_get("/health", health)
|
|
253
319
|
app.router.add_post("/ask", ask)
|
|
320
|
+
app.router.add_get("/turns", turns)
|
|
254
321
|
return app
|
|
255
322
|
|
|
256
323
|
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""An append-only log of HTTP turns, and the reader behind ``GET /turns``.
|
|
2
|
+
|
|
3
|
+
The daemon's thread store keeps only a trimmed recap of each conversation,
|
|
4
|
+
so it cannot answer "what was asked about this page last month". When
|
|
5
|
+
``[http] turn_log`` names a file, every answered HTTP turn is appended to it
|
|
6
|
+
as one JSON line: when, which handle and thread, the question, the answer,
|
|
7
|
+
and whatever flat ``metadata`` the caller attached (a docs site sends the
|
|
8
|
+
page and heading). The sender is never written. The fork is still told who
|
|
9
|
+
is asking and may repeat it in its answer, so a caller that shows the log to
|
|
10
|
+
other people sends a pseudonym as the sender rather than a name or email.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import json
|
|
17
|
+
import logging
|
|
18
|
+
import os
|
|
19
|
+
import re
|
|
20
|
+
from collections import deque
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from datetime import datetime, timezone
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Any, Mapping, Optional
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger("agent_tunnel.turn_log")
|
|
27
|
+
|
|
28
|
+
META_KEY_RE = re.compile(r"^[A-Za-z0-9_.-]{1,40}$")
|
|
29
|
+
MAX_META_KEYS = 20
|
|
30
|
+
MAX_META_VALUE = 4000
|
|
31
|
+
MAX_LIMIT = 500
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def validate_metadata(value: Any) -> tuple[dict[str, str], str]:
|
|
35
|
+
"""A caller's ``metadata`` as a flat string map, or why it is not one.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
value: The request's ``metadata`` field; absent or null is empty.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
``(metadata, "")`` on success, else ``({}, error)``. An error that
|
|
42
|
+
mentions "longer" is an over-limit value (the caller answers 413).
|
|
43
|
+
"""
|
|
44
|
+
if value is None:
|
|
45
|
+
return {}, ""
|
|
46
|
+
if not isinstance(value, dict):
|
|
47
|
+
return {}, "metadata must be a JSON object"
|
|
48
|
+
if len(value) > MAX_META_KEYS:
|
|
49
|
+
return {}, f"metadata has more than {MAX_META_KEYS} keys"
|
|
50
|
+
meta: dict[str, str] = {}
|
|
51
|
+
for key, item in value.items():
|
|
52
|
+
if not META_KEY_RE.match(key):
|
|
53
|
+
return {}, "metadata keys must match [A-Za-z0-9_.-]{1,40}"
|
|
54
|
+
if not isinstance(item, str):
|
|
55
|
+
return {}, f"metadata.{key} must be a string"
|
|
56
|
+
if len(item) > MAX_META_VALUE:
|
|
57
|
+
return {}, f"metadata.{key} is longer than {MAX_META_VALUE} characters"
|
|
58
|
+
meta[key] = item
|
|
59
|
+
return meta, ""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def resolve_log_path(configured: str, state_path: Path) -> Optional[Path]:
|
|
63
|
+
"""The turn log's path, or None when logging is off.
|
|
64
|
+
|
|
65
|
+
A relative path sits beside the daemon's state file, like its other
|
|
66
|
+
runtime files.
|
|
67
|
+
"""
|
|
68
|
+
if not configured:
|
|
69
|
+
return None
|
|
70
|
+
path = Path(configured).expanduser()
|
|
71
|
+
return path if path.is_absolute() else state_path.parent / path
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass
|
|
75
|
+
class TurnLog:
|
|
76
|
+
"""Append answered turns to a JSON-lines file and read them back."""
|
|
77
|
+
|
|
78
|
+
path: Path
|
|
79
|
+
_lock: asyncio.Lock = field(default_factory=asyncio.Lock)
|
|
80
|
+
|
|
81
|
+
async def append(
|
|
82
|
+
self,
|
|
83
|
+
handle: str,
|
|
84
|
+
thread: str,
|
|
85
|
+
question: str,
|
|
86
|
+
answer: str,
|
|
87
|
+
metadata: Mapping[str, str],
|
|
88
|
+
) -> None:
|
|
89
|
+
"""Write one turn durably; raises OSError if it could not be written."""
|
|
90
|
+
record = {
|
|
91
|
+
"ts": datetime.now(timezone.utc).isoformat(timespec="seconds"),
|
|
92
|
+
"handle": handle,
|
|
93
|
+
"thread": thread,
|
|
94
|
+
"question": question,
|
|
95
|
+
"answer": answer,
|
|
96
|
+
"metadata": dict(metadata),
|
|
97
|
+
}
|
|
98
|
+
# ASCII escapes: a lone surrogate from JSON input is a valid str but
|
|
99
|
+
# cannot be encoded as UTF-8, and must not cost the reader an answer.
|
|
100
|
+
line = json.dumps(record) + "\n"
|
|
101
|
+
async with self._lock:
|
|
102
|
+
await asyncio.to_thread(self._write, line)
|
|
103
|
+
|
|
104
|
+
def _write(self, line: str) -> None:
|
|
105
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
106
|
+
# Owner-only, like the thread store: the log holds every question
|
|
107
|
+
# and answer.
|
|
108
|
+
fd = os.open(self.path, os.O_RDWR | os.O_CREAT | os.O_APPEND, 0o600)
|
|
109
|
+
with os.fdopen(fd, "a+b") as fh:
|
|
110
|
+
if hasattr(os, "fchmod"): # POSIX only; also narrows an older file
|
|
111
|
+
os.fchmod(fh.fileno(), 0o600)
|
|
112
|
+
# A failed earlier write can leave a record without its newline;
|
|
113
|
+
# end it first so this record stays on a line of its own.
|
|
114
|
+
end = fh.seek(0, os.SEEK_END)
|
|
115
|
+
if end:
|
|
116
|
+
fh.seek(end - 1)
|
|
117
|
+
if fh.read(1) != b"\n":
|
|
118
|
+
fh.write(b"\n")
|
|
119
|
+
fh.write(line.encode("utf-8"))
|
|
120
|
+
fh.flush()
|
|
121
|
+
os.fsync(fh.fileno())
|
|
122
|
+
|
|
123
|
+
def read(
|
|
124
|
+
self, handle: str, meta_filters: Mapping[str, str], limit: int
|
|
125
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
126
|
+
"""Matching turns, newest first, and how many lines were unreadable.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
handle: Only turns of this handle.
|
|
130
|
+
meta_filters: Each key must be present in a turn's metadata with
|
|
131
|
+
exactly this value.
|
|
132
|
+
limit: At most this many turns, the most recent ones.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
``(turns, skipped)``; a missing file is no turns, not an error.
|
|
136
|
+
"""
|
|
137
|
+
if not self.path.exists():
|
|
138
|
+
return [], 0
|
|
139
|
+
# Only the latest `limit` matches are kept while scanning, so memory
|
|
140
|
+
# stays bounded however long the log grows.
|
|
141
|
+
matches: deque[dict[str, Any]] = deque(maxlen=limit)
|
|
142
|
+
skipped = 0
|
|
143
|
+
# Bytes, decoded line by line, so one damaged line is skipped and
|
|
144
|
+
# counted rather than hiding every record after it.
|
|
145
|
+
with self.path.open("rb") as fh:
|
|
146
|
+
for raw in fh:
|
|
147
|
+
if not raw.strip():
|
|
148
|
+
continue
|
|
149
|
+
try:
|
|
150
|
+
turn = json.loads(raw.decode("utf-8"))
|
|
151
|
+
except (UnicodeDecodeError, json.JSONDecodeError):
|
|
152
|
+
skipped += 1
|
|
153
|
+
continue
|
|
154
|
+
meta = turn.get("metadata", {}) if isinstance(turn, dict) else None
|
|
155
|
+
if not isinstance(meta, dict):
|
|
156
|
+
skipped += 1 # valid JSON, but not a turn record
|
|
157
|
+
continue
|
|
158
|
+
if turn.get("handle") != handle:
|
|
159
|
+
continue
|
|
160
|
+
if all(meta.get(k) == v for k, v in meta_filters.items()):
|
|
161
|
+
matches.append(turn)
|
|
162
|
+
if skipped:
|
|
163
|
+
logger.warning("Turn log %s: %d unreadable line(s)", self.path, skipped)
|
|
164
|
+
return list(reversed(matches)), skipped
|
|
@@ -58,7 +58,9 @@ safe.
|
|
|
58
58
|
forks are read-only whatever the handle's access, the configured tool
|
|
59
59
|
lists, the backends' extra args, the configured permission mode, the
|
|
60
60
|
settings' MCP servers, or `allow_skip_permissions`, since the caller picks
|
|
61
|
-
the handle.
|
|
61
|
+
the handle. With `[http] turn_log` set, each answered turn is appended to
|
|
62
|
+
a JSON-lines log with the caller's flat `metadata` and never the sender;
|
|
63
|
+
`GET /turns` reads it back newest first, filtered by `meta.<key>=<value>`.
|
|
62
64
|
Fields must be strings within their limits (over-limit is a 413); the
|
|
63
65
|
per-user cooldown applies per `sender` (a 429 inside it). The
|
|
64
66
|
response separates whether a turn ran from what it produced: a failed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "claude-code-tools"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.29.0"
|
|
4
4
|
description = "Collection of tools for working with Claude Code"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -113,7 +113,7 @@ markers = [
|
|
|
113
113
|
|
|
114
114
|
[tool.commitizen]
|
|
115
115
|
name = "cz_conventional_commits"
|
|
116
|
-
version = "1.
|
|
116
|
+
version = "1.29.0"
|
|
117
117
|
tag_format = "v$version"
|
|
118
118
|
version_files = [
|
|
119
119
|
"pyproject.toml:version",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/__init__.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/backends.py
RENAMED
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/convert.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/discord_bot.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/locking.py
RENAMED
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/paths.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/readonly.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/registry.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/relay.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/serve.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/session.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/store.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/tmux.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/agent_tunnel/trust.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_app_server_rpc.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server_callback.py
RENAMED
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server_fingerprint.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server_generation.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server_legacy.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server_models.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server_process.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server_reservation.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server_resume.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server_retry.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server_reuse.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server_state.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server_supervisor.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server_wait.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/codex_server_worker.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/export_claude_session.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/export_codex_session.py
RENAMED
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/find_claude_session.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/find_codex_session.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/find_original_session.py
RENAMED
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/find_trimmed_sessions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/github_watch_daemon.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/github_watch_store.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/issue_reply_delivery.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/legacy_session_wrappers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/msg/prompt_detect.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/port_claude_noise.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/port_claude_to_codex.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/port_codex_flatten.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/port_codex_to_claude.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/resolve_session_names.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/resolve_session_render.py
RENAMED
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/session_cli_resolution.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/session_resolution.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/session_selection.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/tmux_cli_controller.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/tmux_execution_helpers.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/tmux_remote_controller.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/trim_session_claude.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/trim_session_codex.py
RENAMED
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/workflow_cli_contract.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/workflow_cli_formatting.py
RENAMED
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/workflow_cli_manifest.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/workflow_cli_projection.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/workflow_cli_rendering.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/workflow_cli_snapshots.py
RENAMED
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/workflow_processes.py
RENAMED
|
File without changes
|
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/workflow_store_io.py
RENAMED
|
File without changes
|
{claude_code_tools-1.28.0 → claude_code_tools-1.29.0}/claude_code_tools/workflow_validation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|