bos-ai 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.
@@ -0,0 +1,218 @@
1
+ .local/
2
+
3
+ # Byte-compiled / optimized / DLL files
4
+ __pycache__/
5
+ *.py[codz]
6
+ *$py.class
7
+
8
+ # C extensions
9
+ *.so
10
+
11
+ # Distribution / packaging
12
+ .Python
13
+ build/
14
+ develop-eggs/
15
+ dist/
16
+ downloads/
17
+ eggs/
18
+ .eggs/
19
+ lib/
20
+ lib64/
21
+ parts/
22
+ sdist/
23
+ var/
24
+ wheels/
25
+ share/python-wheels/
26
+ *.egg-info/
27
+ .installed.cfg
28
+ *.egg
29
+ MANIFEST
30
+
31
+ # PyInstaller
32
+ # Usually these files are written by a python script from a template
33
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
34
+ *.manifest
35
+ *.spec
36
+
37
+ # Installer logs
38
+ pip-log.txt
39
+ pip-delete-this-directory.txt
40
+
41
+ # Unit test / coverage reports
42
+ htmlcov/
43
+ .tox/
44
+ .nox/
45
+ .coverage
46
+ .coverage.*
47
+ .cache
48
+ nosetests.xml
49
+ coverage.xml
50
+ *.cover
51
+ *.py.cover
52
+ .hypothesis/
53
+ .pytest_cache/
54
+ cover/
55
+
56
+ # Translations
57
+ *.mo
58
+ *.pot
59
+
60
+ # Django stuff:
61
+ *.log
62
+ local_settings.py
63
+ db.sqlite3
64
+ db.sqlite3-journal
65
+
66
+ # Flask stuff:
67
+ instance/
68
+ .webassets-cache
69
+
70
+ # Scrapy stuff:
71
+ .scrapy
72
+
73
+ # Sphinx documentation
74
+ docs/_build/
75
+
76
+ # PyBuilder
77
+ .pybuilder/
78
+ target/
79
+
80
+ # Jupyter Notebook
81
+ .ipynb_checkpoints
82
+
83
+ # IPython
84
+ profile_default/
85
+ ipython_config.py
86
+
87
+ # pyenv
88
+ # For a library or package, you might want to ignore these files since the code is
89
+ # intended to run in multiple environments; otherwise, check them in:
90
+ # .python-version
91
+
92
+ # pipenv
93
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
94
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
95
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
96
+ # install all needed dependencies.
97
+ # Pipfile.lock
98
+
99
+ # UV
100
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
101
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
102
+ # commonly ignored for libraries.
103
+ # uv.lock
104
+
105
+ # poetry
106
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
107
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
108
+ # commonly ignored for libraries.
109
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
110
+ # poetry.lock
111
+ # poetry.toml
112
+
113
+ # pdm
114
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
115
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
116
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
117
+ # pdm.lock
118
+ # pdm.toml
119
+ .pdm-python
120
+ .pdm-build/
121
+
122
+ # pixi
123
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
124
+ # pixi.lock
125
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
126
+ # in the .local directory. It is recommended not to include this directory in version control.
127
+ .pixi
128
+
129
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
130
+ __pypackages__/
131
+
132
+ # Celery stuff
133
+ celerybeat-schedule
134
+ celerybeat.pid
135
+
136
+ # Redis
137
+ *.rdb
138
+ *.aof
139
+ *.pid
140
+
141
+ # RabbitMQ
142
+ mnesia/
143
+ rabbitmq/
144
+ rabbitmq-data/
145
+
146
+ # ActiveMQ
147
+ activemq-data/
148
+
149
+ # SageMath parsed files
150
+ *.sage.py
151
+
152
+ # Environments
153
+ .env
154
+ .envrc
155
+ .local
156
+ env/
157
+ venv/
158
+ ENV/
159
+ env.bak/
160
+ venv.bak/
161
+
162
+ # Spyder project settings
163
+ .spyderproject
164
+ .spyproject
165
+
166
+ # Rope project settings
167
+ .ropeproject
168
+
169
+ # mkdocs documentation
170
+ /site
171
+
172
+ # mypy
173
+ .mypy_cache/
174
+ .dmypy.json
175
+ dmypy.json
176
+
177
+ # Pyre type checker
178
+ .pyre/
179
+
180
+ # pytype static type analyzer
181
+ .pytype/
182
+
183
+ # Cython debug symbols
184
+ cython_debug/
185
+
186
+ # PyCharm
187
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
188
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
189
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
190
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
191
+ # .idea/
192
+
193
+ # Abstra
194
+ # Abstra is an AI-powered process automation framework.
195
+ # Ignore directories containing user credentials, local state, and settings.
196
+ # Learn more at https://abstra.io/docs
197
+ .abstra/
198
+
199
+ # Visual Studio Code
200
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
201
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
202
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
203
+ # you could uncomment the following to ignore the entire vscode folder
204
+ # .vscode/
205
+
206
+ # Ruff stuff:
207
+ .ruff_cache/
208
+
209
+ # PyPI configuration file
210
+ .pypirc
211
+
212
+ # Marimo
213
+ marimo/_static/
214
+ marimo/_lsp/
215
+ __marimo__/
216
+
217
+ # Streamlit
218
+ .streamlit/secrets.toml
bos_ai-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,126 @@
1
+ Metadata-Version: 2.4
2
+ Name: bos-ai
3
+ Version: 0.1.0
4
+ Summary: Lightweight single-file agent framework
5
+ Requires-Python: >=3.13
6
+ Requires-Dist: beautifulsoup4>=4.14.3
7
+ Requires-Dist: click>=8.1.7
8
+ Requires-Dist: filelock>=3.19.1
9
+ Requires-Dist: litellm==1.83.0
10
+ Requires-Dist: python-dotenv>=1.2.2
11
+ Requires-Dist: rich>=13.0.0
12
+ Requires-Dist: textual>=1.0.0
13
+ Requires-Dist: tomli>=2.0.0; python_version < '3.11'
14
+ Provides-Extra: all
15
+ Requires-Dist: oauth-cli-kit>=0.1.3; extra == 'all'
16
+ Provides-Extra: providers
17
+ Requires-Dist: oauth-cli-kit>=0.1.3; extra == 'providers'
18
+ Description-Content-Type: text/markdown
19
+
20
+ # BOS AI
21
+
22
+ **Lightweight single-file agent framework**
23
+
24
+ `bos-ai` is an extensible, actor-based Python framework for building and running autonomous AI agents. Built around a remarkably lean core, it provides everything you need to run, extend, and orchestrate LLM-driven agents with local tool execution, memory, and message passing.
25
+
26
+ ---
27
+
28
+ ## 🚀 Easy Start
29
+
30
+ 1. **Install the package:**
31
+ ```bash
32
+ pip install bos-ai
33
+ ```
34
+
35
+ 2. **Initialize a Workspace:**
36
+ Navigate into your project directory and initialize the BOS AI workspace:
37
+ ```bash
38
+ bos init
39
+ ```
40
+ This command creates a `.bos` directory and a `config.toml` file to hold your project's agent configurations.
41
+
42
+ 3. **Start Chatting:**
43
+ Use the chat command to interact with your agent:
44
+ ```bash
45
+ bos chat
46
+ ```
47
+
48
+ ---
49
+
50
+ ## 💻 CLI Intro
51
+
52
+ BOS AI ships with a `bos` CLI with lazy-loaded commands to keep startup times incredibly fast:
53
+
54
+ - **`bos init`**: Bootstraps a new workspace. It creates the `.bos/config.toml` file and provisions necessary data directories.
55
+ - **`bos auth`**: Set up authentication for various LLM providers and utilities.
56
+ - **`bos chat`**: Drops you into an interactive chat application to talk to the agents defined in your configuration. You can also use it in "oneshot" mode.
57
+
58
+ **LLM Providers via Auth:**
59
+ ```bash
60
+ # Chat with the Antigravity provider (requires `bos auth antigravity`)
61
+ bos chat -M "hello" -m antigravity/gemini-3.1-pro-low -a main
62
+
63
+ # Chat with the Gemini CLI provider (requires `bos auth gemini-cli`)
64
+ bos chat -M "hello" -m gemini-cli/gemini-2.5-flash -a main
65
+
66
+ # Chat with the Codex provider (requires `bos auth codex`)
67
+ bos chat -M "hello" -m codex/gpt-5.3-codex -a main
68
+ ```
69
+
70
+ **Global Options:**
71
+ - `-w`, `--workspace`: Path to the workspace directory (defaults to `.`).
72
+
73
+ ---
74
+
75
+ ## 🧠 Principles
76
+
77
+ `bos-ai` is built on a few core design principles:
78
+
79
+ - **Lightweight & Embeddable**: The heart of the framework lives in a single, comprehensive file (`core.py`), ensuring that complexity is minimized while keeping the abstractions powerfully dense.
80
+ - **Extensible at the Core**: Every significant layer—from LLM providers, to message persistence, to memory and tools—is powered by an internal Extension System.
81
+ - **Agent as an Actor**: Agents communicate via asynchronous message passing (the `Mailbox` protocol). This enables robust multi-agent orchestration without tightly coupled code.
82
+ - **Harness-Managed Lifecycle**: An `AgentHarness` is used to bootstrap, maintain, and gracefully tear down shared resources (like databases or API connections) across all agents in the workspace.
83
+
84
+ ---
85
+
86
+ ## 🔌 Extension Framework
87
+
88
+ BOS AI utilizes an `ExtensionPoint` pattern for its modular capabilities. You can seamlessly inject your own logic or override defaults decorators.
89
+
90
+ The framework provides named extension points such as:
91
+ - `@ep_provider`: Connect new LLM backends (OpenAI, Anthropic, Gemini, etc.).
92
+ - `@ep_tool`: Add new conversational tools that the LLM can invoke.
93
+ - `@ep_memory_store`: Connect alternative vector databases or key-value stores.
94
+ - `@ep_message_store`: Custom persistence logic for conversation history.
95
+ - `@ep_mailbox`: Implement distributed message-passing interfaces like Redis or RabbitMQ.
96
+ - `@ep_react_interceptor`: Hooks to orchestrate the internal ReAct loop of an agent.
97
+
98
+ Example registering a custom tool:
99
+
100
+ ```python
101
+ from bos.core import ep_tool
102
+
103
+ @ep_tool(
104
+ name="my_custom_tool",
105
+ description="Does something awesome.",
106
+ # ... additional structured metadata ...
107
+ )
108
+ async def my_custom_tool(arg1: str):
109
+ return f"Processed {arg1}"
110
+ ```
111
+
112
+ To load your extensions, simply add their module paths to the `platform.extensions` array in your workspace's `config.toml`.
113
+
114
+ ---
115
+
116
+ ## ⚙️ Configuration System
117
+
118
+ BOS AI uses a hierarchical, TOML-based configuration pattern that is specifically designed for isolation per-workspace.
119
+
120
+ When you run a command, `bos` searches upwards from the current directory to find a `.bos/config.toml` file, eventually falling back to a global `~/.bos/config.toml` if none is found.
121
+
122
+ ### Config Structure (`.bos/config.toml`)
123
+ - **`[platform]`**: Define environment variables, `.env` file locations, and structural extensions loading rules.
124
+ - **`[[platform.agents]]`**: Array of dictionaries defining your agents. You can configure `system_prompt`, limits (`max_tokens`), required `tools`, `skills`, memory definitions, and even `subagents`.
125
+ - **`[harness]`**: Define the overarching services all agents in the environment share. For example, configure the active `memory_store` directory or hook up an interceptor chain.
126
+ - **`[cli]`**: Directives and default options for the command-line application (like specifying a default agent target).
bos_ai-0.1.0/README.md ADDED
@@ -0,0 +1,107 @@
1
+ # BOS AI
2
+
3
+ **Lightweight single-file agent framework**
4
+
5
+ `bos-ai` is an extensible, actor-based Python framework for building and running autonomous AI agents. Built around a remarkably lean core, it provides everything you need to run, extend, and orchestrate LLM-driven agents with local tool execution, memory, and message passing.
6
+
7
+ ---
8
+
9
+ ## 🚀 Easy Start
10
+
11
+ 1. **Install the package:**
12
+ ```bash
13
+ pip install bos-ai
14
+ ```
15
+
16
+ 2. **Initialize a Workspace:**
17
+ Navigate into your project directory and initialize the BOS AI workspace:
18
+ ```bash
19
+ bos init
20
+ ```
21
+ This command creates a `.bos` directory and a `config.toml` file to hold your project's agent configurations.
22
+
23
+ 3. **Start Chatting:**
24
+ Use the chat command to interact with your agent:
25
+ ```bash
26
+ bos chat
27
+ ```
28
+
29
+ ---
30
+
31
+ ## 💻 CLI Intro
32
+
33
+ BOS AI ships with a `bos` CLI with lazy-loaded commands to keep startup times incredibly fast:
34
+
35
+ - **`bos init`**: Bootstraps a new workspace. It creates the `.bos/config.toml` file and provisions necessary data directories.
36
+ - **`bos auth`**: Set up authentication for various LLM providers and utilities.
37
+ - **`bos chat`**: Drops you into an interactive chat application to talk to the agents defined in your configuration. You can also use it in "oneshot" mode.
38
+
39
+ **LLM Providers via Auth:**
40
+ ```bash
41
+ # Chat with the Antigravity provider (requires `bos auth antigravity`)
42
+ bos chat -M "hello" -m antigravity/gemini-3.1-pro-low -a main
43
+
44
+ # Chat with the Gemini CLI provider (requires `bos auth gemini-cli`)
45
+ bos chat -M "hello" -m gemini-cli/gemini-2.5-flash -a main
46
+
47
+ # Chat with the Codex provider (requires `bos auth codex`)
48
+ bos chat -M "hello" -m codex/gpt-5.3-codex -a main
49
+ ```
50
+
51
+ **Global Options:**
52
+ - `-w`, `--workspace`: Path to the workspace directory (defaults to `.`).
53
+
54
+ ---
55
+
56
+ ## 🧠 Principles
57
+
58
+ `bos-ai` is built on a few core design principles:
59
+
60
+ - **Lightweight & Embeddable**: The heart of the framework lives in a single, comprehensive file (`core.py`), ensuring that complexity is minimized while keeping the abstractions powerfully dense.
61
+ - **Extensible at the Core**: Every significant layer—from LLM providers, to message persistence, to memory and tools—is powered by an internal Extension System.
62
+ - **Agent as an Actor**: Agents communicate via asynchronous message passing (the `Mailbox` protocol). This enables robust multi-agent orchestration without tightly coupled code.
63
+ - **Harness-Managed Lifecycle**: An `AgentHarness` is used to bootstrap, maintain, and gracefully tear down shared resources (like databases or API connections) across all agents in the workspace.
64
+
65
+ ---
66
+
67
+ ## 🔌 Extension Framework
68
+
69
+ BOS AI utilizes an `ExtensionPoint` pattern for its modular capabilities. You can seamlessly inject your own logic or override defaults decorators.
70
+
71
+ The framework provides named extension points such as:
72
+ - `@ep_provider`: Connect new LLM backends (OpenAI, Anthropic, Gemini, etc.).
73
+ - `@ep_tool`: Add new conversational tools that the LLM can invoke.
74
+ - `@ep_memory_store`: Connect alternative vector databases or key-value stores.
75
+ - `@ep_message_store`: Custom persistence logic for conversation history.
76
+ - `@ep_mailbox`: Implement distributed message-passing interfaces like Redis or RabbitMQ.
77
+ - `@ep_react_interceptor`: Hooks to orchestrate the internal ReAct loop of an agent.
78
+
79
+ Example registering a custom tool:
80
+
81
+ ```python
82
+ from bos.core import ep_tool
83
+
84
+ @ep_tool(
85
+ name="my_custom_tool",
86
+ description="Does something awesome.",
87
+ # ... additional structured metadata ...
88
+ )
89
+ async def my_custom_tool(arg1: str):
90
+ return f"Processed {arg1}"
91
+ ```
92
+
93
+ To load your extensions, simply add their module paths to the `platform.extensions` array in your workspace's `config.toml`.
94
+
95
+ ---
96
+
97
+ ## ⚙️ Configuration System
98
+
99
+ BOS AI uses a hierarchical, TOML-based configuration pattern that is specifically designed for isolation per-workspace.
100
+
101
+ When you run a command, `bos` searches upwards from the current directory to find a `.bos/config.toml` file, eventually falling back to a global `~/.bos/config.toml` if none is found.
102
+
103
+ ### Config Structure (`.bos/config.toml`)
104
+ - **`[platform]`**: Define environment variables, `.env` file locations, and structural extensions loading rules.
105
+ - **`[[platform.agents]]`**: Array of dictionaries defining your agents. You can configure `system_prompt`, limits (`max_tokens`), required `tools`, `skills`, memory definitions, and even `subagents`.
106
+ - **`[harness]`**: Define the overarching services all agents in the environment share. For example, configure the active `memory_store` directory or hook up an interceptor chain.
107
+ - **`[cli]`**: Directives and default options for the command-line application (like specifying a default agent target).
@@ -0,0 +1,46 @@
1
+ [project]
2
+ name = "bos-ai"
3
+ version = "0.1.0"
4
+ description = "Lightweight single-file agent framework"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ dependencies = [
8
+ "litellm==1.83.0",
9
+ "python-dotenv>=1.2.2",
10
+ "filelock>=3.19.1",
11
+ "click>=8.1.7",
12
+ "rich>=13.0.0",
13
+ "textual>=1.0.0",
14
+ "tomli>=2.0.0; python_version<'3.11'",
15
+ "beautifulsoup4>=4.14.3",
16
+ ]
17
+
18
+ [project.scripts]
19
+ bos = "bos.cli.entry:main"
20
+
21
+ [project.optional-dependencies]
22
+ providers = [
23
+ "oauth-cli-kit>=0.1.3",
24
+ ]
25
+ all = [
26
+ "oauth-cli-kit>=0.1.3",
27
+ ]
28
+
29
+ [build-system]
30
+ requires = ["hatchling"]
31
+ build-backend = "hatchling.build"
32
+
33
+ [tool.hatch.build.targets.wheel]
34
+ packages = ["src/bos"]
35
+
36
+ [dependency-groups]
37
+ dev = [
38
+ "pytest>=8.4.1",
39
+ "pytest-asyncio>=1.1.0",
40
+ "pytest-cov>=6.2.1",
41
+ "ruff>=0.12.4",
42
+ ]
43
+
44
+ [tool.ruff]
45
+ line-length = 120
46
+ lint.select = ["E", "F", "I"]
File without changes
@@ -0,0 +1,130 @@
1
+ from pathlib import Path
2
+
3
+ import click
4
+
5
+ from bos.core import _flock
6
+
7
+
8
+ @click.group(name="auth")
9
+ def auth():
10
+ """Manage authentication for BOS AI."""
11
+ pass
12
+
13
+
14
+ @auth.command(name="codex")
15
+ @click.option("--name", default="default", help="Name of the credential.")
16
+ def codex(name: str):
17
+ """Authenticate with OpenAI Codex."""
18
+ try:
19
+ from oauth_cli_kit import get_token, login_oauth_interactive
20
+ except ImportError:
21
+ raise click.ClickException("oauth_cli_kit not installed. Run: uv pip install oauth-cli-kit")
22
+
23
+ creds_dir = Path.home() / ".config" / "bos"
24
+ creds_dir.mkdir(parents=True, exist_ok=True)
25
+
26
+ token_path = creds_dir / f"codex_auth.{name}.json"
27
+ from oauth_cli_kit.storage import FileTokenStorage
28
+
29
+ storage = FileTokenStorage(str(token_path))
30
+
31
+ token = None
32
+ try:
33
+ token = get_token(storage=storage)
34
+ except Exception:
35
+ pass
36
+
37
+ if not (token and token.access):
38
+ click.echo("Starting interactive OAuth login...\n")
39
+ token = login_oauth_interactive(
40
+ print_fn=lambda s: click.echo(s),
41
+ prompt_fn=lambda s: click.prompt(s),
42
+ originator="tradingdesk",
43
+ storage=storage,
44
+ )
45
+
46
+ if not (token and token.access):
47
+ raise click.ClickException("Authentication failed")
48
+
49
+ click.echo(f"\n✓ Authenticated with OpenAI Codex ({getattr(token, 'account_id', '') or name})")
50
+ click.echo(f" Credentials saved to: {token_path}")
51
+
52
+
53
+ @auth.command(name="antigravity")
54
+ @click.option("--name", default="default", help="Name of the credential.")
55
+ def antigravity(name: str):
56
+ """Authenticate with Google Antigravity."""
57
+ import asyncio
58
+ import json
59
+ import webbrowser
60
+
61
+ from bos.providers.antigravity_provider import login_antigravity
62
+
63
+ creds_dir = Path.home() / ".config" / "bos"
64
+ creds_dir.mkdir(parents=True, exist_ok=True)
65
+ token_path = creds_dir / f"antigravity_auth.{name}.json"
66
+
67
+ def on_url(url: str, msg: str):
68
+ click.echo(f"{msg}\n\n {url}\n")
69
+ try:
70
+ webbrowser.open(url)
71
+ except Exception:
72
+ pass
73
+
74
+ try:
75
+ creds = asyncio.run(
76
+ login_antigravity(
77
+ on_auth=on_url,
78
+ on_progress=lambda msg: click.echo(f" {msg}"),
79
+ )
80
+ )
81
+ with _flock(token_path):
82
+ token_path.write_text(json.dumps(dict(creds.__dict__)))
83
+ click.echo(f"\n✓ Authenticated with Google Antigravity ({creds.email or ''})")
84
+ click.echo(f" Credentials saved to: {token_path}")
85
+ except Exception as e:
86
+ raise click.ClickException(f"Authentication failed: {e}")
87
+
88
+
89
+ @auth.command(name="gemini-cli")
90
+ @click.option("--name", default="default", help="Name of the credential.")
91
+ def gemini_cli(name: str) -> None:
92
+ """Authenticate with Gemini CLI.
93
+
94
+ This command initiates an interactive OAuth 2.0 PKCE flow to authenticate with
95
+ Google Cloud Code Assist using your personal Google account.
96
+ """
97
+ import asyncio
98
+ import json
99
+ import webbrowser
100
+
101
+ from bos.providers.gemini_cli_provider import login_gemini_cli
102
+
103
+ click.echo("\n" + "=" * 50)
104
+ click.echo(" Gemini CLI Authentication")
105
+ click.echo("=" * 50)
106
+
107
+ creds_dir = Path.home() / ".config" / "bos"
108
+ creds_dir.mkdir(parents=True, exist_ok=True)
109
+ token_path = creds_dir / f"gemini_cli_auth.{name}.json"
110
+
111
+ def on_url(url: str, msg: str):
112
+ click.echo(f"{msg}\n\n {url}\n")
113
+ try:
114
+ webbrowser.open(url)
115
+ except Exception:
116
+ pass
117
+
118
+ try:
119
+ creds = asyncio.run(
120
+ login_gemini_cli(
121
+ on_auth=on_url,
122
+ on_progress=lambda msg: click.echo(f" {msg}"),
123
+ )
124
+ )
125
+ with _flock(token_path):
126
+ token_path.write_text(json.dumps(dict(creds.__dict__)))
127
+ click.echo(f"\n✓ Authenticated with Gemini CLI ({creds.email or ''})")
128
+ click.echo(f" Credentials saved to: {token_path}")
129
+ except Exception as e:
130
+ raise click.ClickException(f"Authentication failed: {e}")