ayechat 0.1.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.
- ayechat-0.1.5/LICENSE +21 -0
- ayechat-0.1.5/PKG-INFO +83 -0
- ayechat-0.1.5/README.md +65 -0
- ayechat-0.1.5/pyproject.toml +40 -0
- ayechat-0.1.5/setup.cfg +4 -0
- ayechat-0.1.5/src/aye/__init__.py +0 -0
- ayechat-0.1.5/src/aye/__main__.py +227 -0
- ayechat-0.1.5/src/aye/api.py +49 -0
- ayechat-0.1.5/src/aye/auth.py +54 -0
- ayechat-0.1.5/src/aye/auto_detect_mask.py +206 -0
- ayechat-0.1.5/src/aye/config.py +53 -0
- ayechat-0.1.5/src/aye/download_plugins.py +104 -0
- ayechat-0.1.5/src/aye/repl.py +198 -0
- ayechat-0.1.5/src/aye/service.py +380 -0
- ayechat-0.1.5/src/aye/snapshot.py +359 -0
- ayechat-0.1.5/src/aye/source_collector.py +140 -0
- ayechat-0.1.5/src/aye/ui.py +59 -0
- ayechat-0.1.5/src/ayechat.egg-info/PKG-INFO +83 -0
- ayechat-0.1.5/src/ayechat.egg-info/SOURCES.txt +22 -0
- ayechat-0.1.5/src/ayechat.egg-info/dependency_links.txt +1 -0
- ayechat-0.1.5/src/ayechat.egg-info/entry_points.txt +2 -0
- ayechat-0.1.5/src/ayechat.egg-info/requires.txt +8 -0
- ayechat-0.1.5/src/ayechat.egg-info/top_level.txt +1 -0
- ayechat-0.1.5/tests/test_cli.py +13 -0
ayechat-0.1.5/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Acrotron
|
|
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.
|
ayechat-0.1.5/PKG-INFO
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ayechat
|
|
3
|
+
Version: 0.1.5
|
|
4
|
+
Summary: Aye: Terminal-first AI Code Generator
|
|
5
|
+
Author-email: "Acrotron, Inc." <info@acrotron.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: typer[all]>=0.9.0
|
|
11
|
+
Requires-Dist: httpx>=0.27.0
|
|
12
|
+
Requires-Dist: keyring>=24.0.0
|
|
13
|
+
Requires-Dist: prompt-toolkit>=3.0.0
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
16
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# aye
|
|
20
|
+
Aye: AI‑powered coding assistant for the terminal
|
|
21
|
+
|
|
22
|
+
**Aye** is a terminal‑only AI assistant that:
|
|
23
|
+
|
|
24
|
+
* generates code from natural‑language prompts,
|
|
25
|
+
* automatically snapshots the target file before each change,
|
|
26
|
+
* lets you list, view, and revert those snapshots (`aye snap …`),
|
|
27
|
+
* uses a simple token‑based authentication model (no Cognito required).
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Install from PyPI
|
|
35
|
+
pip install aye-cli
|
|
36
|
+
|
|
37
|
+
# Authenticate (you’ll get a token from your backend)
|
|
38
|
+
aye login
|
|
39
|
+
|
|
40
|
+
# One‑shot generation
|
|
41
|
+
aye generate "create a Python function that parses CSV" -f utils.py
|
|
42
|
+
|
|
43
|
+
# Interactive chat (optional)
|
|
44
|
+
aye chat -f utils.py
|
|
45
|
+
|
|
46
|
+
# Undo / snapshot utilities
|
|
47
|
+
aye snap list utils.py
|
|
48
|
+
aye snap show utils.py 20241012T153045
|
|
49
|
+
aye snap revert utils.py 20241012T153045
|
|
50
|
+
|
|
51
|
+
Development
|
|
52
|
+
|
|
53
|
+
# Clone the repo
|
|
54
|
+
git clone https://github.com/yourorg/aye.git
|
|
55
|
+
cd aye
|
|
56
|
+
|
|
57
|
+
# Create a virtual environment
|
|
58
|
+
python -m venv .venv && source .venv/bin/activate
|
|
59
|
+
|
|
60
|
+
# Install in editable mode with dev deps
|
|
61
|
+
pip install -e .[dev]
|
|
62
|
+
|
|
63
|
+
# Run the test suite
|
|
64
|
+
pytest -q
|
|
65
|
+
|
|
66
|
+
Packaging & distribution
|
|
67
|
+
|
|
68
|
+
# Build a wheel and source distribution
|
|
69
|
+
python -m build
|
|
70
|
+
|
|
71
|
+
# Install the wheel locally for a clean test
|
|
72
|
+
pip install dist/aye_cli-0.1.0-py3-none-any.whl
|
|
73
|
+
|
|
74
|
+
The console script aye will be available on the PATH.
|
|
75
|
+
License
|
|
76
|
+
|
|
77
|
+
MIT – see the LICENSE file (or the license header in pyproject.toml).
|
|
78
|
+
Contributing
|
|
79
|
+
|
|
80
|
+
Feel free to open issues or submit pull requests.
|
|
81
|
+
All contributions are welcome; just follow the usual GitHub workflow.
|
|
82
|
+
|
|
83
|
+
|
ayechat-0.1.5/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# aye
|
|
2
|
+
Aye: AI‑powered coding assistant for the terminal
|
|
3
|
+
|
|
4
|
+
**Aye** is a terminal‑only AI assistant that:
|
|
5
|
+
|
|
6
|
+
* generates code from natural‑language prompts,
|
|
7
|
+
* automatically snapshots the target file before each change,
|
|
8
|
+
* lets you list, view, and revert those snapshots (`aye snap …`),
|
|
9
|
+
* uses a simple token‑based authentication model (no Cognito required).
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Install from PyPI
|
|
17
|
+
pip install aye-cli
|
|
18
|
+
|
|
19
|
+
# Authenticate (you’ll get a token from your backend)
|
|
20
|
+
aye login
|
|
21
|
+
|
|
22
|
+
# One‑shot generation
|
|
23
|
+
aye generate "create a Python function that parses CSV" -f utils.py
|
|
24
|
+
|
|
25
|
+
# Interactive chat (optional)
|
|
26
|
+
aye chat -f utils.py
|
|
27
|
+
|
|
28
|
+
# Undo / snapshot utilities
|
|
29
|
+
aye snap list utils.py
|
|
30
|
+
aye snap show utils.py 20241012T153045
|
|
31
|
+
aye snap revert utils.py 20241012T153045
|
|
32
|
+
|
|
33
|
+
Development
|
|
34
|
+
|
|
35
|
+
# Clone the repo
|
|
36
|
+
git clone https://github.com/yourorg/aye.git
|
|
37
|
+
cd aye
|
|
38
|
+
|
|
39
|
+
# Create a virtual environment
|
|
40
|
+
python -m venv .venv && source .venv/bin/activate
|
|
41
|
+
|
|
42
|
+
# Install in editable mode with dev deps
|
|
43
|
+
pip install -e .[dev]
|
|
44
|
+
|
|
45
|
+
# Run the test suite
|
|
46
|
+
pytest -q
|
|
47
|
+
|
|
48
|
+
Packaging & distribution
|
|
49
|
+
|
|
50
|
+
# Build a wheel and source distribution
|
|
51
|
+
python -m build
|
|
52
|
+
|
|
53
|
+
# Install the wheel locally for a clean test
|
|
54
|
+
pip install dist/aye_cli-0.1.0-py3-none-any.whl
|
|
55
|
+
|
|
56
|
+
The console script aye will be available on the PATH.
|
|
57
|
+
License
|
|
58
|
+
|
|
59
|
+
MIT – see the LICENSE file (or the license header in pyproject.toml).
|
|
60
|
+
Contributing
|
|
61
|
+
|
|
62
|
+
Feel free to open issues or submit pull requests.
|
|
63
|
+
All contributions are welcome; just follow the usual GitHub workflow.
|
|
64
|
+
|
|
65
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ayechat" # the name that will appear on PyPI
|
|
3
|
+
version = "0.1.5"
|
|
4
|
+
description = "Aye: Terminal-first AI Code Generator"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.8"
|
|
7
|
+
license = {text = "MIT"}
|
|
8
|
+
authors = [{name = "Acrotron, Inc.", email = "info@acrotron.com"}]
|
|
9
|
+
|
|
10
|
+
# Runtime dependencies
|
|
11
|
+
dependencies = [
|
|
12
|
+
"typer[all]>=0.9.0", # CLI framework + rich formatting
|
|
13
|
+
"httpx>=0.27.0", # HTTP client
|
|
14
|
+
"keyring>=24.0.0", # Secure storage for the token
|
|
15
|
+
"prompt-toolkit>=3.0.0", # REPL UI
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
# Optional development dependencies (tests, linting, etc.)
|
|
19
|
+
[project.optional-dependencies]
|
|
20
|
+
dev = [
|
|
21
|
+
"pytest>=7.0", # test runner
|
|
22
|
+
"ruff>=0.6", # linting/formatter (optional)
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
# Console script – after installation `aye` will be on the PATH
|
|
26
|
+
[project.scripts]
|
|
27
|
+
aye = "aye.__main__:app"
|
|
28
|
+
|
|
29
|
+
# -------------------------------------------------
|
|
30
|
+
# Build system configuration – tells setuptools where the code lives
|
|
31
|
+
# -------------------------------------------------
|
|
32
|
+
[build-system]
|
|
33
|
+
requires = ["setuptools", "wheel"]
|
|
34
|
+
build-backend = "setuptools.build_meta"
|
|
35
|
+
|
|
36
|
+
[tool.setuptools]
|
|
37
|
+
package-dir = {"" = "src"} # “src/” is the source root
|
|
38
|
+
packages = ["aye"] # the only package we ship
|
|
39
|
+
include-package-data = true
|
|
40
|
+
|
ayechat-0.1.5/setup.cfg
ADDED
|
File without changes
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import typer
|
|
3
|
+
|
|
4
|
+
from .service import (
|
|
5
|
+
handle_login,
|
|
6
|
+
handle_logout,
|
|
7
|
+
handle_generate_cmd,
|
|
8
|
+
handle_chat,
|
|
9
|
+
handle_history_cmd,
|
|
10
|
+
handle_snap_show_cmd,
|
|
11
|
+
handle_restore_cmd,
|
|
12
|
+
handle_prune_cmd,
|
|
13
|
+
handle_cleanup_cmd,
|
|
14
|
+
handle_config_list,
|
|
15
|
+
handle_config_set,
|
|
16
|
+
handle_config_get,
|
|
17
|
+
handle_config_delete
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from .config import load_config
|
|
21
|
+
|
|
22
|
+
# Load configuration at startup
|
|
23
|
+
load_config()
|
|
24
|
+
|
|
25
|
+
app = typer.Typer(help="Aye: AI‑powered coding assistant for the terminal")
|
|
26
|
+
|
|
27
|
+
# Create subcommands
|
|
28
|
+
auth_app = typer.Typer(help="Authentication commands")
|
|
29
|
+
snap_app = typer.Typer(help="Snapshot management commands")
|
|
30
|
+
|
|
31
|
+
app.add_typer(auth_app, name="auth")
|
|
32
|
+
app.add_typer(snap_app, name="snap")
|
|
33
|
+
|
|
34
|
+
# ----------------------------------------------------------------------
|
|
35
|
+
# Authentication commands
|
|
36
|
+
# ----------------------------------------------------------------------
|
|
37
|
+
@auth_app.command()
|
|
38
|
+
def login():
|
|
39
|
+
"""
|
|
40
|
+
Configure personal access token for authenticating with the aye service.
|
|
41
|
+
"""
|
|
42
|
+
handle_login()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@auth_app.command()
|
|
46
|
+
def logout():
|
|
47
|
+
"""
|
|
48
|
+
Remove the stored aye credentials.
|
|
49
|
+
|
|
50
|
+
Examples: \n
|
|
51
|
+
aye auth logout
|
|
52
|
+
"""
|
|
53
|
+
handle_logout()
|
|
54
|
+
|
|
55
|
+
# ----------------------------------------------------------------------
|
|
56
|
+
# One‑shot generation
|
|
57
|
+
# ----------------------------------------------------------------------
|
|
58
|
+
@app.command()
|
|
59
|
+
def generate(
|
|
60
|
+
prompt: str = typer.Argument(..., help="Prompt for the LLM"),
|
|
61
|
+
mode: str = typer.Option(
|
|
62
|
+
"replace",
|
|
63
|
+
"--mode",
|
|
64
|
+
"-m",
|
|
65
|
+
help="replace | append | insert (default: replace)",
|
|
66
|
+
),
|
|
67
|
+
):
|
|
68
|
+
"""
|
|
69
|
+
Send a single prompt to the backend.
|
|
70
|
+
|
|
71
|
+
Examples: \n
|
|
72
|
+
aye generate "Create a function that reverses a string" \n
|
|
73
|
+
aye generate "Add type hints to this function" --mode append \n
|
|
74
|
+
"""
|
|
75
|
+
handle_generate_cmd(prompt, mode)
|
|
76
|
+
|
|
77
|
+
# ----------------------------------------------------------------------
|
|
78
|
+
# Interactive REPL (chat) command
|
|
79
|
+
# ----------------------------------------------------------------------
|
|
80
|
+
@app.command()
|
|
81
|
+
def chat(
|
|
82
|
+
root: Path = typer.Option(
|
|
83
|
+
None, "--root", "-r", help="Root folder where source files are located."
|
|
84
|
+
),
|
|
85
|
+
file_mask: str = typer.Option(
|
|
86
|
+
"*.py", "--file-mask", "-m", help="File mask for source files to include into generation. Comma-separated masks are allowed."
|
|
87
|
+
),
|
|
88
|
+
):
|
|
89
|
+
"""
|
|
90
|
+
Start an interactive REPL. Use /exit or Ctrl‑D to leave.
|
|
91
|
+
|
|
92
|
+
Examples: \n
|
|
93
|
+
aye chat \n
|
|
94
|
+
aye chat --root ./src \n
|
|
95
|
+
aye chat --file-mask "*.js" --root ./frontend \n
|
|
96
|
+
"""
|
|
97
|
+
handle_chat(root, file_mask)
|
|
98
|
+
|
|
99
|
+
# ----------------------------------------------------------------------
|
|
100
|
+
# Snapshot commands
|
|
101
|
+
# ----------------------------------------------------------------------
|
|
102
|
+
@snap_app.command("history")
|
|
103
|
+
def history(
|
|
104
|
+
file: Path = typer.Argument(None, help="File to list snapshots for")
|
|
105
|
+
):
|
|
106
|
+
"""
|
|
107
|
+
Show timestamps of saved snapshots for *file* or all snapshots if no file provided.
|
|
108
|
+
|
|
109
|
+
Examples: \n
|
|
110
|
+
aye snap history \n
|
|
111
|
+
aye snap history src/main.py \n
|
|
112
|
+
"""
|
|
113
|
+
handle_history_cmd(file)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@snap_app.command("show")
|
|
117
|
+
def show(
|
|
118
|
+
file: Path = typer.Argument(..., help="File whose snapshot to show"),
|
|
119
|
+
ordinal: str = typer.Argument(..., help="Ordinal of the snapshot (e.g., 001)"),
|
|
120
|
+
):
|
|
121
|
+
"""
|
|
122
|
+
Print the contents of a specific snapshot.
|
|
123
|
+
|
|
124
|
+
Examples: \n
|
|
125
|
+
aye snap show src/main.py 001 \n
|
|
126
|
+
"""
|
|
127
|
+
handle_snap_show_cmd(file, ordinal)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@snap_app.command("restore")
|
|
131
|
+
def restore(
|
|
132
|
+
ordinal: str = typer.Argument(None, help="Ordinal of the snapshot to restore (e.g., 001, default: latest)"),
|
|
133
|
+
file_name: str = typer.Argument(None, help="Specific file to restore from the snapshot"),
|
|
134
|
+
):
|
|
135
|
+
"""
|
|
136
|
+
Replace all files with the latest snapshot or specified snapshot by ordinal.
|
|
137
|
+
If file_name is provided, only that file is restored.
|
|
138
|
+
|
|
139
|
+
Examples:\n
|
|
140
|
+
aye snap restore \n
|
|
141
|
+
aye snap restore 001 \n
|
|
142
|
+
aye snap restore 001 myfile.py \n
|
|
143
|
+
"""
|
|
144
|
+
handle_restore_cmd(ordinal, file_name)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# ----------------------------------------------------------------------
|
|
148
|
+
# Snapshot cleanup/pruning commands
|
|
149
|
+
# ----------------------------------------------------------------------
|
|
150
|
+
@snap_app.command()
|
|
151
|
+
def keep(
|
|
152
|
+
num: int = typer.Option(10, "--num", "-n", help="Number of recent snapshots to keep (default: 10)"),
|
|
153
|
+
):
|
|
154
|
+
"""
|
|
155
|
+
Delete all but the most recent N snapshots.
|
|
156
|
+
|
|
157
|
+
Examples: \n
|
|
158
|
+
aye snap keep \n
|
|
159
|
+
aye snap keep --num 5 \n
|
|
160
|
+
aye snap keep -n 3 \n
|
|
161
|
+
"""
|
|
162
|
+
handle_prune_cmd(num)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@snap_app.command()
|
|
166
|
+
def cleanup(
|
|
167
|
+
days: int = typer.Option(30, "--days", "-d", help="Delete snapshots older than N days (default: 30)"),
|
|
168
|
+
):
|
|
169
|
+
"""
|
|
170
|
+
Delete snapshots older than N days.
|
|
171
|
+
|
|
172
|
+
Examples: \n
|
|
173
|
+
aye snap cleanup \n
|
|
174
|
+
aye snap cleanup --days 7 \n
|
|
175
|
+
aye snap cleanup -d 14 \n
|
|
176
|
+
"""
|
|
177
|
+
handle_cleanup_cmd(days)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# ----------------------------------------------------------------------
|
|
181
|
+
# Configuration management commands
|
|
182
|
+
# ----------------------------------------------------------------------
|
|
183
|
+
@app.command()
|
|
184
|
+
def config(
|
|
185
|
+
action: str = typer.Argument(..., help="Action to perform: list, get, set, delete"),
|
|
186
|
+
key: str = typer.Argument(None, help="Configuration key"),
|
|
187
|
+
value: str = typer.Argument(None, help="Configuration value (for set action)"),
|
|
188
|
+
):
|
|
189
|
+
"""
|
|
190
|
+
Manage configuration values for file masks, root directories, and other settings.
|
|
191
|
+
|
|
192
|
+
Actions: \n
|
|
193
|
+
- list: Show all configuration values \n
|
|
194
|
+
- get: Retrieve a specific configuration value \n
|
|
195
|
+
- set: Set a configuration value \n
|
|
196
|
+
- delete: Remove a configuration value \n
|
|
197
|
+
|
|
198
|
+
Examples: \n
|
|
199
|
+
aye config list \n
|
|
200
|
+
aye config get file_mask \n
|
|
201
|
+
aye config set file_mask "*.py,*.js" \n
|
|
202
|
+
aye config delete file_mask \n
|
|
203
|
+
"""
|
|
204
|
+
if action == "list":
|
|
205
|
+
handle_config_list()
|
|
206
|
+
elif action == "get":
|
|
207
|
+
if not key:
|
|
208
|
+
typer.echo("[red]Error:[/] Key is required for get action.")
|
|
209
|
+
raise typer.Exit(code=1)
|
|
210
|
+
handle_config_get(key)
|
|
211
|
+
elif action == "set":
|
|
212
|
+
if not key or not value:
|
|
213
|
+
typer.echo("[red]Error:[/] Key and value are required for set action.")
|
|
214
|
+
raise typer.Exit(code=1)
|
|
215
|
+
handle_config_set(key, value)
|
|
216
|
+
elif action == "delete":
|
|
217
|
+
if not key:
|
|
218
|
+
typer.echo("[red]Error:[/] Key is required for delete action.")
|
|
219
|
+
raise typer.Exit(code=1)
|
|
220
|
+
handle_config_delete(key)
|
|
221
|
+
else:
|
|
222
|
+
typer.echo(f"[red]Error:[/] Invalid action '{action}'. Use: list, get, set, delete")
|
|
223
|
+
raise typer.Exit(code=1)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
if __name__ == "__main__":
|
|
227
|
+
app()
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Any, Dict
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
from .auth import get_token
|
|
6
|
+
|
|
7
|
+
# -------------------------------------------------
|
|
8
|
+
# 👉 EDIT THIS TO POINT TO YOUR SERVICE
|
|
9
|
+
# -------------------------------------------------
|
|
10
|
+
BASE_URL = "https://api.acrotron.com"
|
|
11
|
+
TIMEOUT = 30.0
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _auth_headers() -> Dict[str, str]:
|
|
15
|
+
token = get_token()
|
|
16
|
+
if not token:
|
|
17
|
+
raise RuntimeError("No auth token – run `aye login` first.")
|
|
18
|
+
return {"Authorization": f"Bearer {token}"}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def cli_invoke(user_id="v@acrotron.com", chat_id=-1, message="", source_files={}):
|
|
22
|
+
payload = {"user_id": user_id, "chat_id": chat_id, "message": message, "source_files": source_files}
|
|
23
|
+
|
|
24
|
+
url = f"{BASE_URL}/invoke_cli"
|
|
25
|
+
|
|
26
|
+
with httpx.Client(timeout=TIMEOUT, verify=False) as client:
|
|
27
|
+
resp = client.post(url, json=payload, headers=_auth_headers())
|
|
28
|
+
resp.raise_for_status()
|
|
29
|
+
return resp.json()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def fetch_plugin_manifest():
|
|
33
|
+
"""Fetch the plugin manifest from the server."""
|
|
34
|
+
url = f"{BASE_URL}/plugins"
|
|
35
|
+
|
|
36
|
+
with httpx.Client(timeout=TIMEOUT, verify=False) as client:
|
|
37
|
+
resp = client.post(url, headers=_auth_headers())
|
|
38
|
+
resp.raise_for_status()
|
|
39
|
+
return resp.json()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def fetch_server_time() -> int:
|
|
43
|
+
"""Fetch the current server timestamp."""
|
|
44
|
+
url = f"{BASE_URL}/time"
|
|
45
|
+
|
|
46
|
+
with httpx.Client(timeout=TIMEOUT, verify=False) as client:
|
|
47
|
+
resp = client.get(url)
|
|
48
|
+
resp.raise_for_status()
|
|
49
|
+
return resp.json()['timestamp']
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# auth.py
|
|
2
|
+
import os
|
|
3
|
+
import typer
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from rich import print as rprint
|
|
6
|
+
|
|
7
|
+
SERVICE_NAME = "aye-cli"
|
|
8
|
+
TOKEN_ENV_VAR = "AYE_TOKEN"
|
|
9
|
+
TOKEN_FILE = Path.home() / ".ayecfg"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def store_token(token: str) -> None:
|
|
13
|
+
"""Persist the token in ~/.ayecfg (unless AYE_TOKEN is set)."""
|
|
14
|
+
TOKEN_FILE.write_text(token.strip(), encoding="utf-8")
|
|
15
|
+
TOKEN_FILE.chmod(0o600) # POSIX only
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_token() -> str | None:
|
|
19
|
+
"""Return the stored token (env → file)."""
|
|
20
|
+
# 1. Try environment variable first
|
|
21
|
+
env_token = os.getenv(TOKEN_ENV_VAR)
|
|
22
|
+
if env_token:
|
|
23
|
+
return env_token.strip()
|
|
24
|
+
|
|
25
|
+
# 2. Try config file
|
|
26
|
+
if TOKEN_FILE.is_file():
|
|
27
|
+
try:
|
|
28
|
+
return TOKEN_FILE.read_text(encoding="utf-8").strip()
|
|
29
|
+
except Exception:
|
|
30
|
+
pass # Continue if file read fails
|
|
31
|
+
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def delete_token() -> None:
|
|
36
|
+
"""Delete the token from file (but not environment)."""
|
|
37
|
+
# Delete the file-based token
|
|
38
|
+
TOKEN_FILE.unlink(missing_ok=True)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def login_flow() -> None:
|
|
42
|
+
"""
|
|
43
|
+
Small login flow:
|
|
44
|
+
1. Prompt user to obtain token at https://aye.acrotron.com
|
|
45
|
+
2. User enters/pastes the token in terminal (hidden input)
|
|
46
|
+
3. Save the token to ~/.ayecfg (if AYE_TOKEN not set)
|
|
47
|
+
"""
|
|
48
|
+
#typer.echo(
|
|
49
|
+
# "Obtain your personal access token at https://aye.acrotron.com"
|
|
50
|
+
#)
|
|
51
|
+
rprint("[yellow]Obtain your personal access token at https://aye.acrotron.com[/]")
|
|
52
|
+
token = typer.prompt("Paste your token", hide_input=True)
|
|
53
|
+
store_token(token.strip())
|
|
54
|
+
typer.secho("✅ Token saved.", fg=typer.colors.GREEN)
|