soothe-cli 0.3.6__tar.gz → 0.3.8__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.
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/PKG-INFO +4 -4
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/README.md +3 -3
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/cli/commands/config_cmd.py +1 -1
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/cli/commands/thread_cmd.py +23 -282
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/cli/main.py +35 -132
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/cli/stream/formatter.py +7 -5
- soothe_cli-0.3.8/src/soothe_cli/loop_commands.py +869 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/app.py +170 -70
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/command_registry.py +2 -2
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/config.py +2 -2
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/model_config.py +66 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/sessions.py +79 -108
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/textual_adapter.py +7 -7
- soothe_cli-0.3.8/src/soothe_cli/tui/widgets/loop_selector.py +1550 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/widgets/messages.py +10 -42
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/widgets/thread_selector.py +5 -6
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/widgets/welcome.py +12 -10
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/.gitignore +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/pyproject.toml +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/__init__.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/cli/__init__.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/cli/commands/__init__.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/cli/commands/autopilot_cmd.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/cli/commands/run_cmd.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/cli/commands/status_cmd.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/cli/commands/subagent_names.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/cli/execution/__init__.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/cli/execution/daemon.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/cli/execution/headless.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/cli/execution/launcher.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/cli/renderer.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/cli/stream/__init__.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/cli/stream/context.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/cli/stream/display_line.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/cli/stream/pipeline.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/cli/utils.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/config/__init__.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/config/cli_config.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/plan/__init__.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/plan/rich_tree.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/shared/__init__.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/shared/command_router.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/shared/config_loader.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/shared/display_policy.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/shared/essential_events.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/shared/event_processor.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/shared/message_processing.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/shared/presentation_engine.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/shared/processor_state.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/shared/renderer_protocol.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/shared/rendering.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/shared/slash_commands.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/shared/subagent_routing.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/shared/suppression_state.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/shared/tool_call_resolution.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/shared/tool_card_payload.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/shared/tool_formatters/__init__.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/shared/tool_formatters/base.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/shared/tool_formatters/execution.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/shared/tool_formatters/fallback.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/shared/tool_formatters/file_ops.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/shared/tool_formatters/goal_formatter.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/shared/tool_formatters/media.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/shared/tool_formatters/structured.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/shared/tool_formatters/web.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/shared/tool_message_format.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/shared/tool_output_formatter.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/shared/tui_trace_log.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/__init__.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/_ask_user_types.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/_cli_context.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/_env_vars.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/_session_stats.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/_version.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/app.tcss +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/daemon_session.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/file_ops.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/formatting.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/hooks.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/input.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/media_utils.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/message_display_filter.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/output.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/project_utils.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/skills/__init__.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/skills/invocation.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/skills/load.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/theme.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/tool_display.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/unicode_security.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/update_check.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/widgets/__init__.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/widgets/_links.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/widgets/approval.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/widgets/ask_user.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/widgets/autocomplete.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/widgets/autopilot_dashboard.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/widgets/autopilot_screen.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/widgets/chat_input.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/widgets/clipboard.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/widgets/diff.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/widgets/editor.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/widgets/history.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/widgets/loading.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/widgets/mcp_viewer.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/widgets/message_store.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/widgets/model_selector.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/widgets/notification_settings.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/widgets/status.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/widgets/theme_selector.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/widgets/tool_renderers.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/widgets/tool_widgets.py +0 -0
- {soothe_cli-0.3.6 → soothe_cli-0.3.8}/src/soothe_cli/tui/widgets/tools.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: soothe-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.8
|
|
4
4
|
Summary: Soothe CLI client - communicates with daemon via WebSocket
|
|
5
5
|
Project-URL: Homepage, https://github.com/OpenSoothe/soothe
|
|
6
6
|
Project-URL: Documentation, https://soothe.readthedocs.io
|
|
@@ -54,9 +54,9 @@ soothe
|
|
|
54
54
|
# Headless single-prompt mode
|
|
55
55
|
soothe -p "Research AI advances"
|
|
56
56
|
|
|
57
|
-
#
|
|
58
|
-
soothe
|
|
59
|
-
soothe
|
|
57
|
+
# Loop management
|
|
58
|
+
soothe loop list
|
|
59
|
+
soothe loop continue loop_abc123
|
|
60
60
|
|
|
61
61
|
# Configuration
|
|
62
62
|
soothe config show
|
|
@@ -19,9 +19,9 @@ soothe
|
|
|
19
19
|
# Headless single-prompt mode
|
|
20
20
|
soothe -p "Research AI advances"
|
|
21
21
|
|
|
22
|
-
#
|
|
23
|
-
soothe
|
|
24
|
-
soothe
|
|
22
|
+
# Loop management
|
|
23
|
+
soothe loop list
|
|
24
|
+
soothe loop continue loop_abc123
|
|
25
25
|
|
|
26
26
|
# Configuration
|
|
27
27
|
soothe config show
|
|
@@ -189,7 +189,7 @@ def config_init(
|
|
|
189
189
|
target.write_text("# Soothe configuration\n# See docs/user_guide.md for options\n")
|
|
190
190
|
typer.echo(f"Created minimal {target}")
|
|
191
191
|
|
|
192
|
-
for subdir in ("
|
|
192
|
+
for subdir in ("generated_agents", "logs", "data"):
|
|
193
193
|
(home / subdir).mkdir(parents=True, exist_ok=True)
|
|
194
194
|
|
|
195
195
|
# Migrate runtime data files from root to data/ subdirectory
|
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
"""Thread commands for Soothe CLI.
|
|
1
|
+
"""Thread commands for Soothe CLI (read-only diagnostics).
|
|
2
2
|
|
|
3
3
|
All thread operations communicate exclusively via daemon WebSocket RPC.
|
|
4
4
|
The daemon must be running for thread commands to work.
|
|
5
|
+
|
|
6
|
+
Note: Thread commands are read-only diagnostics per RFC-503 (Loop-First UX).
|
|
7
|
+
Users manage loops (primary entity), not threads (internal execution contexts).
|
|
8
|
+
For thread lifecycle management, use loop commands: soothe loop <subcommand>
|
|
5
9
|
"""
|
|
6
10
|
|
|
7
11
|
import asyncio
|
|
12
|
+
import json
|
|
8
13
|
import sys
|
|
9
14
|
from pathlib import Path
|
|
10
15
|
from typing import Annotated, Any
|
|
11
16
|
|
|
12
17
|
import typer
|
|
13
18
|
from soothe_sdk.client import WebSocketClient, is_daemon_live, websocket_url_from_config
|
|
14
|
-
from soothe_sdk.client.config import SOOTHE_HOME
|
|
15
|
-
from soothe_sdk.utils.logging import resolve_cli_log_level
|
|
16
19
|
|
|
17
20
|
from soothe_cli.shared import load_config
|
|
18
21
|
|
|
@@ -92,7 +95,7 @@ def _echo_thread_table(rows: list[dict[str, object]]) -> None:
|
|
|
92
95
|
typer.echo("No threads.")
|
|
93
96
|
return
|
|
94
97
|
typer.echo(f"{'ID':<20} {'Status':<10} {'Created':<19} {'Last Message':<19} {'Topic':<30}")
|
|
95
|
-
typer.echo("
|
|
98
|
+
typer.echo("─" * 104)
|
|
96
99
|
for raw in rows:
|
|
97
100
|
tid_raw = str(raw.get("thread_id", ""))
|
|
98
101
|
tid = (
|
|
@@ -127,13 +130,15 @@ def thread_list(
|
|
|
127
130
|
typer.Option("--limit", "-l", help="Limit number of threads shown."),
|
|
128
131
|
] = None,
|
|
129
132
|
) -> None:
|
|
130
|
-
"""List all agent threads.
|
|
133
|
+
"""List all agent threads (read-only diagnostics).
|
|
131
134
|
|
|
132
135
|
Examples:
|
|
133
136
|
soothe thread list
|
|
134
137
|
soothe thread list --status active
|
|
135
138
|
soothe thread list --limit 10
|
|
136
139
|
soothe thread list --limit 20 --status idle
|
|
140
|
+
|
|
141
|
+
Note: For thread lifecycle management, use loop commands (RFC-503).
|
|
137
142
|
"""
|
|
138
143
|
cfg = load_config(config)
|
|
139
144
|
ws_url = websocket_url_from_config(cfg)
|
|
@@ -181,105 +186,6 @@ def thread_list(
|
|
|
181
186
|
asyncio.run(_list())
|
|
182
187
|
|
|
183
188
|
|
|
184
|
-
def thread_continue(
|
|
185
|
-
thread_id: Annotated[
|
|
186
|
-
str | None,
|
|
187
|
-
typer.Argument(help="Thread ID to continue. Omit to continue last active thread."),
|
|
188
|
-
] = None,
|
|
189
|
-
config: Annotated[
|
|
190
|
-
str | None,
|
|
191
|
-
typer.Option("--config", "-c", help="Path to configuration file."),
|
|
192
|
-
] = None,
|
|
193
|
-
*,
|
|
194
|
-
new: Annotated[
|
|
195
|
-
bool,
|
|
196
|
-
typer.Option("--new", help="Create a new thread instead of continuing."),
|
|
197
|
-
] = False,
|
|
198
|
-
) -> None:
|
|
199
|
-
"""Continue a conversation thread in the TUI.
|
|
200
|
-
|
|
201
|
-
Requires a running daemon. Start daemon with 'soothe daemon start' first.
|
|
202
|
-
|
|
203
|
-
Examples:
|
|
204
|
-
soothe thread continue abc123
|
|
205
|
-
soothe thread continue --new
|
|
206
|
-
soothe thread continue
|
|
207
|
-
"""
|
|
208
|
-
from soothe_cli.cli.execution import run_tui
|
|
209
|
-
from soothe_cli.shared import setup_logging
|
|
210
|
-
|
|
211
|
-
cfg = load_config(config)
|
|
212
|
-
log_level = resolve_cli_log_level(
|
|
213
|
-
cfg.logging.verbosity,
|
|
214
|
-
logging_level=cfg.logging.level,
|
|
215
|
-
)
|
|
216
|
-
log_file = Path(SOOTHE_HOME) / "logs" / "soothe-cli.log"
|
|
217
|
-
setup_logging(log_level, log_file=log_file)
|
|
218
|
-
ws_url = websocket_url_from_config(cfg)
|
|
219
|
-
_require_daemon(ws_url)
|
|
220
|
-
|
|
221
|
-
# Handle --new flag
|
|
222
|
-
if new:
|
|
223
|
-
thread_id = None
|
|
224
|
-
elif not thread_id:
|
|
225
|
-
# Find the most recently updated active thread through the daemon
|
|
226
|
-
async def get_last_thread_via_daemon() -> str | None:
|
|
227
|
-
client = WebSocketClient(url=ws_url)
|
|
228
|
-
try:
|
|
229
|
-
await client.connect()
|
|
230
|
-
await client.send_thread_list()
|
|
231
|
-
while True:
|
|
232
|
-
event = await client.read_event()
|
|
233
|
-
if not event:
|
|
234
|
-
break
|
|
235
|
-
if event.get("type") != "thread_list_response":
|
|
236
|
-
continue
|
|
237
|
-
threads = event.get("threads", [])
|
|
238
|
-
active_threads = [t for t in threads if t.get("status") in ("active", "idle")]
|
|
239
|
-
if not active_threads:
|
|
240
|
-
typer.echo("No active threads found.", err=True)
|
|
241
|
-
sys.exit(1)
|
|
242
|
-
active_threads.sort(key=lambda x: x.get("updated_at", ""), reverse=True)
|
|
243
|
-
return active_threads[0].get("thread_id")
|
|
244
|
-
finally:
|
|
245
|
-
await client.close()
|
|
246
|
-
|
|
247
|
-
typer.echo("No active threads found.", err=True)
|
|
248
|
-
sys.exit(1)
|
|
249
|
-
|
|
250
|
-
thread_id = asyncio.run(get_last_thread_via_daemon())
|
|
251
|
-
|
|
252
|
-
run_tui(cfg, thread_id=thread_id)
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
def thread_archive(
|
|
256
|
-
thread_id: Annotated[str, typer.Argument(help="Thread ID to archive.")],
|
|
257
|
-
config: Annotated[
|
|
258
|
-
str | None,
|
|
259
|
-
typer.Option("--config", "-c", help="Path to configuration file."),
|
|
260
|
-
] = None,
|
|
261
|
-
) -> None:
|
|
262
|
-
"""Archive a thread.
|
|
263
|
-
|
|
264
|
-
Example:
|
|
265
|
-
soothe thread archive abc123
|
|
266
|
-
"""
|
|
267
|
-
cfg = load_config(config)
|
|
268
|
-
ws_url = websocket_url_from_config(cfg)
|
|
269
|
-
_require_daemon(ws_url)
|
|
270
|
-
|
|
271
|
-
resp = asyncio.run(
|
|
272
|
-
_rpc(ws_url, "send_thread_archive", {"thread_id": thread_id}, "thread_operation_ack")
|
|
273
|
-
)
|
|
274
|
-
if resp.get("success"):
|
|
275
|
-
typer.echo(f"Archived thread {thread_id}.")
|
|
276
|
-
else:
|
|
277
|
-
typer.echo(
|
|
278
|
-
f"Failed to archive thread: {resp.get('message', resp.get('error', 'unknown'))}",
|
|
279
|
-
err=True,
|
|
280
|
-
)
|
|
281
|
-
|
|
282
|
-
|
|
283
189
|
def thread_show(
|
|
284
190
|
thread_id: Annotated[str, typer.Argument(help="Thread ID to show.")],
|
|
285
191
|
config: Annotated[
|
|
@@ -287,10 +193,12 @@ def thread_show(
|
|
|
287
193
|
typer.Option("--config", "-c", help="Path to configuration file."),
|
|
288
194
|
] = None,
|
|
289
195
|
) -> None:
|
|
290
|
-
"""Show thread details.
|
|
196
|
+
"""Show thread details (read-only diagnostics).
|
|
291
197
|
|
|
292
198
|
Example:
|
|
293
199
|
soothe thread show abc123
|
|
200
|
+
|
|
201
|
+
Note: For thread lifecycle management, use loop commands (RFC-503).
|
|
294
202
|
"""
|
|
295
203
|
cfg = load_config(config)
|
|
296
204
|
ws_url = websocket_url_from_config(cfg)
|
|
@@ -331,45 +239,6 @@ def thread_show(
|
|
|
331
239
|
asyncio.run(_show())
|
|
332
240
|
|
|
333
241
|
|
|
334
|
-
def thread_delete(
|
|
335
|
-
thread_id: Annotated[str, typer.Argument(help="Thread ID to delete.")],
|
|
336
|
-
config: Annotated[
|
|
337
|
-
str | None,
|
|
338
|
-
typer.Option("--config", "-c", help="Path to configuration file."),
|
|
339
|
-
] = None,
|
|
340
|
-
*,
|
|
341
|
-
yes: Annotated[
|
|
342
|
-
bool,
|
|
343
|
-
typer.Option("--yes", "-y", help="Skip confirmation."),
|
|
344
|
-
] = False,
|
|
345
|
-
) -> None:
|
|
346
|
-
"""Permanently delete a thread.
|
|
347
|
-
|
|
348
|
-
Example:
|
|
349
|
-
soothe thread delete abc123
|
|
350
|
-
"""
|
|
351
|
-
if not yes:
|
|
352
|
-
confirm = typer.confirm(f"Permanently delete thread {thread_id}?")
|
|
353
|
-
if not confirm:
|
|
354
|
-
typer.echo("Cancelled.")
|
|
355
|
-
return
|
|
356
|
-
|
|
357
|
-
cfg = load_config(config)
|
|
358
|
-
ws_url = websocket_url_from_config(cfg)
|
|
359
|
-
_require_daemon(ws_url)
|
|
360
|
-
|
|
361
|
-
resp = asyncio.run(
|
|
362
|
-
_rpc(ws_url, "send_thread_delete", {"thread_id": thread_id}, "thread_operation_ack")
|
|
363
|
-
)
|
|
364
|
-
if resp.get("success"):
|
|
365
|
-
typer.echo(f"Deleted thread {thread_id}.")
|
|
366
|
-
else:
|
|
367
|
-
typer.echo(
|
|
368
|
-
f"Failed to delete thread: {resp.get('message', resp.get('error', 'unknown'))}",
|
|
369
|
-
err=True,
|
|
370
|
-
)
|
|
371
|
-
|
|
372
|
-
|
|
373
242
|
def thread_export(
|
|
374
243
|
thread_id: Annotated[str, typer.Argument(help="Thread ID to export.")],
|
|
375
244
|
output: Annotated[
|
|
@@ -381,15 +250,14 @@ def thread_export(
|
|
|
381
250
|
typer.Option("--format", "-f", help="Export format: jsonl or md."),
|
|
382
251
|
] = "jsonl",
|
|
383
252
|
) -> None:
|
|
384
|
-
"""Export thread conversation to a file.
|
|
253
|
+
"""Export thread conversation to a file (read-only diagnostics).
|
|
385
254
|
|
|
386
255
|
Example:
|
|
387
256
|
soothe thread export abc123 --output out.jsonl
|
|
388
257
|
soothe thread export abc123 --format md --output out.md
|
|
389
|
-
"""
|
|
390
|
-
import json
|
|
391
|
-
from pathlib import Path
|
|
392
258
|
|
|
259
|
+
Note: For thread lifecycle management, use loop commands (RFC-503).
|
|
260
|
+
"""
|
|
393
261
|
cfg = load_config(config=None)
|
|
394
262
|
ws_url = websocket_url_from_config(cfg)
|
|
395
263
|
_require_daemon(ws_url)
|
|
@@ -448,10 +316,12 @@ def thread_stats(
|
|
|
448
316
|
typer.Option("--config", "-c", help="Path to configuration file."),
|
|
449
317
|
] = None,
|
|
450
318
|
) -> None:
|
|
451
|
-
"""Show thread execution statistics.
|
|
319
|
+
"""Show thread execution statistics (read-only diagnostics).
|
|
452
320
|
|
|
453
321
|
Example:
|
|
454
322
|
soothe thread stats abc123
|
|
323
|
+
|
|
324
|
+
Note: For thread lifecycle management, use loop commands (RFC-503).
|
|
455
325
|
"""
|
|
456
326
|
cfg = load_config(config)
|
|
457
327
|
ws_url = websocket_url_from_config(cfg)
|
|
@@ -498,137 +368,6 @@ def thread_stats(
|
|
|
498
368
|
asyncio.run(_stats())
|
|
499
369
|
|
|
500
370
|
|
|
501
|
-
def thread_tag(
|
|
502
|
-
thread_id: Annotated[str, typer.Argument(help="Thread ID.")],
|
|
503
|
-
tags: Annotated[
|
|
504
|
-
list[str],
|
|
505
|
-
typer.Argument(help="Tags to add/remove."),
|
|
506
|
-
],
|
|
507
|
-
config: Annotated[
|
|
508
|
-
str | None,
|
|
509
|
-
typer.Option("--config", "-c", help="Path to configuration file."),
|
|
510
|
-
] = None,
|
|
511
|
-
*,
|
|
512
|
-
remove: Annotated[
|
|
513
|
-
bool,
|
|
514
|
-
typer.Option("--remove", help="Remove tags instead of adding."),
|
|
515
|
-
] = False,
|
|
516
|
-
) -> None:
|
|
517
|
-
"""Add or remove tags from a thread.
|
|
518
|
-
|
|
519
|
-
Examples:
|
|
520
|
-
soothe thread tag abc123 research analysis
|
|
521
|
-
soothe thread tag abc123 research --remove
|
|
522
|
-
"""
|
|
523
|
-
cfg = load_config(config)
|
|
524
|
-
ws_url = websocket_url_from_config(cfg)
|
|
525
|
-
_require_daemon(ws_url)
|
|
526
|
-
|
|
527
|
-
async def _tag() -> None:
|
|
528
|
-
client = WebSocketClient(url=ws_url)
|
|
529
|
-
try:
|
|
530
|
-
await client.connect()
|
|
531
|
-
|
|
532
|
-
# Get current thread state to read existing tags
|
|
533
|
-
await client.send_thread_get(thread_id)
|
|
534
|
-
thread_data: dict[str, Any] = {}
|
|
535
|
-
async with asyncio.timeout(30.0):
|
|
536
|
-
while True:
|
|
537
|
-
event = await client.read_event()
|
|
538
|
-
if not event:
|
|
539
|
-
typer.echo("No response from daemon.", err=True)
|
|
540
|
-
return
|
|
541
|
-
etype = event.get("type", "")
|
|
542
|
-
if etype == "thread_get_response":
|
|
543
|
-
thread_data = event.get("thread", {})
|
|
544
|
-
break
|
|
545
|
-
if etype == "error":
|
|
546
|
-
typer.echo(f"Error: {event.get('message', 'unknown')}", err=True)
|
|
547
|
-
return
|
|
548
|
-
|
|
549
|
-
# Update tags
|
|
550
|
-
metadata = dict(thread_data.get("metadata", {}))
|
|
551
|
-
current_tags = set(metadata.get("tags", []))
|
|
552
|
-
|
|
553
|
-
if remove:
|
|
554
|
-
current_tags -= set(tags)
|
|
555
|
-
else:
|
|
556
|
-
current_tags |= set(tags)
|
|
557
|
-
|
|
558
|
-
metadata["tags"] = sorted(current_tags)
|
|
559
|
-
|
|
560
|
-
# Update state via thread_update_state
|
|
561
|
-
await client.send_thread_update_state(thread_id, {"metadata": metadata})
|
|
562
|
-
|
|
563
|
-
# Wait for ack
|
|
564
|
-
async with asyncio.timeout(10.0):
|
|
565
|
-
while True:
|
|
566
|
-
event = await client.read_event()
|
|
567
|
-
if not event:
|
|
568
|
-
break
|
|
569
|
-
if event.get("type") in (
|
|
570
|
-
"thread_update_state_response",
|
|
571
|
-
"thread_operation_ack",
|
|
572
|
-
):
|
|
573
|
-
break
|
|
574
|
-
|
|
575
|
-
tag_list = ", ".join(metadata["tags"]) if metadata["tags"] else "(none)"
|
|
576
|
-
typer.echo(f"Tags: {tag_list}")
|
|
577
|
-
except TimeoutError:
|
|
578
|
-
typer.echo("Timed out waiting for response.", err=True)
|
|
579
|
-
finally:
|
|
580
|
-
await client.close()
|
|
581
|
-
|
|
582
|
-
asyncio.run(_tag())
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
def thread_create(
|
|
586
|
-
config: Annotated[
|
|
587
|
-
str | None,
|
|
588
|
-
typer.Option("--config", "-c", help="Path to configuration file."),
|
|
589
|
-
] = None,
|
|
590
|
-
*,
|
|
591
|
-
message: Annotated[
|
|
592
|
-
str | None,
|
|
593
|
-
typer.Option("--message", "-m", help="Initial message to seed the thread."),
|
|
594
|
-
] = None,
|
|
595
|
-
tag: Annotated[
|
|
596
|
-
list[str] | None,
|
|
597
|
-
typer.Option("--tag", "-t", help="Tags for the thread (repeatable)."),
|
|
598
|
-
] = None,
|
|
599
|
-
) -> None:
|
|
600
|
-
"""Create a new persisted thread.
|
|
601
|
-
|
|
602
|
-
Examples:
|
|
603
|
-
soothe thread create
|
|
604
|
-
soothe thread create --message "Hello world"
|
|
605
|
-
soothe thread create --tag research --tag analysis
|
|
606
|
-
"""
|
|
607
|
-
cfg = load_config(config)
|
|
608
|
-
ws_url = websocket_url_from_config(cfg)
|
|
609
|
-
_require_daemon(ws_url)
|
|
610
|
-
|
|
611
|
-
metadata: dict[str, Any] | None = None
|
|
612
|
-
if tag:
|
|
613
|
-
metadata = {"tags": sorted(tag)}
|
|
614
|
-
|
|
615
|
-
resp = asyncio.run(
|
|
616
|
-
_rpc(
|
|
617
|
-
ws_url,
|
|
618
|
-
"send_thread_create",
|
|
619
|
-
{"initial_message": message, "metadata": metadata},
|
|
620
|
-
"thread_created",
|
|
621
|
-
)
|
|
622
|
-
)
|
|
623
|
-
if resp.get("thread_id"):
|
|
624
|
-
typer.echo(f"Created thread {resp['thread_id']}")
|
|
625
|
-
else:
|
|
626
|
-
typer.echo(
|
|
627
|
-
f"Failed to create thread: {resp.get('message', resp.get('error', 'unknown'))}",
|
|
628
|
-
err=True,
|
|
629
|
-
)
|
|
630
|
-
|
|
631
|
-
|
|
632
371
|
def thread_artifacts(
|
|
633
372
|
thread_id: Annotated[str, typer.Argument(help="Thread ID to list artifacts for.")],
|
|
634
373
|
config: Annotated[
|
|
@@ -636,10 +375,12 @@ def thread_artifacts(
|
|
|
636
375
|
typer.Option("--config", "-c", help="Path to configuration file."),
|
|
637
376
|
] = None,
|
|
638
377
|
) -> None:
|
|
639
|
-
"""List artifacts for a thread.
|
|
378
|
+
"""List artifacts for a thread (read-only diagnostics).
|
|
640
379
|
|
|
641
380
|
Example:
|
|
642
381
|
soothe thread artifacts abc123
|
|
382
|
+
|
|
383
|
+
Note: For thread lifecycle management, use loop commands (RFC-503).
|
|
643
384
|
"""
|
|
644
385
|
cfg = load_config(config)
|
|
645
386
|
ws_url = websocket_url_from_config(cfg)
|
|
@@ -653,7 +394,7 @@ def thread_artifacts(
|
|
|
653
394
|
typer.echo("No artifacts found.")
|
|
654
395
|
return
|
|
655
396
|
typer.echo(f"{'Name':<30} {'Type':<15} {'Summary':<40}")
|
|
656
|
-
typer.echo("
|
|
397
|
+
typer.echo("─" * 90)
|
|
657
398
|
for a in artifacts:
|
|
658
399
|
name = str(a.get("name", ""))[:30]
|
|
659
400
|
a_type = str(a.get("type", ""))[:15]
|
|
@@ -93,7 +93,7 @@ def main(
|
|
|
93
93
|
soothe # Interactive TUI mode
|
|
94
94
|
soothe -p "Research AI advances" # Headless single-prompt mode
|
|
95
95
|
soothe --config custom.yml # Ignored for client settings; use ~/.soothe/config/cli_config.yml
|
|
96
|
-
soothe
|
|
96
|
+
soothe loop list # List AgentLoop instances
|
|
97
97
|
"""
|
|
98
98
|
# Handle -h/--help flag
|
|
99
99
|
if show_help:
|
|
@@ -121,10 +121,16 @@ def main(
|
|
|
121
121
|
|
|
122
122
|
|
|
123
123
|
# ---------------------------------------------------------------------------
|
|
124
|
-
# Thread Command (Nested Subcommands)
|
|
124
|
+
# Thread Command (Nested Subcommands) - Read-Only Diagnostics
|
|
125
125
|
# ---------------------------------------------------------------------------
|
|
126
|
+
# NOTE: Thread commands are read-only diagnostics per RFC-503 (Loop-First UX).
|
|
127
|
+
# Users manage loops (primary entity), not threads (internal execution contexts).
|
|
128
|
+
# For thread lifecycle management, use loop commands: soothe loop <subcommand>
|
|
126
129
|
|
|
127
|
-
thread_app = typer.Typer(
|
|
130
|
+
thread_app = typer.Typer(
|
|
131
|
+
name="thread",
|
|
132
|
+
help="Inspect conversation threads (read-only diagnostics)",
|
|
133
|
+
)
|
|
128
134
|
add_help_alias(thread_app)
|
|
129
135
|
app.add_typer(thread_app)
|
|
130
136
|
|
|
@@ -140,11 +146,13 @@ def _thread_list(
|
|
|
140
146
|
typer.Option("--status", "-s", help="Filter by status (active, archived)."),
|
|
141
147
|
] = None,
|
|
142
148
|
) -> None:
|
|
143
|
-
"""List all
|
|
149
|
+
"""List all agent threads (read-only diagnostics).
|
|
144
150
|
|
|
145
151
|
Examples:
|
|
146
152
|
soothe thread list
|
|
147
153
|
soothe thread list --status active
|
|
154
|
+
|
|
155
|
+
Note: For thread lifecycle management, use loop commands (RFC-503).
|
|
148
156
|
"""
|
|
149
157
|
from soothe_cli.cli.commands.thread_cmd import thread_list
|
|
150
158
|
|
|
@@ -159,85 +167,18 @@ def _thread_show(
|
|
|
159
167
|
typer.Option("--config", "-c", help="Path to configuration file."),
|
|
160
168
|
] = None,
|
|
161
169
|
) -> None:
|
|
162
|
-
"""Show thread details.
|
|
170
|
+
"""Show thread details (read-only diagnostics).
|
|
163
171
|
|
|
164
172
|
Example:
|
|
165
173
|
soothe thread show abc123
|
|
174
|
+
|
|
175
|
+
Note: For thread lifecycle management, use loop commands (RFC-503).
|
|
166
176
|
"""
|
|
167
177
|
from soothe_cli.cli.commands.thread_cmd import thread_show
|
|
168
178
|
|
|
169
179
|
thread_show(thread_id=thread_id, config=config)
|
|
170
180
|
|
|
171
181
|
|
|
172
|
-
@thread_app.command("continue")
|
|
173
|
-
def _thread_continue(
|
|
174
|
-
thread_id: Annotated[
|
|
175
|
-
str | None,
|
|
176
|
-
typer.Argument(help="Thread ID to continue. Omit to continue last active thread."),
|
|
177
|
-
] = None,
|
|
178
|
-
config: Annotated[
|
|
179
|
-
str | None,
|
|
180
|
-
typer.Option("--config", "-c", help="Path to configuration file."),
|
|
181
|
-
] = None,
|
|
182
|
-
new: Annotated[ # noqa: FBT002
|
|
183
|
-
bool,
|
|
184
|
-
typer.Option("--new", help="Create a new thread instead of continuing."),
|
|
185
|
-
] = False,
|
|
186
|
-
) -> None:
|
|
187
|
-
"""Continue a conversation thread in the TUI.
|
|
188
|
-
|
|
189
|
-
Requires a running daemon. Start daemon with 'soothe-daemon start' first.
|
|
190
|
-
|
|
191
|
-
Examples:
|
|
192
|
-
soothe thread continue abc123
|
|
193
|
-
soothe thread continue --new
|
|
194
|
-
soothe thread continue
|
|
195
|
-
"""
|
|
196
|
-
from soothe_cli.cli.commands.thread_cmd import thread_continue
|
|
197
|
-
|
|
198
|
-
thread_continue(thread_id=thread_id, config=config, new=new)
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
@thread_app.command("archive")
|
|
202
|
-
def _thread_archive(
|
|
203
|
-
thread_id: Annotated[str, typer.Argument(help="Thread ID to archive.")],
|
|
204
|
-
config: Annotated[
|
|
205
|
-
str | None,
|
|
206
|
-
typer.Option("--config", "-c", help="Path to configuration file."),
|
|
207
|
-
] = None,
|
|
208
|
-
) -> None:
|
|
209
|
-
"""Archive a thread.
|
|
210
|
-
|
|
211
|
-
Example:
|
|
212
|
-
soothe thread archive abc123
|
|
213
|
-
"""
|
|
214
|
-
from soothe_cli.cli.commands.thread_cmd import thread_archive
|
|
215
|
-
|
|
216
|
-
thread_archive(thread_id=thread_id, config=config)
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
@thread_app.command("delete")
|
|
220
|
-
def _thread_delete(
|
|
221
|
-
thread_id: Annotated[str, typer.Argument(help="Thread ID to delete.")],
|
|
222
|
-
config: Annotated[
|
|
223
|
-
str | None,
|
|
224
|
-
typer.Option("--config", "-c", help="Path to configuration file."),
|
|
225
|
-
] = None,
|
|
226
|
-
yes: Annotated[ # noqa: FBT002
|
|
227
|
-
bool,
|
|
228
|
-
typer.Option("--yes", "-y", help="Skip confirmation."),
|
|
229
|
-
] = False,
|
|
230
|
-
) -> None:
|
|
231
|
-
"""Permanently delete a thread.
|
|
232
|
-
|
|
233
|
-
Example:
|
|
234
|
-
soothe thread delete abc123
|
|
235
|
-
"""
|
|
236
|
-
from soothe_cli.cli.commands.thread_cmd import thread_delete
|
|
237
|
-
|
|
238
|
-
thread_delete(thread_id=thread_id, config=config, yes=yes)
|
|
239
|
-
|
|
240
|
-
|
|
241
182
|
@thread_app.command("export")
|
|
242
183
|
def _thread_export(
|
|
243
184
|
thread_id: Annotated[str, typer.Argument(help="Thread ID to export.")],
|
|
@@ -250,10 +191,12 @@ def _thread_export(
|
|
|
250
191
|
typer.Option("--format", "-f", help="Export format: jsonl or md."),
|
|
251
192
|
] = "jsonl",
|
|
252
193
|
) -> None:
|
|
253
|
-
"""Export thread conversation to a file.
|
|
194
|
+
"""Export thread conversation to a file (read-only diagnostics).
|
|
254
195
|
|
|
255
196
|
Example:
|
|
256
|
-
soothe thread export abc123 --output out.
|
|
197
|
+
soothe thread export abc123 --output out.jsonl
|
|
198
|
+
|
|
199
|
+
Note: For thread lifecycle management, use loop commands (RFC-503).
|
|
257
200
|
"""
|
|
258
201
|
from soothe_cli.cli.commands.thread_cmd import thread_export
|
|
259
202
|
|
|
@@ -268,70 +211,18 @@ def _thread_stats(
|
|
|
268
211
|
typer.Option("--config", "-c", help="Path to configuration file."),
|
|
269
212
|
] = None,
|
|
270
213
|
) -> None:
|
|
271
|
-
"""Show thread execution statistics.
|
|
214
|
+
"""Show thread execution statistics (read-only diagnostics).
|
|
272
215
|
|
|
273
216
|
Example:
|
|
274
217
|
soothe thread stats abc123
|
|
218
|
+
|
|
219
|
+
Note: For thread lifecycle management, use loop commands (RFC-503).
|
|
275
220
|
"""
|
|
276
221
|
from soothe_cli.cli.commands.thread_cmd import thread_stats
|
|
277
222
|
|
|
278
223
|
thread_stats(thread_id=thread_id, config=config)
|
|
279
224
|
|
|
280
225
|
|
|
281
|
-
@thread_app.command("tag")
|
|
282
|
-
def _thread_tag(
|
|
283
|
-
thread_id: Annotated[str, typer.Argument(help="Thread ID.")],
|
|
284
|
-
tags: Annotated[
|
|
285
|
-
list[str],
|
|
286
|
-
typer.Argument(help="Tags to add/remove."),
|
|
287
|
-
],
|
|
288
|
-
config: Annotated[
|
|
289
|
-
str | None,
|
|
290
|
-
typer.Option("--config", "-c", help="Path to configuration file."),
|
|
291
|
-
] = None,
|
|
292
|
-
remove: Annotated[ # noqa: FBT002
|
|
293
|
-
bool,
|
|
294
|
-
typer.Option("--remove", help="Remove tags instead of adding."),
|
|
295
|
-
] = False,
|
|
296
|
-
) -> None:
|
|
297
|
-
"""Add or remove tags from a thread.
|
|
298
|
-
|
|
299
|
-
Examples:
|
|
300
|
-
soothe thread tag abc123 research analysis
|
|
301
|
-
soothe thread tag abc123 research --remove
|
|
302
|
-
"""
|
|
303
|
-
from soothe_cli.cli.commands.thread_cmd import thread_tag
|
|
304
|
-
|
|
305
|
-
thread_tag(thread_id=thread_id, tags=tags, config=config, remove=remove)
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
@thread_app.command("create")
|
|
309
|
-
def _thread_create(
|
|
310
|
-
config: Annotated[
|
|
311
|
-
str | None,
|
|
312
|
-
typer.Option("--config", "-c", help="Path to configuration file."),
|
|
313
|
-
] = None,
|
|
314
|
-
message: Annotated[
|
|
315
|
-
str | None,
|
|
316
|
-
typer.Option("--message", "-m", help="Initial message to seed the thread."),
|
|
317
|
-
] = None,
|
|
318
|
-
tag: Annotated[
|
|
319
|
-
list[str] | None,
|
|
320
|
-
typer.Option("--tag", "-t", help="Tags for the thread (repeatable)."),
|
|
321
|
-
] = None,
|
|
322
|
-
) -> None:
|
|
323
|
-
"""Create a new persisted thread.
|
|
324
|
-
|
|
325
|
-
Examples:
|
|
326
|
-
soothe thread create
|
|
327
|
-
soothe thread create --message "Hello world"
|
|
328
|
-
soothe thread create --tag research
|
|
329
|
-
"""
|
|
330
|
-
from soothe_cli.cli.commands.thread_cmd import thread_create
|
|
331
|
-
|
|
332
|
-
thread_create(config=config, message=message, tag=tag)
|
|
333
|
-
|
|
334
|
-
|
|
335
226
|
@thread_app.command("artifacts")
|
|
336
227
|
def _thread_artifacts(
|
|
337
228
|
thread_id: Annotated[str, typer.Argument(help="Thread ID to list artifacts for.")],
|
|
@@ -340,16 +231,28 @@ def _thread_artifacts(
|
|
|
340
231
|
typer.Option("--config", "-c", help="Path to configuration file."),
|
|
341
232
|
] = None,
|
|
342
233
|
) -> None:
|
|
343
|
-
"""List artifacts for a thread.
|
|
234
|
+
"""List artifacts for a thread (read-only diagnostics).
|
|
344
235
|
|
|
345
236
|
Example:
|
|
346
237
|
soothe thread artifacts abc123
|
|
238
|
+
|
|
239
|
+
Note: For thread lifecycle management, use loop commands (RFC-503).
|
|
347
240
|
"""
|
|
348
241
|
from soothe_cli.cli.commands.thread_cmd import thread_artifacts
|
|
349
242
|
|
|
350
243
|
thread_artifacts(thread_id=thread_id, config=config)
|
|
351
244
|
|
|
352
245
|
|
|
246
|
+
# ---------------------------------------------------------------------------
|
|
247
|
+
# Loop Command (Nested Subcommands)
|
|
248
|
+
# ---------------------------------------------------------------------------
|
|
249
|
+
|
|
250
|
+
from soothe_cli.loop_commands import loop_app as _loop_app # noqa: E402
|
|
251
|
+
|
|
252
|
+
add_help_alias(_loop_app)
|
|
253
|
+
app.add_typer(_loop_app, name="loop")
|
|
254
|
+
|
|
255
|
+
|
|
353
256
|
# ---------------------------------------------------------------------------
|
|
354
257
|
# Config Command (Nested Subcommands)
|
|
355
258
|
# ---------------------------------------------------------------------------
|