optio-antigravity 0.1.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 (52) hide show
  1. optio_antigravity-0.1.0/PKG-INFO +67 -0
  2. optio_antigravity-0.1.0/README.md +35 -0
  3. optio_antigravity-0.1.0/pyproject.toml +56 -0
  4. optio_antigravity-0.1.0/setup.cfg +4 -0
  5. optio_antigravity-0.1.0/src/optio_antigravity/__init__.py +37 -0
  6. optio_antigravity-0.1.0/src/optio_antigravity/auth_scrape.py +117 -0
  7. optio_antigravity-0.1.0/src/optio_antigravity/conversation.py +581 -0
  8. optio_antigravity-0.1.0/src/optio_antigravity/conversation_listener.py +316 -0
  9. optio_antigravity-0.1.0/src/optio_antigravity/cred_watcher.py +132 -0
  10. optio_antigravity-0.1.0/src/optio_antigravity/fs_allowlist.py +88 -0
  11. optio_antigravity-0.1.0/src/optio_antigravity/host_actions.py +1335 -0
  12. optio_antigravity-0.1.0/src/optio_antigravity/models.py +115 -0
  13. optio_antigravity-0.1.0/src/optio_antigravity/prompt.py +190 -0
  14. optio_antigravity-0.1.0/src/optio_antigravity/seed_manifest.py +178 -0
  15. optio_antigravity-0.1.0/src/optio_antigravity/session.py +933 -0
  16. optio_antigravity-0.1.0/src/optio_antigravity/snapshots.py +103 -0
  17. optio_antigravity-0.1.0/src/optio_antigravity/types.py +340 -0
  18. optio_antigravity-0.1.0/src/optio_antigravity/verify.py +311 -0
  19. optio_antigravity-0.1.0/src/optio_antigravity.egg-info/PKG-INFO +67 -0
  20. optio_antigravity-0.1.0/src/optio_antigravity.egg-info/SOURCES.txt +50 -0
  21. optio_antigravity-0.1.0/src/optio_antigravity.egg-info/dependency_links.txt +1 -0
  22. optio_antigravity-0.1.0/src/optio_antigravity.egg-info/requires.txt +10 -0
  23. optio_antigravity-0.1.0/src/optio_antigravity.egg-info/top_level.txt +1 -0
  24. optio_antigravity-0.1.0/tests/test_antigravity_cache.py +366 -0
  25. optio_antigravity-0.1.0/tests/test_auth_scrape.py +125 -0
  26. optio_antigravity-0.1.0/tests/test_config.py +120 -0
  27. optio_antigravity-0.1.0/tests/test_conversation.py +277 -0
  28. optio_antigravity-0.1.0/tests/test_conversation_controls.py +58 -0
  29. optio_antigravity-0.1.0/tests/test_conversation_listener.py +234 -0
  30. optio_antigravity-0.1.0/tests/test_conversation_sandbox_enforce.py +103 -0
  31. optio_antigravity-0.1.0/tests/test_conversation_teardown.py +74 -0
  32. optio_antigravity-0.1.0/tests/test_cred_watcher.py +235 -0
  33. optio_antigravity-0.1.0/tests/test_file_download.py +182 -0
  34. optio_antigravity-0.1.0/tests/test_file_upload.py +30 -0
  35. optio_antigravity-0.1.0/tests/test_fs_allowlist.py +267 -0
  36. optio_antigravity-0.1.0/tests/test_host_actions.py +87 -0
  37. optio_antigravity-0.1.0/tests/test_import.py +4 -0
  38. optio_antigravity-0.1.0/tests/test_input_wiring.py +99 -0
  39. optio_antigravity-0.1.0/tests/test_models.py +139 -0
  40. optio_antigravity-0.1.0/tests/test_ported_features.py +271 -0
  41. optio_antigravity-0.1.0/tests/test_prompt.py +36 -0
  42. optio_antigravity-0.1.0/tests/test_real_binary_gates.py +219 -0
  43. optio_antigravity-0.1.0/tests/test_sandbox_enforce.py +101 -0
  44. optio_antigravity-0.1.0/tests/test_seed_manifest.py +121 -0
  45. optio_antigravity-0.1.0/tests/test_session_conversation.py +422 -0
  46. optio_antigravity-0.1.0/tests/test_session_lease.py +214 -0
  47. optio_antigravity-0.1.0/tests/test_session_local.py +112 -0
  48. optio_antigravity-0.1.0/tests/test_session_remote.py +81 -0
  49. optio_antigravity-0.1.0/tests/test_session_resume.py +145 -0
  50. optio_antigravity-0.1.0/tests/test_session_seed.py +112 -0
  51. optio_antigravity-0.1.0/tests/test_snapshots.py +81 -0
  52. optio_antigravity-0.1.0/tests/test_verify.py +261 -0
