forgexa-cli 1.24.5__tar.gz → 1.25.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 (27) hide show
  1. {forgexa_cli-1.24.5 → forgexa_cli-1.25.0}/PKG-INFO +1 -1
  2. {forgexa_cli-1.24.5 → forgexa_cli-1.25.0}/forgexa_cli/__init__.py +1 -1
  3. forgexa_cli-1.25.0/forgexa_cli/_local_bind.py +83 -0
  4. forgexa_cli-1.25.0/forgexa_cli/agent_core.py +1771 -0
  5. {forgexa_cli-1.24.5 → forgexa_cli-1.25.0}/forgexa_cli/daemon.py +897 -1532
  6. {forgexa_cli-1.24.5 → forgexa_cli-1.25.0}/forgexa_cli/main.py +142 -0
  7. {forgexa_cli-1.24.5 → forgexa_cli-1.25.0}/forgexa_cli.egg-info/PKG-INFO +1 -1
  8. {forgexa_cli-1.24.5 → forgexa_cli-1.25.0}/forgexa_cli.egg-info/SOURCES.txt +3 -0
  9. {forgexa_cli-1.24.5 → forgexa_cli-1.25.0}/pyproject.toml +1 -1
  10. forgexa_cli-1.25.0/tests/test_local_bind_commands.py +189 -0
  11. {forgexa_cli-1.24.5 → forgexa_cli-1.25.0}/README.md +0 -0
  12. {forgexa_cli-1.24.5 → forgexa_cli-1.25.0}/forgexa_cli/_build_config.py +0 -0
  13. {forgexa_cli-1.24.5 → forgexa_cli-1.25.0}/forgexa_cli/autoupgrade.py +0 -0
  14. {forgexa_cli-1.24.5 → forgexa_cli-1.25.0}/forgexa_cli/py.typed +0 -0
  15. {forgexa_cli-1.24.5 → forgexa_cli-1.25.0}/forgexa_cli.egg-info/dependency_links.txt +0 -0
  16. {forgexa_cli-1.24.5 → forgexa_cli-1.25.0}/forgexa_cli.egg-info/entry_points.txt +0 -0
  17. {forgexa_cli-1.24.5 → forgexa_cli-1.25.0}/forgexa_cli.egg-info/requires.txt +0 -0
  18. {forgexa_cli-1.24.5 → forgexa_cli-1.25.0}/forgexa_cli.egg-info/top_level.txt +0 -0
  19. {forgexa_cli-1.24.5 → forgexa_cli-1.25.0}/setup.cfg +0 -0
  20. {forgexa_cli-1.24.5 → forgexa_cli-1.25.0}/tests/test_auth_and_runtime_commands.py +0 -0
  21. {forgexa_cli-1.24.5 → forgexa_cli-1.25.0}/tests/test_autoupgrade.py +0 -0
  22. {forgexa_cli-1.24.5 → forgexa_cli-1.25.0}/tests/test_check_command.py +0 -0
  23. {forgexa_cli-1.24.5 → forgexa_cli-1.25.0}/tests/test_expiry_warnings_and_revoke.py +0 -0
  24. {forgexa_cli-1.24.5 → forgexa_cli-1.25.0}/tests/test_runtime_credentials.py +0 -0
  25. {forgexa_cli-1.24.5 → forgexa_cli-1.25.0}/tests/test_session_credentials.py +0 -0
  26. {forgexa_cli-1.24.5 → forgexa_cli-1.25.0}/tests/test_silent_install.py +0 -0
  27. {forgexa_cli-1.24.5 → forgexa_cli-1.25.0}/tests/test_upgrade_observability.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forgexa-cli
3
- Version: 1.24.5
3
+ Version: 1.25.0
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.24.5"
2
+ __version__ = "1.25.0"
@@ -0,0 +1,83 @@
1
+ """Forgexa IDE local-bind: CLI-side mapping file + git identity helpers.
2
+
3
+ Deliberately duplicates (does not import) the same logic already vendored
4
+ in backend/app/daemon.py (which itself duplicates
5
+ backend/app/services/git_url_normalize.py) — the CLI package and the
6
+ standalone daemon module are each meant to run independently, and importing
7
+ the full daemon.py module here would pull in its heavier import-time
8
+ environment checks for no benefit. Keep this file's mapping format and
9
+ normalize_repo_identity() in sync with daemon.py's versions if either ever
10
+ changes. See docs/designs/forgexa-ide-design.md §6.1/§6.2/§6.4.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import os
16
+ import re
17
+ import subprocess
18
+ from pathlib import Path
19
+
20
+
21
+ def local_workspaces_mapping_path() -> Path:
22
+ return Path.home() / ".forgexa" / "local-workspaces.json"
23
+
24
+
25
+ def load_local_workspaces_mapping() -> dict:
26
+ path = local_workspaces_mapping_path()
27
+ if not path.exists():
28
+ return {}
29
+ try:
30
+ data = json.loads(path.read_text(encoding="utf-8"))
31
+ return data if isinstance(data, dict) else {}
32
+ except (OSError, ValueError):
33
+ return {}
34
+
35
+
36
+ def save_local_workspaces_mapping(mapping: dict) -> None:
37
+ """Write the mapping atomically with OS-user-private permissions."""
38
+ path = local_workspaces_mapping_path()
39
+ path.parent.mkdir(parents=True, exist_ok=True)
40
+ tmp_path = path.with_name(path.name + ".tmp")
41
+ tmp_path.write_text(json.dumps(mapping, indent=2, sort_keys=True), encoding="utf-8")
42
+ if os.name != "nt":
43
+ os.chmod(tmp_path, 0o600)
44
+ tmp_path.replace(path)
45
+
46
+
47
+ _SSH_SCHEME_RE = re.compile(r"^ssh://(?:[^@/]+@)?([^/:]+)(?::\d+)?/(.+?)(?:\.git)?/?$")
48
+ _HTTP_SCHEME_RE = re.compile(r"^https?://(?:[^@/]+@)?([^/:]+)(?::\d+)?/(.+?)(?:\.git)?/?$")
49
+ _SCP_LIKE_RE = re.compile(r"^[^@/]+@([^:/]+):(.+?)(?:\.git)?/?$")
50
+
51
+
52
+ def normalize_repo_identity(repo_url: str) -> str | None:
53
+ """Canonical host-inclusive git remote identity (see module docstring)."""
54
+ repo_url_clean = (repo_url or "").strip()
55
+ if not repo_url_clean:
56
+ return None
57
+
58
+ match = _SSH_SCHEME_RE.match(repo_url_clean) or _HTTP_SCHEME_RE.match(repo_url_clean)
59
+ if not match and "@" in repo_url_clean and "://" not in repo_url_clean:
60
+ match = _SCP_LIKE_RE.match(repo_url_clean)
61
+ if not match:
62
+ return None
63
+
64
+ host = match.group(1).lower()
65
+ path = match.group(2).strip("/").lower()
66
+ path = re.sub(r"^scm/", "", path)
67
+ if not path:
68
+ return None
69
+ return f"{host}/{path}"
70
+
71
+
72
+ def local_git_remote_identity(path: Path) -> str | None:
73
+ """Best-effort `git remote get-url origin` + normalize, or None on any failure."""
74
+ try:
75
+ result = subprocess.run(
76
+ ["git", "remote", "get-url", "origin"],
77
+ cwd=str(path), capture_output=True, text=True, timeout=10,
78
+ )
79
+ except (OSError, subprocess.TimeoutExpired):
80
+ return None
81
+ if result.returncode != 0:
82
+ return None
83
+ return normalize_repo_identity(result.stdout.strip())