forgexa-cli 1.18.3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forgexa-cli
3
- Version: 1.18.3
3
+ Version: 1.18.5
4
4
  Summary: Forgexa CLI — command-line client and AI agent runtime for the Forgexa platform
5
5
  Author-email: Jason Sun <dev.winds@gmail.com>
6
6
  License-Expression: MIT
@@ -1,2 +1,2 @@
1
1
  """forgexa-cli — Forgexa command-line client."""
2
- __version__ = "1.18.3"
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.3"
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 only the small set of user
4776
- config files it needs for headless execution.
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
- row = await conn.execute(
6725
- select(User.id)
6726
- .order_by(
6727
- case((User.status == "active", 0), else_=1),
6728
- User.created_at,
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
- .limit(1)
6731
- )
6732
- r = row.first()
6733
- uid = str(r[0]) if r else None
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(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forgexa-cli
3
- Version: 1.18.3
3
+ Version: 1.18.5
4
4
  Summary: Forgexa CLI — command-line client and AI agent runtime for the Forgexa platform
5
5
  Author-email: Jason Sun <dev.winds@gmail.com>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "forgexa-cli"
3
- version = "1.18.3"
3
+ version = "1.18.5"
4
4
  description = "Forgexa CLI — command-line client and AI agent runtime for the Forgexa platform"
5
5
  requires-python = ">=3.9"
6
6
  license = "MIT"
File without changes
File without changes