gemx 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.
- gemx-0.1.0/.gitignore +24 -0
- gemx-0.1.0/LICENSE +21 -0
- gemx-0.1.0/PKG-INFO +111 -0
- gemx-0.1.0/README.md +89 -0
- gemx-0.1.0/pyproject.toml +94 -0
- gemx-0.1.0/src/gemx/__init__.py +29 -0
- gemx-0.1.0/src/gemx/cli.py +114 -0
- gemx-0.1.0/src/gemx/client.py +235 -0
- gemx-0.1.0/src/gemx/errors.py +19 -0
- gemx-0.1.0/src/gemx/formats.py +103 -0
- gemx-0.1.0/src/gemx/py.typed +0 -0
- gemx-0.1.0/tests/__init__.py +0 -0
- gemx-0.1.0/tests/scripts/TEST_GEMINI.md +55 -0
- gemx-0.1.0/tests/scripts/gemini_console.js +332 -0
- gemx-0.1.0/tests/scripts/gemini_console_prompt.js +159 -0
- gemx-0.1.0/tests/test_cli.py +43 -0
- gemx-0.1.0/tests/test_client.py +47 -0
- gemx-0.1.0/tests/test_formats.py +77 -0
gemx-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
|
|
9
|
+
# uv / venv
|
|
10
|
+
.venv/
|
|
11
|
+
venv/
|
|
12
|
+
uv.lock
|
|
13
|
+
|
|
14
|
+
# Tooling caches
|
|
15
|
+
.mypy_cache/
|
|
16
|
+
.ruff_cache/
|
|
17
|
+
.pytest_cache/
|
|
18
|
+
|
|
19
|
+
# Playwright profile data (never commit a logged-in session)
|
|
20
|
+
*.jimi-profile/
|
|
21
|
+
profile/
|
|
22
|
+
|
|
23
|
+
# OS
|
|
24
|
+
.DS_Store
|
gemx-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ra0x3 / Stonehedge Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
gemx-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gemx
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Drive the Gemini web UI from Python via Playwright
|
|
5
|
+
Project-URL: Homepage, https://github.com/ra0x3/gemx
|
|
6
|
+
Project-URL: Repository, https://github.com/ra0x3/gemx
|
|
7
|
+
Project-URL: Issues, https://github.com/ra0x3/gemx/issues
|
|
8
|
+
Author: ra0x3
|
|
9
|
+
Maintainer: ra0x3
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: automation,browser,gemini,llm,playwright,scraping
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.12
|
|
20
|
+
Requires-Dist: playwright>=1.49
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# Gemx
|
|
24
|
+
|
|
25
|
+
**Drive the Gemini web UI from Python.** A play on "Gemini" — Gemx treats
|
|
26
|
+
`gemini.google.com` as if it were an API, using Playwright to enter a prompt,
|
|
27
|
+
submit it, and capture the structured reply.
|
|
28
|
+
|
|
29
|
+
It exists because the obvious approaches don't work: Gemini's editor is
|
|
30
|
+
[Quill](https://quilljs.com/), which keeps its own document model and ignores
|
|
31
|
+
DOM surgery, Playwright `fill()`, and synthetic input events — those leave the
|
|
32
|
+
model empty and the turn errors with *"I encountered an error."* Gemx injects
|
|
33
|
+
text via `execCommand('insertText')` (the same trusted input pipeline a manual
|
|
34
|
+
paste uses), reads the reply from `message-content .markdown`, and retains the
|
|
35
|
+
*peak* streamed text because Gemini re-mounts the response node mid-stream.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
uv add gemx
|
|
41
|
+
# Playwright needs a browser the first time:
|
|
42
|
+
uv run playwright install chromium
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## CLI
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# JSON (default)
|
|
49
|
+
gemx "List 3 NBA teams as a JSON array"
|
|
50
|
+
|
|
51
|
+
# XML
|
|
52
|
+
gemx --format xml "Describe the solar system as XML"
|
|
53
|
+
|
|
54
|
+
# Plain text, reading the prompt from stdin
|
|
55
|
+
echo "Summarize the plot of Dune in one sentence" | gemx --format txt
|
|
56
|
+
|
|
57
|
+
# Watch the browser while it works
|
|
58
|
+
gemx --headful --verbose "Hello there"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The chosen `--format` (`json`, `xml`, or `txt`) is appended to the prompt as an
|
|
62
|
+
instruction *and* drives how Gemx parses the reply.
|
|
63
|
+
|
|
64
|
+
| Option | Description |
|
|
65
|
+
| --- | --- |
|
|
66
|
+
| `-f, --format {json,xml,txt}` | Output format (default: `json`). |
|
|
67
|
+
| `-p, --profile-dir PATH` | Chrome profile dir (default: `~/.gemx/profile`). |
|
|
68
|
+
| `--headful` | Show the browser window. |
|
|
69
|
+
| `--response-timeout SECONDS` | Wait for a response to start (default: 180). |
|
|
70
|
+
| `-v, --verbose` | Log progress to stderr. |
|
|
71
|
+
|
|
72
|
+
## Library
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
import asyncio
|
|
76
|
+
from pathlib import Path
|
|
77
|
+
from gemx import Gemx, GemxConfig, OutputFormat
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
async def main() -> None:
|
|
81
|
+
config = GemxConfig(profile_dir=Path("~/.gemx/profile"))
|
|
82
|
+
async with Gemx(config) as gemx:
|
|
83
|
+
data = await gemx.ask("List 3 fruits as JSON", OutputFormat.JSON)
|
|
84
|
+
print(data)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
asyncio.run(main())
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Authentication
|
|
91
|
+
|
|
92
|
+
Gemx drives a real, signed-in Gemini session. Point `--profile-dir` at a Chrome
|
|
93
|
+
profile that is already logged into your Google account (run once with
|
|
94
|
+
`--headful` to sign in); subsequent runs reuse that profile.
|
|
95
|
+
|
|
96
|
+
## Development
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
uv sync
|
|
100
|
+
uv run ruff check .
|
|
101
|
+
uv run mypy
|
|
102
|
+
uv run pylint src/gemx
|
|
103
|
+
uv run pytest
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The browser-console debugging scripts used to discover and verify the current
|
|
107
|
+
Gemini selectors live in [`tests/scripts/`](tests/scripts/).
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
MIT © ra0x3 — [Stonehedge Labs](https://github.com/stonehedgelabs)
|
gemx-0.1.0/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Gemx
|
|
2
|
+
|
|
3
|
+
**Drive the Gemini web UI from Python.** A play on "Gemini" — Gemx treats
|
|
4
|
+
`gemini.google.com` as if it were an API, using Playwright to enter a prompt,
|
|
5
|
+
submit it, and capture the structured reply.
|
|
6
|
+
|
|
7
|
+
It exists because the obvious approaches don't work: Gemini's editor is
|
|
8
|
+
[Quill](https://quilljs.com/), which keeps its own document model and ignores
|
|
9
|
+
DOM surgery, Playwright `fill()`, and synthetic input events — those leave the
|
|
10
|
+
model empty and the turn errors with *"I encountered an error."* Gemx injects
|
|
11
|
+
text via `execCommand('insertText')` (the same trusted input pipeline a manual
|
|
12
|
+
paste uses), reads the reply from `message-content .markdown`, and retains the
|
|
13
|
+
*peak* streamed text because Gemini re-mounts the response node mid-stream.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
uv add gemx
|
|
19
|
+
# Playwright needs a browser the first time:
|
|
20
|
+
uv run playwright install chromium
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## CLI
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# JSON (default)
|
|
27
|
+
gemx "List 3 NBA teams as a JSON array"
|
|
28
|
+
|
|
29
|
+
# XML
|
|
30
|
+
gemx --format xml "Describe the solar system as XML"
|
|
31
|
+
|
|
32
|
+
# Plain text, reading the prompt from stdin
|
|
33
|
+
echo "Summarize the plot of Dune in one sentence" | gemx --format txt
|
|
34
|
+
|
|
35
|
+
# Watch the browser while it works
|
|
36
|
+
gemx --headful --verbose "Hello there"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The chosen `--format` (`json`, `xml`, or `txt`) is appended to the prompt as an
|
|
40
|
+
instruction *and* drives how Gemx parses the reply.
|
|
41
|
+
|
|
42
|
+
| Option | Description |
|
|
43
|
+
| --- | --- |
|
|
44
|
+
| `-f, --format {json,xml,txt}` | Output format (default: `json`). |
|
|
45
|
+
| `-p, --profile-dir PATH` | Chrome profile dir (default: `~/.gemx/profile`). |
|
|
46
|
+
| `--headful` | Show the browser window. |
|
|
47
|
+
| `--response-timeout SECONDS` | Wait for a response to start (default: 180). |
|
|
48
|
+
| `-v, --verbose` | Log progress to stderr. |
|
|
49
|
+
|
|
50
|
+
## Library
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
import asyncio
|
|
54
|
+
from pathlib import Path
|
|
55
|
+
from gemx import Gemx, GemxConfig, OutputFormat
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
async def main() -> None:
|
|
59
|
+
config = GemxConfig(profile_dir=Path("~/.gemx/profile"))
|
|
60
|
+
async with Gemx(config) as gemx:
|
|
61
|
+
data = await gemx.ask("List 3 fruits as JSON", OutputFormat.JSON)
|
|
62
|
+
print(data)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
asyncio.run(main())
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Authentication
|
|
69
|
+
|
|
70
|
+
Gemx drives a real, signed-in Gemini session. Point `--profile-dir` at a Chrome
|
|
71
|
+
profile that is already logged into your Google account (run once with
|
|
72
|
+
`--headful` to sign in); subsequent runs reuse that profile.
|
|
73
|
+
|
|
74
|
+
## Development
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
uv sync
|
|
78
|
+
uv run ruff check .
|
|
79
|
+
uv run mypy
|
|
80
|
+
uv run pylint src/gemx
|
|
81
|
+
uv run pytest
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The browser-console debugging scripts used to discover and verify the current
|
|
85
|
+
Gemini selectors live in [`tests/scripts/`](tests/scripts/).
|
|
86
|
+
|
|
87
|
+
## License
|
|
88
|
+
|
|
89
|
+
MIT © ra0x3 — [Stonehedge Labs](https://github.com/stonehedgelabs)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "gemx"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Drive the Gemini web UI from Python via Playwright"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
license = { text = "MIT" }
|
|
8
|
+
authors = [{ name = "ra0x3" }]
|
|
9
|
+
maintainers = [{ name = "ra0x3" }]
|
|
10
|
+
keywords = ["gemini", "playwright", "automation", "llm", "browser", "scraping"]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 4 - Beta",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"License :: OSI Approved :: MIT License",
|
|
15
|
+
"Programming Language :: Python :: 3.12",
|
|
16
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
17
|
+
"Typing :: Typed",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"playwright>=1.49",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Homepage = "https://github.com/ra0x3/gemx"
|
|
25
|
+
Repository = "https://github.com/ra0x3/gemx"
|
|
26
|
+
Issues = "https://github.com/ra0x3/gemx/issues"
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
gemx = "gemx.cli:main"
|
|
30
|
+
|
|
31
|
+
[dependency-groups]
|
|
32
|
+
dev = [
|
|
33
|
+
"mypy>=1.13",
|
|
34
|
+
"ruff>=0.8",
|
|
35
|
+
"pylint>=3.3",
|
|
36
|
+
"pytest>=8.3",
|
|
37
|
+
"pytest-asyncio>=0.24",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[build-system]
|
|
41
|
+
requires = ["hatchling"]
|
|
42
|
+
build-backend = "hatchling.build"
|
|
43
|
+
|
|
44
|
+
[tool.hatch.build.targets.wheel]
|
|
45
|
+
packages = ["src/gemx"]
|
|
46
|
+
|
|
47
|
+
[tool.ruff]
|
|
48
|
+
line-length = 88
|
|
49
|
+
target-version = "py312"
|
|
50
|
+
src = ["src", "tests"]
|
|
51
|
+
|
|
52
|
+
[tool.ruff.lint]
|
|
53
|
+
select = ["E", "F", "I", "N", "UP", "B", "A", "C4", "SIM", "RUF"]
|
|
54
|
+
ignore = ["A003"]
|
|
55
|
+
|
|
56
|
+
[tool.ruff.lint.isort]
|
|
57
|
+
known-first-party = ["gemx"]
|
|
58
|
+
|
|
59
|
+
[tool.mypy]
|
|
60
|
+
python_version = "3.12"
|
|
61
|
+
strict = true
|
|
62
|
+
warn_unused_configs = true
|
|
63
|
+
warn_redundant_casts = true
|
|
64
|
+
warn_unused_ignores = true
|
|
65
|
+
disallow_any_generics = true
|
|
66
|
+
disallow_untyped_defs = true
|
|
67
|
+
no_implicit_optional = true
|
|
68
|
+
files = ["src/gemx"]
|
|
69
|
+
|
|
70
|
+
[[tool.mypy.overrides]]
|
|
71
|
+
module = "tests.*"
|
|
72
|
+
disallow_untyped_defs = false
|
|
73
|
+
|
|
74
|
+
[[tool.mypy.overrides]]
|
|
75
|
+
module = "playwright.*"
|
|
76
|
+
ignore_missing_imports = true
|
|
77
|
+
|
|
78
|
+
[tool.pylint.main]
|
|
79
|
+
py-version = "3.12"
|
|
80
|
+
source-roots = ["src"]
|
|
81
|
+
|
|
82
|
+
[tool.pylint.format]
|
|
83
|
+
max-line-length = 88
|
|
84
|
+
|
|
85
|
+
[tool.pylint."messages control"]
|
|
86
|
+
disable = [
|
|
87
|
+
"missing-module-docstring",
|
|
88
|
+
"too-few-public-methods",
|
|
89
|
+
"duplicate-code",
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
[tool.pytest.ini_options]
|
|
93
|
+
asyncio_mode = "auto"
|
|
94
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Gemx — drive the Gemini web UI from Python.
|
|
2
|
+
|
|
3
|
+
A play on "Gemini". Treats ``gemini.google.com`` as if it were an API.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from .client import Gemx, GemxConfig
|
|
9
|
+
from .errors import (
|
|
10
|
+
GemxError,
|
|
11
|
+
InputError,
|
|
12
|
+
ResponseParseError,
|
|
13
|
+
ResponseTimeoutError,
|
|
14
|
+
)
|
|
15
|
+
from .formats import OutputFormat, format_instruction, parse_output
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"Gemx",
|
|
19
|
+
"GemxConfig",
|
|
20
|
+
"GemxError",
|
|
21
|
+
"InputError",
|
|
22
|
+
"OutputFormat",
|
|
23
|
+
"ResponseParseError",
|
|
24
|
+
"ResponseTimeoutError",
|
|
25
|
+
"format_instruction",
|
|
26
|
+
"parse_output",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Command-line interface: ``gemx``."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import asyncio
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from xml.etree import ElementTree as ET
|
|
12
|
+
|
|
13
|
+
from . import __version__
|
|
14
|
+
from .client import Gemx, GemxConfig
|
|
15
|
+
from .errors import GemxError
|
|
16
|
+
from .formats import OutputFormat
|
|
17
|
+
|
|
18
|
+
DEFAULT_PROFILE_DIR = Path("~/.gemx/profile")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
22
|
+
"""Construct the ``gemx`` argument parser."""
|
|
23
|
+
parser = argparse.ArgumentParser(
|
|
24
|
+
prog="gemx",
|
|
25
|
+
description="Drive the Gemini web UI from the command line.",
|
|
26
|
+
)
|
|
27
|
+
parser.add_argument("--version", action="version", version=f"gemx {__version__}")
|
|
28
|
+
parser.add_argument(
|
|
29
|
+
"prompt",
|
|
30
|
+
nargs="?",
|
|
31
|
+
help="Prompt to send. If omitted, read from stdin.",
|
|
32
|
+
)
|
|
33
|
+
parser.add_argument(
|
|
34
|
+
"-f",
|
|
35
|
+
"--format",
|
|
36
|
+
type=OutputFormat.from_str,
|
|
37
|
+
default=OutputFormat.JSON,
|
|
38
|
+
metavar="{json,xml,txt}",
|
|
39
|
+
help="Output format requested from Gemini (default: json).",
|
|
40
|
+
)
|
|
41
|
+
parser.add_argument(
|
|
42
|
+
"-p",
|
|
43
|
+
"--profile-dir",
|
|
44
|
+
type=Path,
|
|
45
|
+
default=DEFAULT_PROFILE_DIR,
|
|
46
|
+
help=f"Chrome profile dir for the session (default: {DEFAULT_PROFILE_DIR}).",
|
|
47
|
+
)
|
|
48
|
+
parser.add_argument(
|
|
49
|
+
"--headful",
|
|
50
|
+
action="store_true",
|
|
51
|
+
help="Show the browser window instead of running headless.",
|
|
52
|
+
)
|
|
53
|
+
parser.add_argument(
|
|
54
|
+
"--response-timeout",
|
|
55
|
+
type=int,
|
|
56
|
+
default=180,
|
|
57
|
+
metavar="SECONDS",
|
|
58
|
+
help="Max seconds to wait for a response to start (default: 180).",
|
|
59
|
+
)
|
|
60
|
+
parser.add_argument(
|
|
61
|
+
"-v",
|
|
62
|
+
"--verbose",
|
|
63
|
+
action="store_true",
|
|
64
|
+
help="Log progress to stderr.",
|
|
65
|
+
)
|
|
66
|
+
return parser
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _render(value: object, fmt: OutputFormat) -> str:
|
|
70
|
+
"""Serialize a parsed value back to a string for stdout."""
|
|
71
|
+
if fmt is OutputFormat.JSON:
|
|
72
|
+
return json.dumps(value, indent=2, ensure_ascii=False)
|
|
73
|
+
if fmt is OutputFormat.XML:
|
|
74
|
+
assert isinstance(value, ET.Element)
|
|
75
|
+
return ET.tostring(value, encoding="unicode")
|
|
76
|
+
return str(value)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
async def _run(args: argparse.Namespace) -> int:
|
|
80
|
+
prompt = args.prompt if args.prompt is not None else sys.stdin.read()
|
|
81
|
+
prompt = prompt.strip()
|
|
82
|
+
if not prompt:
|
|
83
|
+
print("error: empty prompt", file=sys.stderr)
|
|
84
|
+
return 2
|
|
85
|
+
|
|
86
|
+
config = GemxConfig(
|
|
87
|
+
profile_dir=args.profile_dir,
|
|
88
|
+
headless=not args.headful,
|
|
89
|
+
response_timeout_s=args.response_timeout,
|
|
90
|
+
)
|
|
91
|
+
try:
|
|
92
|
+
async with Gemx(config) as gemx:
|
|
93
|
+
result = await gemx.ask(prompt, args.format)
|
|
94
|
+
except GemxError as exc:
|
|
95
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
96
|
+
return 1
|
|
97
|
+
|
|
98
|
+
print(_render(result, args.format))
|
|
99
|
+
return 0
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def main(argv: list[str] | None = None) -> int:
|
|
103
|
+
"""Entry point for the ``gemx`` console script."""
|
|
104
|
+
args = build_parser().parse_args(argv)
|
|
105
|
+
logging.basicConfig(
|
|
106
|
+
level=logging.INFO if args.verbose else logging.WARNING,
|
|
107
|
+
format="%(levelname)s %(name)s: %(message)s",
|
|
108
|
+
stream=sys.stderr,
|
|
109
|
+
)
|
|
110
|
+
return asyncio.run(_run(args))
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
if __name__ == "__main__":
|
|
114
|
+
raise SystemExit(main())
|