ccoding 0.2.4__tar.gz → 0.3.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.
- {ccoding-0.2.4 → ccoding-0.3.0}/PKG-INFO +2 -2
- {ccoding-0.2.4 → ccoding-0.3.0}/pyproject.toml +1 -1
- {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/api.py +10 -3
- {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/config.py +5 -0
- {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/remote.py +1 -1
- {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/remote_common.py +25 -3
- {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/ui.py +20 -0
- {ccoding-0.2.4 → ccoding-0.3.0}/uv.lock +1 -1
- {ccoding-0.2.4 → ccoding-0.3.0}/.github/workflows/publish.yml +0 -0
- {ccoding-0.2.4 → ccoding-0.3.0}/.gitignore +0 -0
- {ccoding-0.2.4 → ccoding-0.3.0}/CLAUDE.md +0 -0
- {ccoding-0.2.4 → ccoding-0.3.0}/LICENSE +0 -0
- {ccoding-0.2.4 → ccoding-0.3.0}/README.md +0 -0
- {ccoding-0.2.4 → ccoding-0.3.0}/screenshot.png +0 -0
- {ccoding-0.2.4 → ccoding-0.3.0}/screenshot_mobile1.jpeg +0 -0
- {ccoding-0.2.4 → ccoding-0.3.0}/screenshot_mobile2.jpeg +0 -0
- {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/__init__.py +0 -0
- {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/__main__.py +0 -0
- {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/cli.py +0 -0
- {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/env_schema.json +0 -0
- {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/logo.py +0 -0
- {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/remote_cli.py +0 -0
- {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/remote_client.py +0 -0
- {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/remote_server.py +0 -0
- {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/web/__init__.py +0 -0
- {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/web/app.js +0 -0
- {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/web/assets/addon-fit.js +0 -0
- {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/web/assets/xterm-LICENSE +0 -0
- {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/web/assets/xterm-addon-fit-LICENSE +0 -0
- {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/web/assets/xterm.css +0 -0
- {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/web/assets/xterm.js +0 -0
- {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/web/index.html +0 -0
- {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/web/style.css +0 -0
|
@@ -125,8 +125,15 @@ def validate_launch_requirements(config: dict[str, Any]) -> str | None:
|
|
|
125
125
|
return None
|
|
126
126
|
|
|
127
127
|
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
SKIP_PERMISSIONS_FLAG = "--allow-dangerously-skip-permissions"
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def build_claude_command(config: dict[str, Any], args: list[str]) -> list[str]:
|
|
132
|
+
command = ["claude", *args]
|
|
133
|
+
# args 透传自用户命令行,可能已手动携带该 flag,去重避免重复传参
|
|
134
|
+
if config.get("skip_permissions") and SKIP_PERMISSIONS_FLAG not in command:
|
|
135
|
+
command.append(SKIP_PERMISSIONS_FLAG)
|
|
136
|
+
return command
|
|
130
137
|
|
|
131
138
|
|
|
132
139
|
def launch_claude(config: dict[str, Any], args: list[str]) -> str | None:
|
|
@@ -136,7 +143,7 @@ def launch_claude(config: dict[str, Any], args: list[str]) -> str | None:
|
|
|
136
143
|
return "Missing base URL or API key."
|
|
137
144
|
env = build_env(config, masked=False)
|
|
138
145
|
try:
|
|
139
|
-
subprocess.run(build_claude_command(args), check=True, env=env)
|
|
146
|
+
subprocess.run(build_claude_command(config, args), check=True, env=env)
|
|
140
147
|
except FileNotFoundError:
|
|
141
148
|
return "Could not find 'claude' on PATH."
|
|
142
149
|
except subprocess.CalledProcessError as exc:
|
|
@@ -91,6 +91,7 @@ def default_config() -> dict[str, Any]:
|
|
|
91
91
|
"haiku": {"owned_by": None, "id": None},
|
|
92
92
|
},
|
|
93
93
|
"toggles": DEFAULT_TOGGLES.copy(),
|
|
94
|
+
"skip_permissions": False,
|
|
94
95
|
"remote": copy.deepcopy(DEFAULT_REMOTE),
|
|
95
96
|
}
|
|
96
97
|
|
|
@@ -223,6 +224,10 @@ def load_config() -> dict[str, Any]:
|
|
|
223
224
|
if new_toggles:
|
|
224
225
|
config["toggles"] = new_toggles
|
|
225
226
|
|
|
227
|
+
skip_permissions = data.get("skip_permissions")
|
|
228
|
+
if isinstance(skip_permissions, bool):
|
|
229
|
+
config["skip_permissions"] = skip_permissions
|
|
230
|
+
|
|
226
231
|
config["remote"] = normalize_remote(data.get("remote"))
|
|
227
232
|
|
|
228
233
|
return config
|
|
@@ -246,7 +246,7 @@ def ensure_tmux_session(config: dict[str, Any], args: list[str], session_name: s
|
|
|
246
246
|
|
|
247
247
|
env = build_env(config, masked=False)
|
|
248
248
|
result = subprocess.run(
|
|
249
|
-
["tmux", "new-session", "-d", "-s", session_name, *build_claude_command(args)],
|
|
249
|
+
["tmux", "new-session", "-d", "-s", session_name, *build_claude_command(config, args)],
|
|
250
250
|
env=env,
|
|
251
251
|
capture_output=True,
|
|
252
252
|
text=True,
|
|
@@ -3,11 +3,20 @@ from __future__ import annotations
|
|
|
3
3
|
import base64
|
|
4
4
|
import contextlib
|
|
5
5
|
import datetime as dt
|
|
6
|
-
import fcntl
|
|
7
6
|
import hashlib
|
|
8
7
|
import hmac
|
|
9
8
|
import os
|
|
10
9
|
import re
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
import fcntl
|
|
13
|
+
except ImportError:
|
|
14
|
+
fcntl = None
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
import msvcrt
|
|
18
|
+
except ImportError:
|
|
19
|
+
msvcrt = None
|
|
11
20
|
import secrets
|
|
12
21
|
import time
|
|
13
22
|
from dataclasses import dataclass
|
|
@@ -209,11 +218,24 @@ def web_asset_text(name: str) -> str:
|
|
|
209
218
|
def file_lock(path: Any) -> Iterator[None]:
|
|
210
219
|
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
211
220
|
with path.open("a+") as lock_file:
|
|
212
|
-
|
|
221
|
+
if msvcrt is not None:
|
|
222
|
+
if path.stat().st_size == 0:
|
|
223
|
+
lock_file.write("0")
|
|
224
|
+
lock_file.flush()
|
|
225
|
+
lock_file.seek(0)
|
|
226
|
+
getattr(msvcrt, "locking")(lock_file.fileno(), getattr(msvcrt, "LK_LOCK"), 1)
|
|
227
|
+
elif fcntl is not None:
|
|
228
|
+
fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX)
|
|
229
|
+
else:
|
|
230
|
+
raise OSError("file locking is not supported on this platform")
|
|
213
231
|
try:
|
|
214
232
|
yield
|
|
215
233
|
finally:
|
|
216
|
-
|
|
234
|
+
if msvcrt is not None:
|
|
235
|
+
lock_file.seek(0)
|
|
236
|
+
getattr(msvcrt, "locking")(lock_file.fileno(), getattr(msvcrt, "LK_UNLCK"), 1)
|
|
237
|
+
elif fcntl is not None:
|
|
238
|
+
fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
|
|
217
239
|
|
|
218
240
|
|
|
219
241
|
def rotate_log(path: Any, max_bytes: int, backups: int) -> None:
|
|
@@ -38,6 +38,7 @@ from .remote_common import remote_display_url, remote_local_host_port, remote_mo
|
|
|
38
38
|
# 配置界面 field 类型标识
|
|
39
39
|
_FIELD_BASE_URL = "base_url"
|
|
40
40
|
_FIELD_API_KEY = "api_key"
|
|
41
|
+
_FIELD_SKIP_PERMISSIONS = "skip_permissions"
|
|
41
42
|
_FIELD_REMOTE_ENABLED = "remote.enabled"
|
|
42
43
|
_FIELD_REMOTE_MODE = "remote.mode"
|
|
43
44
|
_FIELD_REMOTE_LOCAL_HOST = "remote.local.host"
|
|
@@ -151,6 +152,7 @@ class CursesApp:
|
|
|
151
152
|
_FIELD_REMOTE_SESSION_NAME,
|
|
152
153
|
_FIELD_REMOTE_REUSE_SESSION,
|
|
153
154
|
]
|
|
155
|
+
fields.append(_FIELD_SKIP_PERMISSIONS)
|
|
154
156
|
for k in toggles:
|
|
155
157
|
fields.append(f"{_FIELD_TOGGLE}{k}")
|
|
156
158
|
fields.append(_FIELD_ADD_TOGGLE)
|
|
@@ -398,6 +400,18 @@ class CursesApp:
|
|
|
398
400
|
remote_rows[field] = (y, value_x, display)
|
|
399
401
|
y += 1
|
|
400
402
|
|
|
403
|
+
y += 1
|
|
404
|
+
addstr_safe(stdscr, y, x, "─── Launch [space/enter]=toggle ──────────────────────────────────────")
|
|
405
|
+
y += 1
|
|
406
|
+
launch_items = [
|
|
407
|
+
(_FIELD_SKIP_PERMISSIONS, "SKIP PERMISSIONS:", "on" if self.config.get("skip_permissions") else "off"),
|
|
408
|
+
]
|
|
409
|
+
for field, label, display in launch_items:
|
|
410
|
+
is_focused = focused == field
|
|
411
|
+
addstr_safe(stdscr, y, x, label)
|
|
412
|
+
addstr_safe(stdscr, y, x + 22, display, curses.A_REVERSE if is_focused else 0)
|
|
413
|
+
y += 1
|
|
414
|
+
|
|
401
415
|
y += 1
|
|
402
416
|
addstr_safe(stdscr, y, x, "─── Toggles (env vars) [n]=new [d]=delete [ESC]=save & back ────────")
|
|
403
417
|
y += 1
|
|
@@ -560,6 +574,12 @@ class CursesApp:
|
|
|
560
574
|
self.config_focus_index = min(len(fields) - 1, self.config_focus_index + 1)
|
|
561
575
|
return
|
|
562
576
|
|
|
577
|
+
if focused == _FIELD_SKIP_PERMISSIONS:
|
|
578
|
+
if key in (curses.KEY_ENTER, 10, 13, ord(" ")):
|
|
579
|
+
self.config["skip_permissions"] = not bool(self.config.get("skip_permissions"))
|
|
580
|
+
save_config(self.config)
|
|
581
|
+
return
|
|
582
|
+
|
|
563
583
|
if focused in (_FIELD_REMOTE_ENABLED, _FIELD_REMOTE_REUSE_SESSION, _FIELD_REMOTE_SERVER_AUTO_CONNECT):
|
|
564
584
|
if key in (curses.KEY_ENTER, 10, 13, ord(" ")):
|
|
565
585
|
remote = self.config.setdefault("remote", {})
|
|
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
|
|
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
|