pocket-coding 0.2.2__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.
- {pocket_coding-0.2.2 → pocket_coding-0.3.0}/PKG-INFO +1 -1
- {pocket_coding-0.2.2 → pocket_coding-0.3.0}/poco/__init__.py +1 -1
- pocket_coding-0.3.0/poco/app.py +124 -0
- {pocket_coding-0.2.2 → pocket_coding-0.3.0}/poco/config/__init__.py +4 -0
- {pocket_coding-0.2.2 → pocket_coding-0.3.0}/poco/config/store.py +74 -0
- {pocket_coding-0.2.2 → pocket_coding-0.3.0}/poco/providers/__init__.py +6 -0
- {pocket_coding-0.2.2 → pocket_coding-0.3.0}/poco/providers/codex.py +1 -1
- pocket_coding-0.3.0/poco/providers/cursor.py +424 -0
- {pocket_coding-0.2.2 → pocket_coding-0.3.0}/poco/providers/models.py +10 -0
- {pocket_coding-0.2.2 → pocket_coding-0.3.0}/poco/relay/app.py +38 -5
- {pocket_coding-0.2.2 → pocket_coding-0.3.0}/poco/relay/cards.py +33 -22
- {pocket_coding-0.2.2 → pocket_coding-0.3.0}/poco/relay/messenger.py +1 -1
- {pocket_coding-0.2.2 → pocket_coding-0.3.0}/poco/relay/models.py +4 -0
- {pocket_coding-0.2.2 → pocket_coding-0.3.0}/poco/relay/runtime.py +25 -34
- {pocket_coding-0.2.2 → pocket_coding-0.3.0}/poco/relay/stores.py +3 -1
- {pocket_coding-0.2.2 → pocket_coding-0.3.0}/poco/runtime.py +10 -0
- pocket_coding-0.3.0/poco/tui/app.py +1119 -0
- pocket_coding-0.3.0/poco/tui/resources.py +192 -0
- pocket_coding-0.3.0/poco/tui/sections.py +219 -0
- pocket_coding-0.3.0/poco/tui/state.py +121 -0
- {pocket_coding-0.2.2 → pocket_coding-0.3.0}/pyproject.toml +1 -1
- pocket_coding-0.2.2/poco/app.py +0 -86
- pocket_coding-0.2.2/poco/bootstrap/__init__.py +0 -5
- pocket_coding-0.2.2/poco/bootstrap/feishu.py +0 -303
- pocket_coding-0.2.2/poco/tui/app.py +0 -1002
- pocket_coding-0.2.2/poco/tui/menus/__init__.py +0 -9
- pocket_coding-0.2.2/poco/tui/menus/config/__init__.py +0 -37
- pocket_coding-0.2.2/poco/tui/menus/config/claude.py +0 -513
- pocket_coding-0.2.2/poco/tui/menus/config/controller.py +0 -233
- pocket_coding-0.2.2/poco/tui/menus/config/render_base.py +0 -119
- pocket_coding-0.2.2/poco/tui/menus/config/render_forms.py +0 -56
- pocket_coding-0.2.2/poco/tui/menus/config/sections.py +0 -199
- pocket_coding-0.2.2/poco/tui/menus/config/types.py +0 -117
- pocket_coding-0.2.2/poco/tui/menus/root.py +0 -34
- pocket_coding-0.2.2/poco/tui/resources.py +0 -315
- {pocket_coding-0.2.2 → pocket_coding-0.3.0}/.gitignore +0 -0
- {pocket_coding-0.2.2 → pocket_coding-0.3.0}/README.md +0 -0
- {pocket_coding-0.2.2 → pocket_coding-0.3.0}/README.zh-CN.md +0 -0
- {pocket_coding-0.2.2 → pocket_coding-0.3.0}/poco/providers/base.py +0 -0
- {pocket_coding-0.2.2 → pocket_coding-0.3.0}/poco/providers/claude.py +0 -0
- {pocket_coding-0.2.2 → pocket_coding-0.3.0}/poco/relay/__init__.py +0 -0
- {pocket_coding-0.2.2 → pocket_coding-0.3.0}/poco/relay/utils.py +0 -0
- {pocket_coding-0.2.2 → pocket_coding-0.3.0}/poco/tui/__init__.py +0 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import argparse
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from logging.handlers import RotatingFileHandler
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from .runtime import (
|
|
10
|
+
PoCoService,
|
|
11
|
+
RingLogHandler,
|
|
12
|
+
)
|
|
13
|
+
from .config import ConfigStore, bind_workspace, build_paths, ensure_dirs, get_nested, workspace_binding
|
|
14
|
+
from .tui import PoCoTui
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def parse_args() -> argparse.Namespace:
|
|
18
|
+
parser = argparse.ArgumentParser(description="PoCo")
|
|
19
|
+
parser.add_argument("--log-level", default="INFO")
|
|
20
|
+
sub = parser.add_subparsers(dest="command")
|
|
21
|
+
config_cmd = sub.add_parser("config", help="open PoCo and focus config tab")
|
|
22
|
+
config_cmd.add_argument("--show", action="store_true", help="show masked config and exit")
|
|
23
|
+
config_cmd.add_argument("key", nargs="?", help="config key, e.g. app_id or feishu.app_id")
|
|
24
|
+
config_cmd.add_argument("value", nargs="?", help="config value")
|
|
25
|
+
return parser.parse_args()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _env_flag(name: str) -> bool:
|
|
29
|
+
value = os.getenv(name, "").strip().lower()
|
|
30
|
+
return value in {"1", "true", "yes", "on"}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _env_feishu_binding() -> tuple[str, str, str] | None:
|
|
34
|
+
if not _env_flag("POCO_BIND_SKIP"):
|
|
35
|
+
return None
|
|
36
|
+
app_id = os.getenv("POCO_FEISHU_APP_ID", "").strip()
|
|
37
|
+
app_secret = os.getenv("POCO_FEISHU_APP_SECRET", "").strip()
|
|
38
|
+
if not app_id or not app_secret:
|
|
39
|
+
return None
|
|
40
|
+
alias = os.getenv("POCO_FEISHU_ALIAS", "").strip()
|
|
41
|
+
return app_id, app_secret, alias
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _apply_env_binding(workspace: Path, app_id: str, app_secret: str, alias: str) -> str:
|
|
45
|
+
bind_workspace(workspace, app_id)
|
|
46
|
+
paths = build_paths(app_id)
|
|
47
|
+
ensure_dirs(paths)
|
|
48
|
+
store = ConfigStore(paths.config_path, paths)
|
|
49
|
+
config = store.load()
|
|
50
|
+
feishu = config.setdefault("feishu", {})
|
|
51
|
+
feishu["app_id"] = app_id
|
|
52
|
+
feishu["app_secret"] = app_secret
|
|
53
|
+
if alias:
|
|
54
|
+
feishu["alias"] = alias
|
|
55
|
+
store.save(config)
|
|
56
|
+
return app_id
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def main() -> None:
|
|
60
|
+
args = parse_args()
|
|
61
|
+
workspace = Path.cwd()
|
|
62
|
+
env_binding = _env_feishu_binding()
|
|
63
|
+
binding = workspace_binding(workspace)
|
|
64
|
+
if env_binding is not None:
|
|
65
|
+
binding = _apply_env_binding(workspace, *env_binding)
|
|
66
|
+
root_logger = logging.getLogger()
|
|
67
|
+
root_logger.setLevel(getattr(logging, str(getattr(args, "log_level", "INFO")).upper(), logging.INFO))
|
|
68
|
+
for handler in list(root_logger.handlers):
|
|
69
|
+
if isinstance(handler, logging.StreamHandler) and not isinstance(handler, logging.FileHandler):
|
|
70
|
+
root_logger.removeHandler(handler)
|
|
71
|
+
ring = RingLogHandler()
|
|
72
|
+
ring.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(name)s: %(message)s"))
|
|
73
|
+
root_logger.addHandler(ring)
|
|
74
|
+
file_handler: RotatingFileHandler | None = None
|
|
75
|
+
|
|
76
|
+
def build_service_for(app_id: str | None) -> PoCoService:
|
|
77
|
+
nonlocal file_handler
|
|
78
|
+
paths = build_paths(app_id or "default")
|
|
79
|
+
ensure_dirs(paths)
|
|
80
|
+
if file_handler is not None:
|
|
81
|
+
root_logger.removeHandler(file_handler)
|
|
82
|
+
file_handler.close()
|
|
83
|
+
file_handler = RotatingFileHandler(
|
|
84
|
+
paths.log_path,
|
|
85
|
+
maxBytes=2_000_000,
|
|
86
|
+
backupCount=3,
|
|
87
|
+
encoding="utf-8",
|
|
88
|
+
mode="w",
|
|
89
|
+
)
|
|
90
|
+
file_handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(name)s: %(message)s"))
|
|
91
|
+
root_logger.addHandler(file_handler)
|
|
92
|
+
logging.getLogger("poco").info(
|
|
93
|
+
"Persistent log file enabled at %s (instance=%s)",
|
|
94
|
+
paths.log_path,
|
|
95
|
+
app_id or "default",
|
|
96
|
+
)
|
|
97
|
+
store = ConfigStore(paths.config_path, paths)
|
|
98
|
+
return PoCoService(store, ring, paths)
|
|
99
|
+
|
|
100
|
+
service = build_service_for(binding)
|
|
101
|
+
|
|
102
|
+
if args.command == "config" and args.show:
|
|
103
|
+
print(json.dumps(service.masked_config(), ensure_ascii=False, indent=2))
|
|
104
|
+
return
|
|
105
|
+
if args.command == "config" and args.key:
|
|
106
|
+
if args.value is None:
|
|
107
|
+
raise SystemExit("usage: poco config <key> <value>")
|
|
108
|
+
path, _ = service.set_config_value(args.key, args.value)
|
|
109
|
+
current = get_nested(service.masked_config(), path)
|
|
110
|
+
print(json.dumps({path: current}, ensure_ascii=False, indent=2))
|
|
111
|
+
return
|
|
112
|
+
app = PoCoTui(
|
|
113
|
+
service,
|
|
114
|
+
service_factory=build_service_for,
|
|
115
|
+
focus_config=args.command == "config",
|
|
116
|
+
skip_bind_on_boot=env_binding is not None,
|
|
117
|
+
)
|
|
118
|
+
result = app.run()
|
|
119
|
+
if result == "restart":
|
|
120
|
+
os.execv(sys.executable, [sys.executable, *sys.argv])
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
if __name__ == "__main__":
|
|
124
|
+
main()
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from .store import (
|
|
2
2
|
PoCoPaths,
|
|
3
|
+
SavedFeishuBot,
|
|
3
4
|
WORKSPACE_BINDINGS_PATH,
|
|
4
5
|
CONFIG_KEY_ALIASES,
|
|
5
6
|
CONFIG_PATH,
|
|
@@ -22,12 +23,14 @@ from .store import (
|
|
|
22
23
|
normalize_config_key,
|
|
23
24
|
parse_config_value,
|
|
24
25
|
saved_feishu_bot_ids,
|
|
26
|
+
saved_feishu_bots,
|
|
25
27
|
set_nested,
|
|
26
28
|
workspace_binding,
|
|
27
29
|
)
|
|
28
30
|
|
|
29
31
|
__all__ = [
|
|
30
32
|
"PoCoPaths",
|
|
33
|
+
"SavedFeishuBot",
|
|
31
34
|
"WORKSPACE_BINDINGS_PATH",
|
|
32
35
|
"CONFIG_KEY_ALIASES",
|
|
33
36
|
"CONFIG_PATH",
|
|
@@ -50,6 +53,7 @@ __all__ = [
|
|
|
50
53
|
"normalize_config_key",
|
|
51
54
|
"parse_config_value",
|
|
52
55
|
"saved_feishu_bot_ids",
|
|
56
|
+
"saved_feishu_bots",
|
|
53
57
|
"set_nested",
|
|
54
58
|
"workspace_binding",
|
|
55
59
|
]
|
|
@@ -27,6 +27,13 @@ class PoCoPaths:
|
|
|
27
27
|
relay_lock_path: Path
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
@dataclass(frozen=True)
|
|
31
|
+
class SavedFeishuBot:
|
|
32
|
+
app_id: str
|
|
33
|
+
app_name: str = ""
|
|
34
|
+
alias: str = ""
|
|
35
|
+
|
|
36
|
+
|
|
30
37
|
def normalize_instance_name(instance: str) -> str:
|
|
31
38
|
value = (instance or "").strip()
|
|
32
39
|
if not value:
|
|
@@ -65,6 +72,8 @@ LOG_PATH = _DEFAULT_PATHS.log_path
|
|
|
65
72
|
|
|
66
73
|
DEFAULT_CONFIG: Dict[str, Any] = {
|
|
67
74
|
"feishu": {
|
|
75
|
+
"alias": "",
|
|
76
|
+
"app_name": "",
|
|
68
77
|
"app_id": "",
|
|
69
78
|
"app_secret": "",
|
|
70
79
|
"encrypt_key": "",
|
|
@@ -81,6 +90,13 @@ DEFAULT_CONFIG: Dict[str, Any] = {
|
|
|
81
90
|
"approval_policy": "never",
|
|
82
91
|
"sandbox": "danger-full-access",
|
|
83
92
|
},
|
|
93
|
+
"cursor": {
|
|
94
|
+
"bin": "cursor-agent",
|
|
95
|
+
"app_server_args": "",
|
|
96
|
+
"model": "auto",
|
|
97
|
+
"approval_policy": "force",
|
|
98
|
+
"sandbox": "disabled",
|
|
99
|
+
},
|
|
84
100
|
"claude": {
|
|
85
101
|
"bin": "claude",
|
|
86
102
|
"app_server_args": "",
|
|
@@ -129,6 +145,7 @@ DEFAULT_CONFIG: Dict[str, Any] = {
|
|
|
129
145
|
}
|
|
130
146
|
|
|
131
147
|
INPUT_IDS = {
|
|
148
|
+
"feishu.alias": "feishu_alias",
|
|
132
149
|
"feishu.app_id": "feishu_app_id",
|
|
133
150
|
"feishu.app_secret": "feishu_app_secret",
|
|
134
151
|
"feishu.encrypt_key": "feishu_encrypt_key",
|
|
@@ -141,6 +158,11 @@ INPUT_IDS = {
|
|
|
141
158
|
"codex.reasoning_effort": "codex_reasoning_effort",
|
|
142
159
|
"codex.approval_policy": "codex_approval_policy",
|
|
143
160
|
"codex.sandbox": "codex_sandbox",
|
|
161
|
+
"cursor.bin": "cursor_bin",
|
|
162
|
+
"cursor.app_server_args": "cursor_app_server_args",
|
|
163
|
+
"cursor.model": "cursor_model",
|
|
164
|
+
"cursor.approval_policy": "cursor_approval_policy",
|
|
165
|
+
"cursor.sandbox": "cursor_sandbox",
|
|
144
166
|
"claude.bin": "claude_bin",
|
|
145
167
|
"claude.app_server_args": "claude_app_server_args",
|
|
146
168
|
"claude.approval_policy": "claude_approval_policy",
|
|
@@ -162,6 +184,7 @@ INPUT_IDS = {
|
|
|
162
184
|
}
|
|
163
185
|
|
|
164
186
|
CONFIG_KEY_ALIASES = {
|
|
187
|
+
"alias": "feishu.alias",
|
|
165
188
|
"app_id": "feishu.app_id",
|
|
166
189
|
"app_secret": "feishu.app_secret",
|
|
167
190
|
"encrypt_key": "feishu.encrypt_key",
|
|
@@ -175,6 +198,11 @@ CONFIG_KEY_ALIASES = {
|
|
|
175
198
|
"reasoning_effort": "codex.reasoning_effort",
|
|
176
199
|
"approval_policy": "codex.approval_policy",
|
|
177
200
|
"sandbox": "codex.sandbox",
|
|
201
|
+
"cursor_bin": "cursor.bin",
|
|
202
|
+
"cursor_app_server_args": "cursor.app_server_args",
|
|
203
|
+
"cursor_model": "cursor.model",
|
|
204
|
+
"cursor_approval_policy": "cursor.approval_policy",
|
|
205
|
+
"cursor_sandbox": "cursor.sandbox",
|
|
178
206
|
"claude_bin": "claude.bin",
|
|
179
207
|
"claude_app_server_args": "claude.app_server_args",
|
|
180
208
|
"claude_approval_policy": "claude.approval_policy",
|
|
@@ -210,12 +238,24 @@ def normalize_config(config: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
210
238
|
Returns:
|
|
211
239
|
The normalized config dictionary.
|
|
212
240
|
"""
|
|
241
|
+
feishu = config.setdefault("feishu", {})
|
|
242
|
+
feishu.setdefault("alias", "")
|
|
243
|
+
feishu.setdefault("app_name", "")
|
|
244
|
+
|
|
213
245
|
codex = config.setdefault("codex", {})
|
|
214
246
|
if not str(codex.get("model", "")).strip():
|
|
215
247
|
codex["model"] = DEFAULT_CONFIG["codex"]["model"]
|
|
216
248
|
if not str(codex.get("reasoning_effort", "")).strip():
|
|
217
249
|
codex["reasoning_effort"] = DEFAULT_CONFIG["codex"]["reasoning_effort"]
|
|
218
250
|
|
|
251
|
+
cursor = config.setdefault("cursor", {})
|
|
252
|
+
if not str(cursor.get("model", "")).strip():
|
|
253
|
+
cursor["model"] = DEFAULT_CONFIG["cursor"]["model"]
|
|
254
|
+
if not str(cursor.get("approval_policy", "")).strip():
|
|
255
|
+
cursor["approval_policy"] = DEFAULT_CONFIG["cursor"]["approval_policy"]
|
|
256
|
+
if not str(cursor.get("sandbox", "")).strip():
|
|
257
|
+
cursor["sandbox"] = DEFAULT_CONFIG["cursor"]["sandbox"]
|
|
258
|
+
|
|
219
259
|
claude = config.setdefault("claude", {})
|
|
220
260
|
if not str(claude.get("default_backend", "")).strip():
|
|
221
261
|
claude["default_backend"] = DEFAULT_CONFIG["claude"]["default_backend"]
|
|
@@ -337,6 +377,40 @@ def saved_feishu_bot_ids() -> list[str]:
|
|
|
337
377
|
return sorted(bot_ids)
|
|
338
378
|
|
|
339
379
|
|
|
380
|
+
def saved_feishu_bots() -> list[SavedFeishuBot]:
|
|
381
|
+
"""Returns saved Feishu bots with local alias and app name when available."""
|
|
382
|
+
bots: dict[str, SavedFeishuBot] = {}
|
|
383
|
+
|
|
384
|
+
def collect_from_config(path: Path, paths: PoCoPaths) -> None:
|
|
385
|
+
try:
|
|
386
|
+
config = ConfigStore(path, paths).load()
|
|
387
|
+
except Exception:
|
|
388
|
+
LOG.exception("Failed to load saved bot config from %s", path)
|
|
389
|
+
return
|
|
390
|
+
if not config_ready(config):
|
|
391
|
+
return
|
|
392
|
+
feishu = config.get("feishu", {})
|
|
393
|
+
app_id = str(feishu.get("app_id", "")).strip()
|
|
394
|
+
if not app_id:
|
|
395
|
+
return
|
|
396
|
+
bots[app_id] = SavedFeishuBot(
|
|
397
|
+
app_id=app_id,
|
|
398
|
+
app_name=str(feishu.get("app_name", "")).strip(),
|
|
399
|
+
alias=str(feishu.get("alias", "")).strip(),
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
if CONFIG_PATH.exists():
|
|
403
|
+
collect_from_config(CONFIG_PATH, _DEFAULT_PATHS)
|
|
404
|
+
|
|
405
|
+
bindings_root = CONFIG_ROOT / "bindings"
|
|
406
|
+
if bindings_root.exists():
|
|
407
|
+
for config_path in bindings_root.glob("*/config.json"):
|
|
408
|
+
instance = config_path.parent.name
|
|
409
|
+
collect_from_config(config_path, build_paths(instance))
|
|
410
|
+
|
|
411
|
+
return sorted(bots.values(), key=lambda item: (item.alias or item.app_name or item.app_id).lower())
|
|
412
|
+
|
|
413
|
+
|
|
340
414
|
def set_nested(config: Dict[str, Any], path: str, value: Any) -> Dict[str, Any]:
|
|
341
415
|
parts = path.split(".")
|
|
342
416
|
current = config
|
|
@@ -7,6 +7,7 @@ from .base import (
|
|
|
7
7
|
)
|
|
8
8
|
from .claude import ClaudeProviderClient, ClaudeSessionLocator
|
|
9
9
|
from .codex import CodexProviderClient, CodexSessionLocator
|
|
10
|
+
from .cursor import CursorProviderClient, CursorSessionLocator
|
|
10
11
|
from .models import model_choices
|
|
11
12
|
|
|
12
13
|
|
|
@@ -16,6 +17,8 @@ def build_provider_client(provider_name: str, provider_config: ProviderConfig, c
|
|
|
16
17
|
return CodexProviderClient(provider_config, cwd)
|
|
17
18
|
if provider == "claude":
|
|
18
19
|
return ClaudeProviderClient(provider_config, cwd)
|
|
20
|
+
if provider == "cursor":
|
|
21
|
+
return CursorProviderClient(provider_config, cwd)
|
|
19
22
|
raise RuntimeError(f"Unsupported provider: {provider_name}")
|
|
20
23
|
|
|
21
24
|
|
|
@@ -23,6 +26,7 @@ def build_session_locators() -> dict[str, SessionLocator]:
|
|
|
23
26
|
return {
|
|
24
27
|
"codex": CodexSessionLocator(),
|
|
25
28
|
"claude": ClaudeSessionLocator(),
|
|
29
|
+
"cursor": CursorSessionLocator(),
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
|
|
@@ -31,6 +35,8 @@ __all__ = [
|
|
|
31
35
|
"ClaudeSessionLocator",
|
|
32
36
|
"CodexProviderClient",
|
|
33
37
|
"CodexSessionLocator",
|
|
38
|
+
"CursorProviderClient",
|
|
39
|
+
"CursorSessionLocator",
|
|
34
40
|
"build_provider_client",
|
|
35
41
|
"build_session_locators",
|
|
36
42
|
"model_choices",
|
|
@@ -216,7 +216,7 @@ class CodexProviderClient:
|
|
|
216
216
|
self._request(
|
|
217
217
|
"initialize",
|
|
218
218
|
{
|
|
219
|
-
"clientInfo": {"name": "poco-provider-bridge", "version": "0.
|
|
219
|
+
"clientInfo": {"name": "poco-provider-bridge", "version": "0.3.0"},
|
|
220
220
|
"capabilities": {"experimentalApi": True},
|
|
221
221
|
},
|
|
222
222
|
)
|