pcode 0.1.0__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.
- pcode/__init__.py +7 -0
- pcode/_job_supervisor.py +65 -0
- pcode/agent.py +592 -0
- pcode/anthropic_oauth.py +469 -0
- pcode/app.py +3165 -0
- pcode/appearance.py +181 -0
- pcode/aside.py +541 -0
- pcode/aside_guard.py +39 -0
- pcode/aside_ui.py +683 -0
- pcode/auth.py +113 -0
- pcode/block.py +39 -0
- pcode/browser.py +393 -0
- pcode/cache_diagnostics.py +336 -0
- pcode/cache_settings.py +58 -0
- pcode/cache_warnings.py +139 -0
- pcode/claude_sdk/__init__.py +96 -0
- pcode/claude_sdk/cli.py +52 -0
- pcode/claude_sdk/errors.py +61 -0
- pcode/claude_sdk/messages.py +154 -0
- pcode/claude_sdk/model.py +179 -0
- pcode/claude_sdk/resume.py +114 -0
- pcode/claude_sdk/session.py +547 -0
- pcode/claude_sdk/session_pool.py +251 -0
- pcode/claude_sdk/workspace.py +34 -0
- pcode/cli.py +73 -0
- pcode/clipboard.py +76 -0
- pcode/code_mode.py +43 -0
- pcode/codex_login.py +189 -0
- pcode/command_transcript.py +71 -0
- pcode/commands.py +145 -0
- pcode/compaction.py +457 -0
- pcode/completion.py +263 -0
- pcode/config.py +223 -0
- pcode/context_breakdown.py +178 -0
- pcode/context_usage.py +52 -0
- pcode/controller.py +3496 -0
- pcode/conversation_ids.py +27 -0
- pcode/conversation_tree.py +160 -0
- pcode/copy_ui.py +172 -0
- pcode/delegation.py +236 -0
- pcode/delta.py +295 -0
- pcode/diagnostics.py +226 -0
- pcode/edit_preview.py +158 -0
- pcode/edit_transcript.py +287 -0
- pcode/edit_ui.py +850 -0
- pcode/edits.py +162 -0
- pcode/email_remote/__init__.py +14 -0
- pcode/email_remote/cli.py +133 -0
- pcode/email_remote/listener.py +695 -0
- pcode/email_remote/mailbox.py +336 -0
- pcode/email_remote/outbound.py +75 -0
- pcode/email_remote/parsing.py +226 -0
- pcode/email_remote/render.py +175 -0
- pcode/email_remote/state.py +79 -0
- pcode/error_report.py +38 -0
- pcode/ext.py +602 -0
- pcode/extension_guide.md +311 -0
- pcode/extensions/browser.py +284 -0
- pcode/extensions/ponytail.py +217 -0
- pcode/extensions/sandbox.py +138 -0
- pcode/extensions/session_history.py +170 -0
- pcode/extensions/web_research.py +89 -0
- pcode/file_refs.py +265 -0
- pcode/filesystem.py +138 -0
- pcode/frame.py +149 -0
- pcode/gateway.py +465 -0
- pcode/git_diff.py +497 -0
- pcode/history.py +589 -0
- pcode/history_cursor.py +147 -0
- pcode/history_embeddings.py +102 -0
- pcode/host.py +941 -0
- pcode/host_protocol.py +291 -0
- pcode/host_ui.py +225 -0
- pcode/input_keys.py +35 -0
- pcode/inspection.py +289 -0
- pcode/inspector_ui.py +448 -0
- pcode/isolated_delegation.py +425 -0
- pcode/job_notices.py +54 -0
- pcode/jobs.py +584 -0
- pcode/jobs_ui.py +265 -0
- pcode/keymap.py +215 -0
- pcode/layout_speed.py +103 -0
- pcode/links.py +90 -0
- pcode/links_ui.py +200 -0
- pcode/live.py +1301 -0
- pcode/llm_proxy.py +44 -0
- pcode/logins.py +233 -0
- pcode/mcp.py +683 -0
- pcode/mcp_notice.py +91 -0
- pcode/mcp_oauth.py +381 -0
- pcode/meridian.py +233 -0
- pcode/meridian_process.py +337 -0
- pcode/meridian_reminders.py +107 -0
- pcode/meridian_setup.py +356 -0
- pcode/model_metadata.py +451 -0
- pcode/model_ui.py +168 -0
- pcode/models.py +209 -0
- pcode/native_results.py +119 -0
- pcode/oauth_pages.py +66 -0
- pcode/output_limits.py +41 -0
- pcode/paste.py +45 -0
- pcode/path_completion.py +66 -0
- pcode/plan_preview.py +131 -0
- pcode/plan_sizes.py +35 -0
- pcode/planning.py +161 -0
- pcode/popup_ui.py +873 -0
- pcode/preferences.py +1176 -0
- pcode/prefix_keys.py +490 -0
- pcode/profile_benchmark.py +332 -0
- pcode/profiling.py +566 -0
- pcode/project_trust.py +94 -0
- pcode/prompt_keys.py +286 -0
- pcode/remote.py +717 -0
- pcode/remote_print.py +125 -0
- pcode/remote_profile.py +237 -0
- pcode/replay_benchmark.py +319 -0
- pcode/repo_context.py +158 -0
- pcode/retries.py +28 -0
- pcode/rpc.py +280 -0
- pcode/runtime.py +244 -0
- pcode/sandbox.py +454 -0
- pcode/session_naming.py +82 -0
- pcode/session_ui.py +550 -0
- pcode/sessions.py +892 -0
- pcode/shell.py +113 -0
- pcode/shell_mode.py +172 -0
- pcode/shell_tools.py +459 -0
- pcode/skills.py +161 -0
- pcode/stall_log.py +187 -0
- pcode/startup_input.py +96 -0
- pcode/steering.py +27 -0
- pcode/stream_display.py +188 -0
- pcode/stream_events.py +437 -0
- pcode/strict_tools.py +130 -0
- pcode/syntax.py +43 -0
- pcode/syntax_colors.py +206 -0
- pcode/task_prompt.py +28 -0
- pcode/task_worktrees.py +482 -0
- pcode/terminal_notify.py +383 -0
- pcode/theme.py +135 -0
- pcode/theme_gallery.py +88 -0
- pcode/thinking_markdown.py +41 -0
- pcode/token_accounting.py +67 -0
- pcode/tool_display.py +784 -0
- pcode/tool_output_limits.py +108 -0
- pcode/tool_panel.py +377 -0
- pcode/transcript_log.py +127 -0
- pcode/transcript_notice.py +103 -0
- pcode/tree_ui.py +288 -0
- pcode/turn.py +44 -0
- pcode/ui.py +3745 -0
- pcode/usage.py +293 -0
- pcode/word_wrap.py +107 -0
- pcode/worker_ui.py +235 -0
- pcode/workers.py +136 -0
- pcode/workspace.py +57 -0
- pcode/workspace_filesystem.py +72 -0
- pcode/worktree.py +650 -0
- pcode-0.1.0.dist-info/METADATA +145 -0
- pcode-0.1.0.dist-info/RECORD +163 -0
- pcode-0.1.0.dist-info/WHEEL +4 -0
- pcode-0.1.0.dist-info/entry_points.txt +3 -0
- pcode-0.1.0.dist-info/licenses/LICENSE +21 -0
pcode/syntax_colors.py
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""Derive prompt-chrome colors from the selected Pygments style.
|
|
2
|
+
|
|
3
|
+
A Pygments style is written for code sitting on its own background, not for
|
|
4
|
+
UI chrome, so nothing in it can be trusted as-is:
|
|
5
|
+
|
|
6
|
+
- Token colors are optional. `gruvbox-light` leaves `Token.Text` uncolored.
|
|
7
|
+
- `highlight_color` is only loosely related to the background. `gruvbox-dark`
|
|
8
|
+
pairs a near-white highlight (#ebdbb2) with near-white text (#dddddd), so
|
|
9
|
+
using it as the selected-row background would hide the row.
|
|
10
|
+
- Nothing forces the style saved for the dark palette to actually be dark:
|
|
11
|
+
`/syntax solarized-light` is accepted while `/theme` is dark.
|
|
12
|
+
|
|
13
|
+
Every derived color is therefore checked against the surface it will be
|
|
14
|
+
painted on, and falls back to the palette's own value (then to a blend) when
|
|
15
|
+
the contrast is too low. Brightness uses the same weighting as
|
|
16
|
+
`theme.background_theme`, so "is this light or dark" means one thing here.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import re
|
|
20
|
+
from collections.abc import Iterable, Mapping
|
|
21
|
+
|
|
22
|
+
from pygments.styles import get_style_by_name
|
|
23
|
+
from pygments.token import Token
|
|
24
|
+
from pygments.util import ClassNotFound
|
|
25
|
+
|
|
26
|
+
# The fields a Palette is built from; every one of them is produced below.
|
|
27
|
+
FIELDS = (
|
|
28
|
+
"accent",
|
|
29
|
+
"muted",
|
|
30
|
+
"surface",
|
|
31
|
+
"foreground",
|
|
32
|
+
"selected",
|
|
33
|
+
"task_heading",
|
|
34
|
+
"success",
|
|
35
|
+
"agents",
|
|
36
|
+
)
|
|
37
|
+
# Where each sub-agent hue comes from, in ring order. Kinds of token a style
|
|
38
|
+
# tends to colour apart from functions (the accent) and strings (success).
|
|
39
|
+
_AGENT_TOKENS = (
|
|
40
|
+
(Token.Keyword, Token.Name.Class),
|
|
41
|
+
(Token.Literal.Number, Token.Name.Builtin),
|
|
42
|
+
(Token.Name.Tag, Token.Name.Decorator),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
_HEX = re.compile(r"^#?(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$")
|
|
46
|
+
_WEIGHTS = (0.299, 0.587, 0.114)
|
|
47
|
+
|
|
48
|
+
# Minimum brightness gap between a color and the surface behind it. Text has
|
|
49
|
+
# to be read, so it demands more than a background band that only has to be
|
|
50
|
+
# seen. Both are deliberately loose: the aim is to catch invisible pairings,
|
|
51
|
+
# not to impose a contrast standard on a style the user chose on purpose.
|
|
52
|
+
_TEXT_GAP = 0.3
|
|
53
|
+
_ACCENT_GAP = 0.18
|
|
54
|
+
_BAND_GAP = 0.04
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _normalize(color: str | None) -> str | None:
|
|
58
|
+
"""Return `#rrggbb`, or None when the style left the value unusable."""
|
|
59
|
+
if not color or not _HEX.match(color):
|
|
60
|
+
return None
|
|
61
|
+
digits = color.lstrip("#")
|
|
62
|
+
if len(digits) == 3:
|
|
63
|
+
digits = "".join(digit * 2 for digit in digits)
|
|
64
|
+
return f"#{digits.lower()}"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _rgb(color: str) -> tuple[float, ...]:
|
|
68
|
+
digits = color.lstrip("#")
|
|
69
|
+
return tuple(int(digits[index : index + 2], 16) / 255 for index in (0, 2, 4))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _brightness(color: str) -> float:
|
|
73
|
+
return sum(value * weight for value, weight in zip(_rgb(color), _WEIGHTS))
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _mix(color: str, other: str, weight: float) -> str:
|
|
77
|
+
blended = (
|
|
78
|
+
round((left * (1 - weight) + right * weight) * 255)
|
|
79
|
+
for left, right in zip(_rgb(color), _rgb(other))
|
|
80
|
+
)
|
|
81
|
+
return "#" + "".join(f"{value:02x}" for value in blended)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _pick(
|
|
85
|
+
candidates: Iterable[str | None],
|
|
86
|
+
requirements: Iterable[tuple[str, float]],
|
|
87
|
+
default: str,
|
|
88
|
+
) -> str:
|
|
89
|
+
"""First candidate keeping the required brightness gap from every backdrop."""
|
|
90
|
+
requirements = tuple(requirements)
|
|
91
|
+
for candidate in candidates:
|
|
92
|
+
color = _normalize(candidate)
|
|
93
|
+
if color and all(
|
|
94
|
+
abs(_brightness(color) - _brightness(backdrop)) >= gap for backdrop, gap in requirements
|
|
95
|
+
):
|
|
96
|
+
return color
|
|
97
|
+
return default
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _token(style, token) -> str | None:
|
|
101
|
+
return dict(style).get(token, {}).get("color")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def derive_colors(style_name: str, fallback: Mapping, backdrop: str | None = None) -> dict:
|
|
105
|
+
"""Palette field values for `style_name`, backed by `fallback`'s values.
|
|
106
|
+
|
|
107
|
+
Without a `backdrop` the colors are meant for the popup, which paints the
|
|
108
|
+
style's own background behind them. Prompt chrome has no such background:
|
|
109
|
+
it sits on whatever the terminal paints, and a style chosen for the other
|
|
110
|
+
appearance would vanish there (`bw` hands out near-black on a dark
|
|
111
|
+
terminal). Pass the background it will actually land on, and the accents
|
|
112
|
+
are checked against that instead, falling back to the palette's own.
|
|
113
|
+
|
|
114
|
+
An unknown style name yields the fallback unchanged: a saved preference
|
|
115
|
+
can outlive the Pygments plugin that provided the style.
|
|
116
|
+
"""
|
|
117
|
+
try:
|
|
118
|
+
style = get_style_by_name(style_name)
|
|
119
|
+
except ClassNotFound:
|
|
120
|
+
return {field: fallback[field] for field in FIELDS}
|
|
121
|
+
|
|
122
|
+
surface = _normalize(style.background_color) or fallback["surface"]
|
|
123
|
+
foreground = _pick(
|
|
124
|
+
(_token(style, Token.Text), fallback["foreground"]),
|
|
125
|
+
((surface, _TEXT_GAP),),
|
|
126
|
+
"#ffffff" if _brightness(surface) < 0.5 else "#1c1c1c",
|
|
127
|
+
)
|
|
128
|
+
# The selected row is a background: it only has to separate from the
|
|
129
|
+
# surface, but whatever is written on it still has to stay readable. The
|
|
130
|
+
# palette's own `selected` is not a candidate -- it belongs to a different
|
|
131
|
+
# background, so a blend off this style's surface stays more coherent.
|
|
132
|
+
selected = _pick(
|
|
133
|
+
(style.highlight_color, getattr(style, "line_number_background_color", None)),
|
|
134
|
+
((surface, _BAND_GAP), (foreground, _TEXT_GAP)),
|
|
135
|
+
_mix(surface, foreground, 0.25),
|
|
136
|
+
)
|
|
137
|
+
# An accent is written on the surface and on the selected row both, unless
|
|
138
|
+
# the caller named the one background that really is behind it.
|
|
139
|
+
if backdrop is None:
|
|
140
|
+
on_surface = ((surface, _ACCENT_GAP),)
|
|
141
|
+
on_selection = ((surface, _ACCENT_GAP), (selected, _ACCENT_GAP))
|
|
142
|
+
last_resort = {field: foreground for field in ("accent", "task_heading", "success")}
|
|
143
|
+
last_resort["muted"] = _mix(foreground, surface, 0.45)
|
|
144
|
+
last_resort["agents"] = (foreground,) * len(_AGENT_TOKENS)
|
|
145
|
+
else:
|
|
146
|
+
on_surface = on_selection = ((backdrop, _ACCENT_GAP),)
|
|
147
|
+
last_resort = {
|
|
148
|
+
field: fallback[field] for field in ("accent", "muted", "task_heading", "success")
|
|
149
|
+
}
|
|
150
|
+
last_resort["agents"] = fallback["agents"]
|
|
151
|
+
accent = _pick(
|
|
152
|
+
(
|
|
153
|
+
_token(style, Token.Name.Function),
|
|
154
|
+
_token(style, Token.Keyword),
|
|
155
|
+
fallback["accent"],
|
|
156
|
+
),
|
|
157
|
+
on_selection,
|
|
158
|
+
last_resort["accent"],
|
|
159
|
+
)
|
|
160
|
+
# The tick and each hue must differ from the accent and from each other,
|
|
161
|
+
# or a sub-agent would merge with the active task (or another sub-agent).
|
|
162
|
+
# Monochrome styles (`algol`) colour everything alike, so the palette's
|
|
163
|
+
# own value is the usual answer there.
|
|
164
|
+
taken = {accent}
|
|
165
|
+
|
|
166
|
+
def distinct(candidates, resort):
|
|
167
|
+
color = _pick((c for c in candidates if _normalize(c) not in taken), on_surface, resort)
|
|
168
|
+
taken.add(color)
|
|
169
|
+
return color
|
|
170
|
+
|
|
171
|
+
success = distinct(
|
|
172
|
+
(_token(style, Token.Literal.String), fallback["success"]), last_resort["success"]
|
|
173
|
+
)
|
|
174
|
+
agents = [
|
|
175
|
+
distinct([*(_token(style, token) for token in tokens), default], resort)
|
|
176
|
+
for tokens, default, resort in zip(_AGENT_TOKENS, fallback["agents"], last_resort["agents"])
|
|
177
|
+
]
|
|
178
|
+
return {
|
|
179
|
+
"surface": surface,
|
|
180
|
+
"foreground": foreground,
|
|
181
|
+
"selected": selected,
|
|
182
|
+
"success": success,
|
|
183
|
+
"agents": tuple(agents),
|
|
184
|
+
"accent": accent,
|
|
185
|
+
"muted": _pick(
|
|
186
|
+
(_token(style, Token.Comment), fallback["muted"]),
|
|
187
|
+
on_surface,
|
|
188
|
+
last_resort["muted"],
|
|
189
|
+
),
|
|
190
|
+
# Apart from the accent, since the status row's detail and the session
|
|
191
|
+
# name wear it beside the accented phase. Not added to `taken`: the
|
|
192
|
+
# first sub-agent hue shares the Keyword colour on purpose.
|
|
193
|
+
"task_heading": _pick(
|
|
194
|
+
(
|
|
195
|
+
color
|
|
196
|
+
for color in (
|
|
197
|
+
_token(style, Token.Keyword),
|
|
198
|
+
_token(style, Token.Name.Class),
|
|
199
|
+
fallback["task_heading"],
|
|
200
|
+
)
|
|
201
|
+
if _normalize(color) != accent
|
|
202
|
+
),
|
|
203
|
+
on_surface,
|
|
204
|
+
last_resort["task_heading"],
|
|
205
|
+
),
|
|
206
|
+
}
|
pcode/task_prompt.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Literal, quote-styled submitted prompts."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from rich.console import Console, ConsoleOptions, RenderResult
|
|
6
|
+
from rich.segment import Segment
|
|
7
|
+
from rich.text import Text
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class TaskPrompt:
|
|
12
|
+
text: str
|
|
13
|
+
|
|
14
|
+
def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
|
|
15
|
+
# Popups render prompts on a console that may carry no pcode theme.
|
|
16
|
+
style = console.get_style("pcode.accent", default="none")
|
|
17
|
+
# Leave room for a quote rail on every physical line, including wraps.
|
|
18
|
+
# On one-column terminals prioritize the text rather than the decoration.
|
|
19
|
+
prefix = "▌ " if options.max_width > 2 else ""
|
|
20
|
+
lines = console.render_lines(
|
|
21
|
+
Text(self.text, style=style),
|
|
22
|
+
options.update(width=max(1, options.max_width - len(prefix))),
|
|
23
|
+
pad=False,
|
|
24
|
+
)
|
|
25
|
+
for line in lines:
|
|
26
|
+
yield Segment(prefix, style)
|
|
27
|
+
yield from line
|
|
28
|
+
yield Segment.line()
|