synapse-cli-agent 0.1.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- synapse/__init__.py +13 -0
- synapse/__main__.py +6 -0
- synapse/app/__init__.py +1 -0
- synapse/app/agent.py +492 -0
- synapse/app/agent_md.py +107 -0
- synapse/cli.py +750 -0
- synapse/commands/__init__.py +1 -0
- synapse/commands/compression.py +573 -0
- synapse/commands/helpers.py +22 -0
- synapse/commands/mcp.py +406 -0
- synapse/commands/model.py +173 -0
- synapse/commands/result.py +34 -0
- synapse/commands/sessions.py +443 -0
- synapse/commands/slash_cmds.py +521 -0
- synapse/commands/slash_complete.py +816 -0
- synapse/commands/theme.py +99 -0
- synapse/config.py +27 -0
- synapse/content/__init__.py +1 -0
- synapse/content/input_history.py +122 -0
- synapse/content/multimodal.py +733 -0
- synapse/content/prompts.py +249 -0
- synapse/content/skills_catalog.py +128 -0
- synapse/integrations/__init__.py +1 -0
- synapse/integrations/checkpoint_seed.py +281 -0
- synapse/integrations/codex_history.py +375 -0
- synapse/integrations/codex_import.py +393 -0
- synapse/integrations/codex_sessions.py +629 -0
- synapse/integrations/describe_image.py +370 -0
- synapse/integrations/http_clients.py +199 -0
- synapse/integrations/llm_openai_compat.py +90 -0
- synapse/integrations/llm_openai_websocket.py +187 -0
- synapse/integrations/mcp_client.py +646 -0
- synapse/integrations/vision_middleware.py +62 -0
- synapse/models/__init__.py +5 -0
- synapse/models/config.py +240 -0
- synapse/models/helpers.py +206 -0
- synapse/models/profile.py +59 -0
- synapse/models/registry.py +722 -0
- synapse/models_registry.py +7 -0
- synapse/observability/__init__.py +1 -0
- synapse/observability/startup_trace.py +127 -0
- synapse/runtime/__init__.py +1 -0
- synapse/runtime/async_runtime.py +176 -0
- synapse/runtime/backends.py +458 -0
- synapse/runtime/context_compact.py +249 -0
- synapse/runtime/execute_capture.py +48 -0
- synapse/runtime/fs_permissions.py +79 -0
- synapse/runtime/harness.py +57 -0
- synapse/runtime/hitl.py +197 -0
- synapse/runtime/interaction_ledger.py +82 -0
- synapse/runtime/middleware.py +802 -0
- synapse/runtime/model_request_compression_middleware.py +745 -0
- synapse/runtime/pathing.py +146 -0
- synapse/runtime/safety.py +184 -0
- synapse/runtime/steer.py +240 -0
- synapse/runtime/subagents.py +207 -0
- synapse/runtime/tool_ignore.py +221 -0
- synapse/runtime/tool_output_eval.py +118 -0
- synapse/runtime/tool_output_middleware.py +585 -0
- synapse/runtime/tool_output_usage_middleware.py +60 -0
- synapse/sessions/__init__.py +31 -0
- synapse/sessions/cancel_repair.py +208 -0
- synapse/sessions/session_recap.py +174 -0
- synapse/sessions/store.py +695 -0
- synapse/sessions/transcript.py +754 -0
- synapse/settings/__init__.py +5 -0
- synapse/settings/config_paths.py +184 -0
- synapse/settings/schema.py +464 -0
- synapse/tool_output/__init__.py +59 -0
- synapse/tool_output/detection.py +170 -0
- synapse/tool_output/metrics.py +32 -0
- synapse/tool_output/models.py +173 -0
- synapse/tool_output/pipeline.py +330 -0
- synapse/tool_output/repository.py +721 -0
- synapse/tool_output/transformers.py +648 -0
- synapse/tools/__init__.py +5 -0
- synapse/tools/session_tools.py +204 -0
- synapse/ui/__init__.py +10 -0
- synapse/ui/bottombar/__init__.py +73 -0
- synapse/ui/bottombar/components/__init__.py +143 -0
- synapse/ui/bottombar/components/key_hints.py +30 -0
- synapse/ui/bottombar/components/mcp.py +64 -0
- synapse/ui/bottombar/components/mode.py +24 -0
- synapse/ui/bottombar/components/model.py +28 -0
- synapse/ui/bottombar/components/thread.py +29 -0
- synapse/ui/bottombar/context.py +36 -0
- synapse/ui/bottombar/core.py +74 -0
- synapse/ui/dialogs/__init__.py +25 -0
- synapse/ui/dialogs/base.py +362 -0
- synapse/ui/dialogs/codex_session_list.py +84 -0
- synapse/ui/dialogs/compression_diagnostics.py +210 -0
- synapse/ui/dialogs/git_explore.py +702 -0
- synapse/ui/dialogs/mcp_panel.py +407 -0
- synapse/ui/dialogs/model_picker.py +128 -0
- synapse/ui/dialogs/safety_panel.py +63 -0
- synapse/ui/dialogs/session_list.py +98 -0
- synapse/ui/dialogs/theme_designer.py +863 -0
- synapse/ui/dialogs/theme_picker.py +113 -0
- synapse/ui/git_explore/__init__.py +31 -0
- synapse/ui/git_explore/engine.py +82 -0
- synapse/ui/git_explore/provider.py +242 -0
- synapse/ui/git_explore/unified.py +85 -0
- synapse/ui/rendering.py +350 -0
- synapse/ui/sink.py +70 -0
- synapse/ui/steer_widget.py +367 -0
- synapse/ui/stream.py +1207 -0
- synapse/ui/stream_events.py +421 -0
- synapse/ui/stream_runtime.py +252 -0
- synapse/ui/theme.py +1154 -0
- synapse/ui/timeline.py +621 -0
- synapse/ui/topbar/__init__.py +97 -0
- synapse/ui/topbar/components/__init__.py +150 -0
- synapse/ui/topbar/components/branch.py +41 -0
- synapse/ui/topbar/components/title.py +24 -0
- synapse/ui/topbar/components/tool_output.py +24 -0
- synapse/ui/topbar/components/usage.py +24 -0
- synapse/ui/topbar/components/workspace.py +32 -0
- synapse/ui/topbar/context.py +32 -0
- synapse/ui/topbar/core.py +979 -0
- synapse/ui/topbar/git_changes_popover.py +178 -0
- synapse/ui/topbar/git_chrome.py +475 -0
- synapse/ui/topbar/tool_output_popover.py +84 -0
- synapse/ui/topbar/widget.py +474 -0
- synapse/ui/tui.py +5717 -0
- synapse/ui/turn_rail.py +71 -0
- synapse/ui/user_turn.py +83 -0
- synapse/ui/welcome.py +261 -0
- synapse_cli_agent-0.1.13.dist-info/METADATA +412 -0
- synapse_cli_agent-0.1.13.dist-info/RECORD +131 -0
- synapse_cli_agent-0.1.13.dist-info/WHEEL +4 -0
- synapse_cli_agent-0.1.13.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Theme selection slash-command handler."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from synapse.commands.helpers import markdown_escape
|
|
8
|
+
from synapse.commands.result import SlashResult
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def handle_theme(
|
|
12
|
+
args: list[str],
|
|
13
|
+
*,
|
|
14
|
+
settings: Any,
|
|
15
|
+
project_root: Path,
|
|
16
|
+
) -> SlashResult:
|
|
17
|
+
"""List or switch UI themes; persist selection to user settings.json."""
|
|
18
|
+
from synapse.ui.theme import (
|
|
19
|
+
format_theme_list_lines,
|
|
20
|
+
get_theme,
|
|
21
|
+
list_theme_names,
|
|
22
|
+
reload_theme_catalog,
|
|
23
|
+
set_theme,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
reload_theme_catalog(project_root)
|
|
27
|
+
if not args or args[0].casefold() in {"list", "ls", "show"}:
|
|
28
|
+
active = getattr(settings, "theme", None) or get_theme().name
|
|
29
|
+
plain = format_theme_list_lines(active=active)
|
|
30
|
+
from synapse.ui.theme import list_themes, theme_kind
|
|
31
|
+
|
|
32
|
+
md = (
|
|
33
|
+
"## Themes\n\n"
|
|
34
|
+
f"**current**: `{active}`\n\n"
|
|
35
|
+
"| | Name | Label | Kind |\n|---|---|---|---|\n"
|
|
36
|
+
)
|
|
37
|
+
for t in list_themes():
|
|
38
|
+
mark = "*" if t.name == active else " "
|
|
39
|
+
tone = theme_kind(t)
|
|
40
|
+
from synapse.ui.theme import BUILTIN_THEMES, _custom
|
|
41
|
+
|
|
42
|
+
if t.name in _custom and t.name not in BUILTIN_THEMES:
|
|
43
|
+
kind = f"custom/{tone}"
|
|
44
|
+
elif t.name in _custom:
|
|
45
|
+
kind = f"override/{tone}"
|
|
46
|
+
else:
|
|
47
|
+
kind = f"built-in/{tone}"
|
|
48
|
+
md += (
|
|
49
|
+
f"| {mark} | `{markdown_escape(t.name)}` "
|
|
50
|
+
f"| {markdown_escape(t.label)} | {markdown_escape(kind)} |\n"
|
|
51
|
+
)
|
|
52
|
+
md += "\nusage: `/theme <name>`\n\nconfig: `settings.json` theme + optional `themes.json`"
|
|
53
|
+
return SlashResult(handled=True, lines=plain, markdown=md)
|
|
54
|
+
|
|
55
|
+
name = args[0].strip()
|
|
56
|
+
# Optional: /theme set <name> | /theme use <name>
|
|
57
|
+
if name.casefold() in {"set", "use", "switch"} and len(args) >= 2:
|
|
58
|
+
name = args[1].strip()
|
|
59
|
+
try:
|
|
60
|
+
theme = set_theme(
|
|
61
|
+
name,
|
|
62
|
+
workspace=project_root,
|
|
63
|
+
persist=True,
|
|
64
|
+
scope="user",
|
|
65
|
+
reload=False,
|
|
66
|
+
)
|
|
67
|
+
except KeyError as exc:
|
|
68
|
+
names = ", ".join(list_theme_names())
|
|
69
|
+
return SlashResult(
|
|
70
|
+
handled=True,
|
|
71
|
+
lines=[str(exc), f"available: {names}"],
|
|
72
|
+
error=True,
|
|
73
|
+
)
|
|
74
|
+
except Exception as exc: # noqa: BLE001
|
|
75
|
+
return SlashResult(
|
|
76
|
+
handled=True,
|
|
77
|
+
lines=[f"theme switch failed: {exc}"],
|
|
78
|
+
error=True,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
settings.theme = theme.name
|
|
83
|
+
except Exception: # noqa: BLE001
|
|
84
|
+
pass
|
|
85
|
+
return SlashResult(
|
|
86
|
+
handled=True,
|
|
87
|
+
lines=[
|
|
88
|
+
f"theme switched to {theme.name} ({theme.label})",
|
|
89
|
+
"saved to ~/.coding-agent/settings.json",
|
|
90
|
+
],
|
|
91
|
+
markdown=(
|
|
92
|
+
"## Theme\n\n"
|
|
93
|
+
f"- **name**: `{markdown_escape(theme.name)}`\n"
|
|
94
|
+
f"- **label**: {markdown_escape(theme.label)}\n\n"
|
|
95
|
+
"*saved to `~/.coding-agent/settings.json`*"
|
|
96
|
+
),
|
|
97
|
+
settings_changed=True,
|
|
98
|
+
theme_name=theme.name,
|
|
99
|
+
)
|
synapse/config.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Compatibility exports for the settings domain.
|
|
2
|
+
|
|
3
|
+
New code should import from :mod:`synapse.settings`. This module remains so
|
|
4
|
+
existing integrations and extension code do not need an atomic migration.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from synapse.settings.schema import (
|
|
8
|
+
Settings,
|
|
9
|
+
bootstrap_project_env,
|
|
10
|
+
config_search_roots,
|
|
11
|
+
executable_config_dirs,
|
|
12
|
+
find_dotenv,
|
|
13
|
+
load_settings,
|
|
14
|
+
project_config_dir,
|
|
15
|
+
user_config_dir,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"Settings",
|
|
20
|
+
"bootstrap_project_env",
|
|
21
|
+
"config_search_roots",
|
|
22
|
+
"executable_config_dirs",
|
|
23
|
+
"find_dotenv",
|
|
24
|
+
"load_settings",
|
|
25
|
+
"project_config_dir",
|
|
26
|
+
"user_config_dir",
|
|
27
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Prompt, skill, input, and multimodal content services."""
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Project-scoped input history for chat CLI / TUI (up/down navigation)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _read_text_lossy(path: Path) -> str:
|
|
9
|
+
"""Read history file, tolerating legacy Windows encodings (GBK etc.)."""
|
|
10
|
+
data = path.read_bytes()
|
|
11
|
+
if not data:
|
|
12
|
+
return ""
|
|
13
|
+
# UTF-8 BOM
|
|
14
|
+
if data.startswith(b"\xef\xbb\xbf"):
|
|
15
|
+
return data.decode("utf-8-sig")
|
|
16
|
+
# UTF-16 LE/BE BOM
|
|
17
|
+
if data.startswith(b"\xff\xfe"):
|
|
18
|
+
return data.decode("utf-16-le")
|
|
19
|
+
if data.startswith(b"\xfe\xff"):
|
|
20
|
+
return data.decode("utf-16-be")
|
|
21
|
+
for enc in ("utf-8", "gbk", "cp936", "gb18030", "cp1252", "latin-1"):
|
|
22
|
+
try:
|
|
23
|
+
return data.decode(enc)
|
|
24
|
+
except UnicodeDecodeError:
|
|
25
|
+
continue
|
|
26
|
+
return data.decode("utf-8", errors="replace")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class InputHistory:
|
|
30
|
+
"""Line-based history stored under the project ``.synapse/`` dir.
|
|
31
|
+
|
|
32
|
+
Shares the same default file as the readline chat CLI
|
|
33
|
+
(``.synapse/history``) so entries accumulate across UIs.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
path: Path,
|
|
39
|
+
*,
|
|
40
|
+
max_entries: int = 1000,
|
|
41
|
+
) -> None:
|
|
42
|
+
self.path = Path(path)
|
|
43
|
+
self.max_entries = max(1, int(max_entries))
|
|
44
|
+
self.entries: list[str] = []
|
|
45
|
+
self._index: int | None = None
|
|
46
|
+
self._draft: str = ""
|
|
47
|
+
self.load()
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def for_project(cls, project_root: Path | None = None, **kwargs) -> InputHistory:
|
|
51
|
+
from synapse.settings.config_paths import SYNAPSE_DIRNAME
|
|
52
|
+
|
|
53
|
+
root = Path(project_root or Path.cwd()).expanduser().resolve()
|
|
54
|
+
return cls(root / SYNAPSE_DIRNAME / "history", **kwargs)
|
|
55
|
+
|
|
56
|
+
def load(self) -> None:
|
|
57
|
+
self.entries = []
|
|
58
|
+
try:
|
|
59
|
+
raw = _read_text_lossy(self.path)
|
|
60
|
+
except (FileNotFoundError, OSError):
|
|
61
|
+
return
|
|
62
|
+
for line in raw.splitlines():
|
|
63
|
+
text = line.rstrip("\n\r")
|
|
64
|
+
if not text or text.startswith("_HiStOrY_"):
|
|
65
|
+
continue
|
|
66
|
+
# readline may escape leading spaces; keep content as-is after strip of
|
|
67
|
+
# trailing junk only. Preserve intentional leading spaces rarely used.
|
|
68
|
+
self.entries.append(text)
|
|
69
|
+
if len(self.entries) > self.max_entries:
|
|
70
|
+
self.entries = self.entries[-self.max_entries :]
|
|
71
|
+
|
|
72
|
+
def save(self) -> None:
|
|
73
|
+
try:
|
|
74
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
75
|
+
body = "\n".join(self.entries[-self.max_entries :])
|
|
76
|
+
if body:
|
|
77
|
+
body += "\n"
|
|
78
|
+
self.path.write_text(body, encoding="utf-8")
|
|
79
|
+
except OSError:
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
def add(self, text: str) -> None:
|
|
83
|
+
line = (text or "").strip()
|
|
84
|
+
if not line:
|
|
85
|
+
self.reset_cursor()
|
|
86
|
+
return
|
|
87
|
+
if self.entries and self.entries[-1] == line:
|
|
88
|
+
self.reset_cursor()
|
|
89
|
+
return
|
|
90
|
+
self.entries.append(line)
|
|
91
|
+
if len(self.entries) > self.max_entries:
|
|
92
|
+
self.entries = self.entries[-self.max_entries :]
|
|
93
|
+
self.save()
|
|
94
|
+
self.reset_cursor()
|
|
95
|
+
|
|
96
|
+
def reset_cursor(self) -> None:
|
|
97
|
+
self._index = None
|
|
98
|
+
self._draft = ""
|
|
99
|
+
|
|
100
|
+
def up(self, current: str) -> str | None:
|
|
101
|
+
"""Move to older entry. Returns text to put in the prompt, or None."""
|
|
102
|
+
if not self.entries:
|
|
103
|
+
return None
|
|
104
|
+
if self._index is None:
|
|
105
|
+
self._draft = current or ""
|
|
106
|
+
self._index = len(self.entries) - 1
|
|
107
|
+
elif self._index > 0:
|
|
108
|
+
self._index -= 1
|
|
109
|
+
return self.entries[self._index]
|
|
110
|
+
|
|
111
|
+
def down(self, current: str) -> str | None: # noqa: ARG002
|
|
112
|
+
"""Move to newer entry, or restore the draft when leaving history."""
|
|
113
|
+
if self._index is None:
|
|
114
|
+
return None
|
|
115
|
+
if self._index < len(self.entries) - 1:
|
|
116
|
+
self._index += 1
|
|
117
|
+
return self.entries[self._index]
|
|
118
|
+
# Past the newest → restore live draft
|
|
119
|
+
self._index = None
|
|
120
|
+
draft = self._draft
|
|
121
|
+
self._draft = ""
|
|
122
|
+
return draft
|