@@ -0,0 +1,67 @@
1
+ Metadata-Version: 2.4
2
+ Name: optio-antigravity
3
+ Version: 0.1.0
4
+ Summary: Run Google Antigravity (agy) as an optio task; local subprocess or remote via SSH; ttyd-served TUI iframe or transcript-driven conversation.
5
+ Author-email: Kristof Csillag <kristof.csillag@deai-labs.com>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/deai-network/optio
8
+ Project-URL: Repository, https://github.com/deai-network/optio
9
+ Project-URL: Issues, https://github.com/deai-network/optio/issues
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Operating System :: MacOS
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Classifier: Topic :: Software Development :: Code Generators
20
+ Classifier: Framework :: AsyncIO
21
+ Requires-Python: >=3.11
22
+ Description-Content-Type: text/markdown
23
+ Requires-Dist: optio-core<0.4,>=0.3
24
+ Requires-Dist: optio-host<0.3,>=0.2
25
+ Requires-Dist: optio-agents<0.5,>=0.4
26
+ Requires-Dist: asyncssh>=2.14
27
+ Requires-Dist: aiohttp>=3.9
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=8.0; extra == "dev"
30
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
31
+ Requires-Dist: pytest-xdist>=3.6; extra == "dev"
32
+
33
+ # optio-antigravity
34
+
35
+ Run Google Antigravity (`agy`) as an `optio` task — either as a local
36
+ subprocess or on a remote host over SSH — with the interactive TUI
37
+ embedded in the optio dashboard via an iframe widget served by `ttyd`,
38
+ or as a transcript-driven conversation rendered in the dashboard chat
39
+ widget.
40
+
41
+ ## Install
42
+
43
+ ```bash
44
+ pip install optio-antigravity
45
+ ```
46
+
47
+ Requires Python 3.11+. Pulls `optio-core`, `optio-host`, `optio-agents`,
48
+ `asyncssh`, and `aiohttp`.
49
+
50
+ ## What it does
51
+
52
+ Antigravity is Google's CLI coding agent (`agy`), sharing state with the
53
+ Gemini CLI under `~/.gemini`. optio-antigravity adapts the optio wrapper
54
+ machinery: it launches `agy` under a PTY, serves the TUI over `ttyd`, and
55
+ coordinates with the host harness through the `optio.log` keyword channel
56
+ (STATUS / DELIVERABLE / DONE / ERROR). The agent reads its task from an
57
+ `AGENTS.md` file planted in the workdir.
58
+
59
+ Because `agy --print` runs one-shot only (no ACP/stream-json) and swallows
60
+ stdout under a non-TTY, conversation mode is **synthetic**: each turn is
61
+ driven with `agy -p --conversation <id>` under a PTY and events are read
62
+ from `~/.gemini/antigravity/transcript.jsonl`.
63
+
64
+ ## Status
65
+
66
+ Stage 0 (MVP): package scaffold. Iframe/ttyd mode, remote/SSH, resume,
67
+ seeds, conversation mode, and filesystem isolation arrive in later stages.
@@ -0,0 +1,35 @@
1
+ # optio-antigravity
2
+
3
+ Run Google Antigravity (`agy`) as an `optio` task — either as a local
4
+ subprocess or on a remote host over SSH — with the interactive TUI
5
+ embedded in the optio dashboard via an iframe widget served by `ttyd`,
6
+ or as a transcript-driven conversation rendered in the dashboard chat
7
+ widget.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pip install optio-antigravity
13
+ ```
14
+
15
+ Requires Python 3.11+. Pulls `optio-core`, `optio-host`, `optio-agents`,
16
+ `asyncssh`, and `aiohttp`.
17
+
18
+ ## What it does
19
+
20
+ Antigravity is Google's CLI coding agent (`agy`), sharing state with the
21
+ Gemini CLI under `~/.gemini`. optio-antigravity adapts the optio wrapper
22
+ machinery: it launches `agy` under a PTY, serves the TUI over `ttyd`, and
23
+ coordinates with the host harness through the `optio.log` keyword channel
24
+ (STATUS / DELIVERABLE / DONE / ERROR). The agent reads its task from an
25
+ `AGENTS.md` file planted in the workdir.
26
+
27
+ Because `agy --print` runs one-shot only (no ACP/stream-json) and swallows
28
+ stdout under a non-TTY, conversation mode is **synthetic**: each turn is
29
+ driven with `agy -p --conversation <id>` under a PTY and events are read
30
+ from `~/.gemini/antigravity/transcript.jsonl`.
31
+
32
+ ## Status
33
+
34
+ Stage 0 (MVP): package scaffold. Iframe/ttyd mode, remote/SSH, resume,
35
+ seeds, conversation mode, and filesystem isolation arrive in later stages.
@@ -0,0 +1,56 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "optio-antigravity"
7
+ version = "0.1.0"
8
+ description = "Run Google Antigravity (agy) as an optio task; local subprocess or remote via SSH; ttyd-served TUI iframe or transcript-driven conversation."
9
+ readme = "README.md"
10
+ license = "Apache-2.0"
11
+ requires-python = ">=3.11"
12
+ authors = [
13
+ { name = "Kristof Csillag", email = "kristof.csillag@deai-labs.com" },
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Programming Language :: Python :: 3.13",
21
+ "Operating System :: POSIX :: Linux",
22
+ "Operating System :: MacOS",
23
+ "Topic :: Software Development :: Libraries :: Python Modules",
24
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
25
+ "Topic :: Software Development :: Code Generators",
26
+ "Framework :: AsyncIO",
27
+ ]
28
+ dependencies = [
29
+ "optio-core>=0.3,<0.4",
30
+ "optio-host>=0.2,<0.3",
31
+ "optio-agents>=0.4,<0.5",
32
+ "asyncssh>=2.14",
33
+ "aiohttp>=3.9",
34
+ ]
35
+
36
+ [project.optional-dependencies]
37
+ dev = [
38
+ "pytest>=8.0",
39
+ "pytest-asyncio>=0.23",
40
+ "pytest-xdist>=3.6",
41
+ ]
42
+
43
+ [project.urls]
44
+ Homepage = "https://github.com/deai-network/optio"
45
+ Repository = "https://github.com/deai-network/optio"
46
+ Issues = "https://github.com/deai-network/optio/issues"
47
+
48
+ [tool.setuptools.packages.find]
49
+ where = ["src"]
50
+
51
+ [tool.pytest.ini_options]
52
+ asyncio_mode = "auto"
53
+ testpaths = ["tests"]
54
+ markers = [
55
+ "serial: spawn-heavy, timing-fragile test that must run in a final non-parallel phase",
56
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,37 @@
1
+ """optio-antigravity — run Google Antigravity (agy) as an optio task."""
2
+
3
+ import logging as _logging
4
+
5
+ from optio_antigravity.seed_manifest import (
6
+ ANTIGRAVITY_CRED_MANIFEST,
7
+ ANTIGRAVITY_SEED_MANIFEST,
8
+ ANTIGRAVITY_SEED_SUFFIX,
9
+ delete_seed,
10
+ list_seeds,
11
+ purge_seed,
12
+ )
13
+ from optio_antigravity.session import (
14
+ create_antigravity_task,
15
+ run_antigravity_session,
16
+ )
17
+ from optio_antigravity.types import AntigravityTaskConfig
18
+ from optio_antigravity.verify import verify_and_refresh_seed
19
+
20
+
21
+ # asyncssh emits per-connection INFO lines that flood worker stdout
22
+ # once an SSH-backed session starts. Quiet by default.
23
+ _logging.getLogger("asyncssh").setLevel(_logging.WARNING)
24
+
25
+
26
+ __all__ = [
27
+ "create_antigravity_task",
28
+ "run_antigravity_session",
29
+ "AntigravityTaskConfig",
30
+ "ANTIGRAVITY_SEED_MANIFEST",
31
+ "ANTIGRAVITY_CRED_MANIFEST",
32
+ "ANTIGRAVITY_SEED_SUFFIX",
33
+ "delete_seed",
34
+ "list_seeds",
35
+ "purge_seed",
36
+ "verify_and_refresh_seed",
37
+ ]
@@ -0,0 +1,117 @@
1
+ """Surface agy's print-only login URL as a clean, copyable ``BROWSER:`` link.
2
+
3
+ agy authenticates with a hosted-redirect OAuth flow that PRINTS the Google
4
+ authorize URL into the TUI ("Open the URL below in your browser:") and never
5
+ shells out to a browser — so the redirect browser-shims never fire (an empty
6
+ ``optio.log`` on the login step is the tell). Worse, agy HARD-wraps the long URL
7
+ to the box width, so ttyd linkifies only the first line and the operator can't
8
+ copy it in one piece.
9
+
10
+ This module reads the tmux pane (the ttyd surface's backing terminal),
11
+ reassembles the wrapped OAuth URL, and appends a ``BROWSER: "<url>"`` marker to
12
+ ``optio.log`` — the same channel the redirect shim uses, so the log-protocol
13
+ driver parses it and calls ``ctx.request_browser_open`` (a single clean link in
14
+ the dashboard). Best-effort and idempotent: each distinct URL is emitted once.
15
+
16
+ iframe/ttyd path only — conversation mode has no tmux pane.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import asyncio
22
+ import re
23
+ import shlex
24
+
25
+ from optio_host.host import Host
26
+
27
+
28
+ # A reassembled candidate must look like an OAuth *authorize* URL, so we never
29
+ # surface arbitrary links the agent prints during normal work.
30
+ _OAUTH_MARKERS = re.compile(r"(oauth|client_id=|redirect_uri=|response_type=code)", re.I)
31
+ _URL_START = re.compile(r"https?://")
32
+ _HAS_ALNUM = re.compile(r"[A-Za-z0-9]")
33
+
34
+
35
+ def extract_auth_url(pane: str) -> "str | None":
36
+ """Reassemble a hard-wrapped OAuth authorize URL from a captured tmux pane.
37
+
38
+ agy prints the URL inside a box, hard-wrapped at the box content width, so
39
+ the fragments arrive as separate physical lines (``capture-pane -J`` joins
40
+ only soft wraps, not these). We start at a line that begins with ``http`` and
41
+ greedily append following fragment lines — non-empty, whitespace-free, and
42
+ containing alphanumerics (so box rules like ``────`` and prose like "After
43
+ authenticating…" both terminate the run). Returns the URL only if the
44
+ assembled string looks like an OAuth authorize URL, else ``None``.
45
+ """
46
+ parts: "list[str]" = []
47
+ collecting = False
48
+ for raw in pane.splitlines():
49
+ s = raw.strip().strip("│").strip()
50
+ if not collecting:
51
+ if _URL_START.match(s) and " " not in s:
52
+ parts = [s]
53
+ collecting = True
54
+ continue
55
+ # A continuation fragment: no whitespace + has alphanumerics (a border of
56
+ # box-drawing chars has none → stops; prose has spaces → stops).
57
+ if s and " " not in s and _HAS_ALNUM.search(s):
58
+ parts.append(s)
59
+ else:
60
+ break
61
+ if not parts:
62
+ return None
63
+ url = "".join(parts)
64
+ if not _URL_START.match(url) or not _OAUTH_MARKERS.search(url):
65
+ return None
66
+ return url
67
+
68
+
69
+ async def _capture_pane(
70
+ host: Host, tmux_path: str, socket_path: str, session_name: str, lines: int,
71
+ ) -> str:
72
+ """Joined dump of the tmux pane's recent scrollback (best-effort)."""
73
+ cmd = (
74
+ f"{shlex.quote(tmux_path)} -S {shlex.quote(socket_path)} "
75
+ f"capture-pane -t {shlex.quote(session_name)} -pJ -S -{int(lines)}"
76
+ )
77
+ r = await host.run_command(cmd)
78
+ return r.stdout if r.exit_code == 0 else ""
79
+
80
+
81
+ async def _emit_browser_marker(host: Host, url: str) -> None:
82
+ """Append a ``BROWSER: "<url>"`` line to optio.log (same format as the shim,
83
+ so the log-protocol parser surfaces it via ctx.request_browser_open)."""
84
+ log = f"{host.workdir}/optio.log"
85
+ await host.run_command(
86
+ f"printf 'BROWSER: \"%s\"\\n' {shlex.quote(url)} >> {shlex.quote(log)}"
87
+ )
88
+
89
+
90
+ async def run_auth_url_scraper(
91
+ host: Host,
92
+ *,
93
+ tmux_path: str,
94
+ socket_path: str,
95
+ session_name: str,
96
+ interval: float = 2.0,
97
+ capture_lines: int = 400,
98
+ ) -> None:
99
+ """Poll the tmux pane; surface each distinct printed OAuth URL once.
100
+
101
+ Runs for the life of the iframe session (the caller cancels it on teardown).
102
+ Never raises out of the loop — a transient capture failure is skipped, and
103
+ ``CancelledError`` (a BaseException) propagates so teardown is clean.
104
+ """
105
+ emitted: "set[str]" = set()
106
+ while True:
107
+ try:
108
+ pane = await _capture_pane(
109
+ host, tmux_path, socket_path, session_name, capture_lines,
110
+ )
111
+ url = extract_auth_url(pane)
112
+ if url and url not in emitted:
113
+ emitted.add(url)
114
+ await _emit_browser_marker(host, url)
115
+ except Exception:
116
+ pass # best-effort; a poll failure must not kill the session
117
+ await asyncio.sleep(interval)