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/appearance.py
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""Notice the terminal switching between light and dark while pcode runs.
|
|
2
|
+
|
|
3
|
+
Two triggers, both answered through `TerminalReplyParser` in the input stream:
|
|
4
|
+
|
|
5
|
+
- Mode 2031: terminals that support it (Ghostty, kitty, iTerm2, VTE, foot, tmux)
|
|
6
|
+
report `CSI ? 997 ; 1|2 n` whenever their color scheme changes. Works over
|
|
7
|
+
SSH and on any OS; terminals without it ignore the request.
|
|
8
|
+
- The desktop's own setting (macOS, or GNOME through `gsettings`), polled.
|
|
9
|
+
A change only prompts a fresh OSC 11 background query: what matters is the
|
|
10
|
+
terminal's background, and a terminal pinned to dark stays dark when the
|
|
11
|
+
desktop goes light. Skipped over SSH, where the desktop is someone else's.
|
|
12
|
+
|
|
13
|
+
Polling is a cheap in-process read on macOS, so the slower GNOME path is the
|
|
14
|
+
only one that spawns anything.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import asyncio
|
|
18
|
+
import os
|
|
19
|
+
import shutil
|
|
20
|
+
import subprocess
|
|
21
|
+
import sys
|
|
22
|
+
from collections.abc import Callable
|
|
23
|
+
from contextlib import contextmanager
|
|
24
|
+
|
|
25
|
+
from pcode.terminal_notify import _isatty, _write_all
|
|
26
|
+
|
|
27
|
+
POLL_SECONDS = 2.0
|
|
28
|
+
# The desktop setting can flip before the terminal repaints; ask again later
|
|
29
|
+
# too. Repeated identical answers change nothing.
|
|
30
|
+
QUERY_DELAYS = (0.3, 1.5)
|
|
31
|
+
ENABLE_REPORTS = b"\x1b[?2031h"
|
|
32
|
+
DISABLE_REPORTS = b"\x1b[?2031l"
|
|
33
|
+
QUERY_BACKGROUND = b"\x1b]11;?\x1b\\"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _macos_reader():
|
|
37
|
+
"""A function reading the global AppleInterfaceStyle, or None without CoreFoundation."""
|
|
38
|
+
import ctypes
|
|
39
|
+
import ctypes.util
|
|
40
|
+
|
|
41
|
+
path = ctypes.util.find_library("CoreFoundation")
|
|
42
|
+
if path is None:
|
|
43
|
+
return None
|
|
44
|
+
cf = ctypes.CDLL(path)
|
|
45
|
+
utf8 = 0x08000100
|
|
46
|
+
cf.CFStringCreateWithCString.restype = ctypes.c_void_p
|
|
47
|
+
cf.CFStringCreateWithCString.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_uint32]
|
|
48
|
+
cf.CFPreferencesCopyAppValue.restype = ctypes.c_void_p
|
|
49
|
+
cf.CFPreferencesCopyAppValue.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
|
|
50
|
+
cf.CFPreferencesAppSynchronize.argtypes = [ctypes.c_void_p]
|
|
51
|
+
cf.CFStringGetCString.restype = ctypes.c_bool
|
|
52
|
+
cf.CFGetTypeID.restype = ctypes.c_ulong
|
|
53
|
+
cf.CFGetTypeID.argtypes = [ctypes.c_void_p]
|
|
54
|
+
cf.CFStringGetTypeID.restype = ctypes.c_ulong
|
|
55
|
+
string_type = cf.CFStringGetTypeID()
|
|
56
|
+
cf.CFStringGetCString.argtypes = [
|
|
57
|
+
ctypes.c_void_p,
|
|
58
|
+
ctypes.c_char_p,
|
|
59
|
+
ctypes.c_long,
|
|
60
|
+
ctypes.c_uint32,
|
|
61
|
+
]
|
|
62
|
+
cf.CFRelease.argtypes = [ctypes.c_void_p]
|
|
63
|
+
key = cf.CFStringCreateWithCString(None, b"AppleInterfaceStyle", utf8)
|
|
64
|
+
domain = ctypes.c_void_p.in_dll(cf, "kCFPreferencesAnyApplication")
|
|
65
|
+
|
|
66
|
+
def read() -> str:
|
|
67
|
+
# Without the sync the process keeps its first cached answer.
|
|
68
|
+
cf.CFPreferencesAppSynchronize(domain)
|
|
69
|
+
value = cf.CFPreferencesCopyAppValue(key, domain)
|
|
70
|
+
if not value: # The key only exists while dark.
|
|
71
|
+
return "light"
|
|
72
|
+
buffer = ctypes.create_string_buffer(16)
|
|
73
|
+
# Any property list type can sit under the key; only a string is ours.
|
|
74
|
+
ok = cf.CFGetTypeID(value) == string_type and cf.CFStringGetCString(
|
|
75
|
+
value, buffer, len(buffer), utf8
|
|
76
|
+
)
|
|
77
|
+
cf.CFRelease(value)
|
|
78
|
+
return "dark" if ok and buffer.value == b"Dark" else "light"
|
|
79
|
+
|
|
80
|
+
return read
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _gnome_read() -> str | None:
|
|
84
|
+
try:
|
|
85
|
+
result = subprocess.run(
|
|
86
|
+
["gsettings", "get", "org.gnome.desktop.interface", "color-scheme"],
|
|
87
|
+
capture_output=True,
|
|
88
|
+
text=True,
|
|
89
|
+
timeout=2,
|
|
90
|
+
)
|
|
91
|
+
except (OSError, subprocess.SubprocessError):
|
|
92
|
+
return None
|
|
93
|
+
if result.returncode != 0:
|
|
94
|
+
return None
|
|
95
|
+
return "dark" if "dark" in result.stdout else "light"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def desktop_reader():
|
|
99
|
+
"""How to read the desktop's light/dark setting here, or None to not poll."""
|
|
100
|
+
if os.environ.get("SSH_CONNECTION") or os.environ.get("SSH_TTY"):
|
|
101
|
+
return None
|
|
102
|
+
try:
|
|
103
|
+
if sys.platform == "darwin":
|
|
104
|
+
return _macos_reader()
|
|
105
|
+
except (OSError, AttributeError, ValueError):
|
|
106
|
+
return None
|
|
107
|
+
if sys.platform.startswith("linux") and shutil.which("gsettings"):
|
|
108
|
+
if os.environ.get("DISPLAY") or os.environ.get("WAYLAND_DISPLAY"):
|
|
109
|
+
return _gnome_read
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class AppearanceWatch:
|
|
114
|
+
"""Ask the terminal for its appearance whenever it may have changed.
|
|
115
|
+
|
|
116
|
+
Answers arrive as input and are handled by the editor's input parser, so
|
|
117
|
+
this only writes requests and never reads.
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
def __init__(
|
|
121
|
+
self, fd: int | None, reader=None, active: Callable[[], bool] = lambda: True
|
|
122
|
+
) -> None:
|
|
123
|
+
self.fd = fd
|
|
124
|
+
self.reader = reader
|
|
125
|
+
# Desktop polling only matters while the theme is `auto`; mode 2031
|
|
126
|
+
# reports stay on regardless, so switching back to auto starts current.
|
|
127
|
+
self.active = active
|
|
128
|
+
self.enabled = False
|
|
129
|
+
self.suspended = False
|
|
130
|
+
|
|
131
|
+
def close(self) -> None:
|
|
132
|
+
if self.enabled and _write_all(self.fd, DISABLE_REPORTS):
|
|
133
|
+
self.enabled = False
|
|
134
|
+
|
|
135
|
+
@contextmanager
|
|
136
|
+
def paused(self):
|
|
137
|
+
"""Hand the terminal to another program: no reports or replies land in its input."""
|
|
138
|
+
enabled = self.enabled
|
|
139
|
+
self.close()
|
|
140
|
+
self.suspended = True
|
|
141
|
+
try:
|
|
142
|
+
yield
|
|
143
|
+
finally:
|
|
144
|
+
self.suspended = False
|
|
145
|
+
if enabled:
|
|
146
|
+
self.enabled = _write_all(self.fd, ENABLE_REPORTS)
|
|
147
|
+
|
|
148
|
+
async def query(self) -> None:
|
|
149
|
+
for delay in QUERY_DELAYS:
|
|
150
|
+
await asyncio.sleep(delay)
|
|
151
|
+
if not self.suspended:
|
|
152
|
+
_write_all(self.fd, QUERY_BACKGROUND)
|
|
153
|
+
|
|
154
|
+
async def run(self) -> None:
|
|
155
|
+
if self.fd is None or not _isatty(self.fd) or os.environ.get("TERM") == "dumb":
|
|
156
|
+
return
|
|
157
|
+
self.enabled = _write_all(self.fd, ENABLE_REPORTS)
|
|
158
|
+
reader = self.reader if self.reader is not None else desktop_reader()
|
|
159
|
+
pending = None
|
|
160
|
+
try:
|
|
161
|
+
if reader is None:
|
|
162
|
+
await asyncio.Future() # Mode 2031 only; disable it on the way out.
|
|
163
|
+
last = await asyncio.to_thread(reader)
|
|
164
|
+
while True:
|
|
165
|
+
await asyncio.sleep(POLL_SECONDS)
|
|
166
|
+
if not self.active():
|
|
167
|
+
# Keep `last`: a flip made meanwhile is still asked about
|
|
168
|
+
# on the first poll after switching back to auto.
|
|
169
|
+
continue
|
|
170
|
+
current = await asyncio.to_thread(reader)
|
|
171
|
+
if current is None:
|
|
172
|
+
continue
|
|
173
|
+
if last is not None and current != last:
|
|
174
|
+
if pending is not None:
|
|
175
|
+
pending.cancel()
|
|
176
|
+
pending = asyncio.create_task(self.query())
|
|
177
|
+
last = current
|
|
178
|
+
finally:
|
|
179
|
+
if pending is not None:
|
|
180
|
+
pending.cancel()
|
|
181
|
+
self.close()
|