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.
Files changed (33) hide show
  1. {ccoding-0.2.4 → ccoding-0.3.0}/PKG-INFO +2 -2
  2. {ccoding-0.2.4 → ccoding-0.3.0}/pyproject.toml +1 -1
  3. {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/api.py +10 -3
  4. {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/config.py +5 -0
  5. {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/remote.py +1 -1
  6. {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/remote_common.py +25 -3
  7. {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/ui.py +20 -0
  8. {ccoding-0.2.4 → ccoding-0.3.0}/uv.lock +1 -1
  9. {ccoding-0.2.4 → ccoding-0.3.0}/.github/workflows/publish.yml +0 -0
  10. {ccoding-0.2.4 → ccoding-0.3.0}/.gitignore +0 -0
  11. {ccoding-0.2.4 → ccoding-0.3.0}/CLAUDE.md +0 -0
  12. {ccoding-0.2.4 → ccoding-0.3.0}/LICENSE +0 -0
  13. {ccoding-0.2.4 → ccoding-0.3.0}/README.md +0 -0
  14. {ccoding-0.2.4 → ccoding-0.3.0}/screenshot.png +0 -0
  15. {ccoding-0.2.4 → ccoding-0.3.0}/screenshot_mobile1.jpeg +0 -0
  16. {ccoding-0.2.4 → ccoding-0.3.0}/screenshot_mobile2.jpeg +0 -0
  17. {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/__init__.py +0 -0
  18. {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/__main__.py +0 -0
  19. {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/cli.py +0 -0
  20. {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/env_schema.json +0 -0
  21. {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/logo.py +0 -0
  22. {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/remote_cli.py +0 -0
  23. {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/remote_client.py +0 -0
  24. {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/remote_server.py +0 -0
  25. {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/web/__init__.py +0 -0
  26. {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/web/app.js +0 -0
  27. {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/web/assets/addon-fit.js +0 -0
  28. {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/web/assets/xterm-LICENSE +0 -0
  29. {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/web/assets/xterm-addon-fit-LICENSE +0 -0
  30. {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/web/assets/xterm.css +0 -0
  31. {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/web/assets/xterm.js +0 -0
  32. {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/web/index.html +0 -0
  33. {ccoding-0.2.4 → ccoding-0.3.0}/src/ccode/web/style.css +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: ccoding
3
- Version: 0.2.4
3
+ Version: 0.3.0
4
4
  Summary: Curses-based Claude Code launcher
5
5
  Project-URL: Homepage, https://github.com/8DE4732A/ccode
6
6
  Project-URL: Repository, https://github.com/8DE4732A/ccode
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ccoding"
3
- version = "0.2.4"
3
+ version = "0.3.0"
4
4
  description = "Curses-based Claude Code launcher"
5
5
  readme = "README.md"
6
6
  license = { file = "LICENSE" }
@@ -125,8 +125,15 @@ def validate_launch_requirements(config: dict[str, Any]) -> str | None:
125
125
  return None
126
126
 
127
127
 
128
- def build_claude_command(args: list[str]) -> list[str]:
129
- return ["claude", *args]
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
- fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX)
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
- fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
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", {})
@@ -36,7 +36,7 @@ wheels = [
36
36
 
37
37
  [[package]]
38
38
  name = "ccoding"
39
- version = "0.2.2"
39
+ version = "0.3.0"
40
40
  source = { editable = "." }
41
41
  dependencies = [
42
42
  { name = "windows-curses", marker = "sys_platform == 'win32'" },
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