forgexa-cli 1.18.4__tar.gz → 1.18.5__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.
- {forgexa_cli-1.18.4 → forgexa_cli-1.18.5}/PKG-INFO +1 -1
- {forgexa_cli-1.18.4 → forgexa_cli-1.18.5}/forgexa_cli/__init__.py +1 -1
- {forgexa_cli-1.18.4 → forgexa_cli-1.18.5}/forgexa_cli/daemon.py +79 -12
- {forgexa_cli-1.18.4 → forgexa_cli-1.18.5}/forgexa_cli.egg-info/PKG-INFO +1 -1
- {forgexa_cli-1.18.4 → forgexa_cli-1.18.5}/pyproject.toml +1 -1
- {forgexa_cli-1.18.4 → forgexa_cli-1.18.5}/README.md +0 -0
- {forgexa_cli-1.18.4 → forgexa_cli-1.18.5}/forgexa_cli/_build_config.py +0 -0
- {forgexa_cli-1.18.4 → forgexa_cli-1.18.5}/forgexa_cli/main.py +0 -0
- {forgexa_cli-1.18.4 → forgexa_cli-1.18.5}/forgexa_cli/py.typed +0 -0
- {forgexa_cli-1.18.4 → forgexa_cli-1.18.5}/forgexa_cli.egg-info/SOURCES.txt +0 -0
- {forgexa_cli-1.18.4 → forgexa_cli-1.18.5}/forgexa_cli.egg-info/dependency_links.txt +0 -0
- {forgexa_cli-1.18.4 → forgexa_cli-1.18.5}/forgexa_cli.egg-info/entry_points.txt +0 -0
- {forgexa_cli-1.18.4 → forgexa_cli-1.18.5}/forgexa_cli.egg-info/requires.txt +0 -0
- {forgexa_cli-1.18.4 → forgexa_cli-1.18.5}/forgexa_cli.egg-info/top_level.txt +0 -0
- {forgexa_cli-1.18.4 → forgexa_cli-1.18.5}/setup.cfg +0 -0
- {forgexa_cli-1.18.4 → forgexa_cli-1.18.5}/tests/test_auth_and_runtime_commands.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""forgexa-cli — Forgexa command-line client."""
|
|
2
|
-
__version__ = "1.18.
|
|
2
|
+
__version__ = "1.18.5"
|
|
@@ -104,6 +104,34 @@ def _save_cli_tokens(access_token: str, refresh_token: str | None = None) -> Non
|
|
|
104
104
|
token_path.chmod(0o600)
|
|
105
105
|
|
|
106
106
|
|
|
107
|
+
def _extract_access_token_subject(token: str | None) -> str | None:
|
|
108
|
+
raw = str(token or "").strip()
|
|
109
|
+
if not raw:
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
parts = raw.split(".")
|
|
113
|
+
if len(parts) != 3:
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
payload = parts[1]
|
|
117
|
+
payload += "=" * (-len(payload) % 4)
|
|
118
|
+
try:
|
|
119
|
+
data = json.loads(base64.urlsafe_b64decode(payload.encode("utf-8")))
|
|
120
|
+
except Exception:
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
subject = str(data.get("sub") or "").strip()
|
|
124
|
+
if not subject:
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
UUID(subject)
|
|
129
|
+
except (TypeError, ValueError):
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
return subject
|
|
133
|
+
|
|
134
|
+
|
|
107
135
|
def _looks_like_pipx_environment(python_path: str | None = None) -> bool:
|
|
108
136
|
candidate = (python_path or sys.executable or "").replace("\\", "/").lower()
|
|
109
137
|
return "/pipx/venvs/" in candidate
|
|
@@ -527,7 +555,7 @@ except (ImportError, ModuleNotFoundError):
|
|
|
527
555
|
# DAEMON_VERSION is the protocol/logic version of the daemon code.
|
|
528
556
|
# Kept in sync with pyproject.toml version via bump-version.sh.
|
|
529
557
|
# CLIENT_TYPE identifies which packaging/distribution this daemon runs in.
|
|
530
|
-
DAEMON_VERSION = "1.18.
|
|
558
|
+
DAEMON_VERSION = "1.18.5"
|
|
531
559
|
|
|
532
560
|
|
|
533
561
|
def _detect_client_type() -> str:
|
|
@@ -3574,6 +3602,16 @@ class ProcessManager:
|
|
|
3574
3602
|
"not found the model", # decoded Kimi 404 body
|
|
3575
3603
|
"provider.api_error", # Kimi SDK error prefix
|
|
3576
3604
|
"kimi authentication required", # pre-flight oauth check sentinel
|
|
3605
|
+
# Generic auth/login failures. These are evaluated only against failure
|
|
3606
|
+
# channels, so they are safe to treat as fallback-worthy.
|
|
3607
|
+
"not logged in",
|
|
3608
|
+
"please run /login",
|
|
3609
|
+
"please log in",
|
|
3610
|
+
"authentication failed",
|
|
3611
|
+
"authentication_failed",
|
|
3612
|
+
"authentication required",
|
|
3613
|
+
"authenticate with the github",
|
|
3614
|
+
"gh auth login",
|
|
3577
3615
|
]
|
|
3578
3616
|
|
|
3579
3617
|
def __init__(self):
|
|
@@ -4772,8 +4810,8 @@ class ProcessManager:
|
|
|
4772
4810
|
even when XDG paths are set. That can fail with EROFS inside the daemon.
|
|
4773
4811
|
|
|
4774
4812
|
Point Claude at writable daemon-owned directories, redirect HOME to
|
|
4775
|
-
the same writable sandbox, and mirror
|
|
4776
|
-
|
|
4813
|
+
the same writable sandbox, and mirror the user's Claude home state so
|
|
4814
|
+
the isolated subprocess keeps the interactive login/session context.
|
|
4777
4815
|
"""
|
|
4778
4816
|
if sandbox_root is None:
|
|
4779
4817
|
sandbox_root = Path.home() / ".forgexa" / "claude-home"
|
|
@@ -4794,6 +4832,16 @@ class ProcessManager:
|
|
|
4794
4832
|
):
|
|
4795
4833
|
path.mkdir(parents=True, exist_ok=True)
|
|
4796
4834
|
|
|
4835
|
+
source_claude_dir = Path.home() / ".claude"
|
|
4836
|
+
try:
|
|
4837
|
+
if source_claude_dir.is_dir():
|
|
4838
|
+
shutil.copytree(source_claude_dir, home_claude_dir, dirs_exist_ok=True)
|
|
4839
|
+
except Exception as exc:
|
|
4840
|
+
logger.debug(
|
|
4841
|
+
"Claude home mirror skipped %s -> %s: %s",
|
|
4842
|
+
source_claude_dir, home_claude_dir, exc,
|
|
4843
|
+
)
|
|
4844
|
+
|
|
4797
4845
|
source_files = [
|
|
4798
4846
|
(Path.home() / ".claude.json", sandbox_root / ".claude.json"),
|
|
4799
4847
|
(Path.home() / ".claude.json", claude_config_dir / ".claude.json"),
|
|
@@ -6720,17 +6768,36 @@ class RuntimeDaemon:
|
|
|
6720
6768
|
except Exception:
|
|
6721
6769
|
from jose import jwt as jwt_module
|
|
6722
6770
|
|
|
6771
|
+
token_path = Path.home() / ".forgexa" / "token"
|
|
6772
|
+
preferred_user_id = None
|
|
6773
|
+
try:
|
|
6774
|
+
if token_path.exists():
|
|
6775
|
+
preferred_user_id = _extract_access_token_subject(token_path.read_text().strip())
|
|
6776
|
+
except OSError:
|
|
6777
|
+
preferred_user_id = None
|
|
6778
|
+
|
|
6723
6779
|
async with engine.connect() as conn:
|
|
6724
|
-
|
|
6725
|
-
|
|
6726
|
-
.
|
|
6727
|
-
|
|
6728
|
-
User.
|
|
6780
|
+
uid = None
|
|
6781
|
+
if preferred_user_id:
|
|
6782
|
+
preferred_result = await conn.execute(
|
|
6783
|
+
select(User.id)
|
|
6784
|
+
.where(User.id == UUID(preferred_user_id))
|
|
6785
|
+
.limit(1)
|
|
6729
6786
|
)
|
|
6730
|
-
.
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
|
|
6787
|
+
preferred_row = preferred_result.first()
|
|
6788
|
+
uid = str(preferred_row[0]) if preferred_row else None
|
|
6789
|
+
|
|
6790
|
+
if not uid:
|
|
6791
|
+
row = await conn.execute(
|
|
6792
|
+
select(User.id)
|
|
6793
|
+
.order_by(
|
|
6794
|
+
case((User.status == "active", 0), else_=1),
|
|
6795
|
+
User.created_at,
|
|
6796
|
+
)
|
|
6797
|
+
.limit(1)
|
|
6798
|
+
)
|
|
6799
|
+
r = row.first()
|
|
6800
|
+
uid = str(r[0]) if r else None
|
|
6734
6801
|
|
|
6735
6802
|
if uid:
|
|
6736
6803
|
token = jwt_module.encode(
|
|
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
|