AntGravityCLI 1.0.2__py3-none-any.whl
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.
- antgravity_cli/__init__.py +0 -0
- antgravity_cli/config.py +68 -0
- antgravity_cli/console_io.py +167 -0
- antgravity_cli/handlers.py +18 -0
- antgravity_cli/i18n.py +73 -0
- antgravity_cli/interfaces.py +44 -0
- antgravity_cli/list_skills.py +53 -0
- antgravity_cli/main.py +52 -0
- antgravity_cli/parser.py +106 -0
- antgravity_cli/repl.py +126 -0
- antgravity_cli/runner.py +31 -0
- antgravity_cli/translate/en-us/config.json +5 -0
- antgravity_cli/translate/en-us/console_io.json +6 -0
- antgravity_cli/translate/en-us/handlers.json +6 -0
- antgravity_cli/translate/en-us/interfaces.json +1 -0
- antgravity_cli/translate/en-us/list_skills.json +5 -0
- antgravity_cli/translate/en-us/main.json +1 -0
- antgravity_cli/translate/en-us/parser.json +8 -0
- antgravity_cli/translate/en-us/repl.json +12 -0
- antgravity_cli/translate/en-us/runner.json +1 -0
- antgravity_cli/translate/en-us/test_cli.json +1 -0
- antgravity_cli/translate/pt-br/config.json +5 -0
- antgravity_cli/translate/pt-br/console_io.json +6 -0
- antgravity_cli/translate/pt-br/handlers.json +6 -0
- antgravity_cli/translate/pt-br/interfaces.json +1 -0
- antgravity_cli/translate/pt-br/list_skills.json +5 -0
- antgravity_cli/translate/pt-br/main.json +1 -0
- antgravity_cli/translate/pt-br/parser.json +8 -0
- antgravity_cli/translate/pt-br/repl.json +12 -0
- antgravity_cli/translate/pt-br/runner.json +1 -0
- antgravity_cli/translate/pt-br/test_cli.json +1 -0
- antgravity_cli/utils.py +10 -0
- antgravitycli-1.0.2.dist-info/METADATA +232 -0
- antgravitycli-1.0.2.dist-info/RECORD +37 -0
- antgravitycli-1.0.2.dist-info/WHEEL +5 -0
- antgravitycli-1.0.2.dist-info/entry_points.txt +2 -0
- antgravitycli-1.0.2.dist-info/top_level.txt +1 -0
|
File without changes
|
antgravity_cli/config.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import click
|
|
3
|
+
from colorama import Fore, Style
|
|
4
|
+
from google.antigravity import LocalAgentConfig
|
|
5
|
+
from google.antigravity.hooks import policy
|
|
6
|
+
|
|
7
|
+
from . import i18n
|
|
8
|
+
from .handlers import cli_ask_user_handler
|
|
9
|
+
|
|
10
|
+
def setup_agent_config(model, yolo, workspace, system_instruction, api_key, skills_path) -> LocalAgentConfig:
|
|
11
|
+
"""Configures and returns the agent's LocalAgentConfig, resolving keys, paths, and policies."""
|
|
12
|
+
# 1. Resolve API Key
|
|
13
|
+
resolved_api_key = api_key or os.environ.get("GEMINI_API_KEY")
|
|
14
|
+
if not resolved_api_key:
|
|
15
|
+
raise ValueError(i18n.t("config", "api_key_not_found"))
|
|
16
|
+
|
|
17
|
+
# 2. Configure Permission Policies
|
|
18
|
+
resolved_workspace = [os.path.abspath(w) for w in workspace] if workspace else [os.path.abspath(".")]
|
|
19
|
+
policies_list = []
|
|
20
|
+
for p in policy.workspace_only(resolved_workspace):
|
|
21
|
+
policies_list.append(p)
|
|
22
|
+
|
|
23
|
+
if yolo:
|
|
24
|
+
policies_list.append(policy.allow_all())
|
|
25
|
+
click.echo(f"{Fore.LIGHTRED_EX}{i18n.t('config', 'yolo_warning')}{Style.RESET_ALL}")
|
|
26
|
+
else:
|
|
27
|
+
for p in policy.confirm_run_command(handler=cli_ask_user_handler):
|
|
28
|
+
policies_list.append(p)
|
|
29
|
+
|
|
30
|
+
# 3. Resolve System Instructions
|
|
31
|
+
sys_inst = None
|
|
32
|
+
if system_instruction:
|
|
33
|
+
if os.path.isfile(system_instruction):
|
|
34
|
+
try:
|
|
35
|
+
with open(system_instruction, "r", encoding="utf-8") as f:
|
|
36
|
+
sys_inst = f.read()
|
|
37
|
+
except Exception as e:
|
|
38
|
+
raise IOError(i18n.t("config", "error_reading_instructions", error=str(e)))
|
|
39
|
+
else:
|
|
40
|
+
sys_inst = system_instruction
|
|
41
|
+
|
|
42
|
+
# 4. Resolve Skills
|
|
43
|
+
raw_skills = list(skills_path) if skills_path else []
|
|
44
|
+
|
|
45
|
+
# Always include the script's physical installation directory's .agents/skills folder
|
|
46
|
+
from .utils import get_base_path
|
|
47
|
+
base_dir = get_base_path()
|
|
48
|
+
cli_skills_dir = os.path.join(base_dir, ".agents", "skills")
|
|
49
|
+
if os.path.isdir(cli_skills_dir) and cli_skills_dir not in raw_skills:
|
|
50
|
+
raw_skills.append(cli_skills_dir)
|
|
51
|
+
|
|
52
|
+
# Normalize paths to absolute normalized format and deduplicate (preserving order)
|
|
53
|
+
resolved_skills = []
|
|
54
|
+
for path in raw_skills:
|
|
55
|
+
if path:
|
|
56
|
+
abs_path = os.path.abspath(os.path.normpath(path))
|
|
57
|
+
if abs_path not in resolved_skills:
|
|
58
|
+
resolved_skills.append(abs_path)
|
|
59
|
+
|
|
60
|
+
# 5. Build Agent Configuration
|
|
61
|
+
return LocalAgentConfig(
|
|
62
|
+
model=model,
|
|
63
|
+
api_key=resolved_api_key,
|
|
64
|
+
policies=policies_list,
|
|
65
|
+
system_instructions=sys_inst,
|
|
66
|
+
workspaces=resolved_workspace,
|
|
67
|
+
skills_paths=resolved_skills if resolved_skills else None
|
|
68
|
+
)
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import re
|
|
3
|
+
import colorama
|
|
4
|
+
from colorama import Fore, Style
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.markdown import Markdown
|
|
7
|
+
from rich.live import Live
|
|
8
|
+
from . import i18n
|
|
9
|
+
from .interfaces import OutputWriter, InputReader
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
from prompt_toolkit import PromptSession
|
|
13
|
+
from prompt_toolkit.completion import WordCompleter
|
|
14
|
+
from prompt_toolkit.formatted_text import ANSI
|
|
15
|
+
HAS_PROMPT_TOOLKIT = True
|
|
16
|
+
except ImportError:
|
|
17
|
+
HAS_PROMPT_TOOLKIT = False
|
|
18
|
+
|
|
19
|
+
if HAS_PROMPT_TOOLKIT:
|
|
20
|
+
# Pattern to match slash commands as a single word in prompt-toolkit
|
|
21
|
+
_PATTERN_CMD = re.compile(r'/[a-zA-Z0-9_-]*')
|
|
22
|
+
|
|
23
|
+
class CommandCompleter(WordCompleter):
|
|
24
|
+
"""Custom WordCompleter that triggers only when the word before cursor starts with a slash '/'."""
|
|
25
|
+
def get_completions(self, document, complete_event):
|
|
26
|
+
word_before_cursor = ""
|
|
27
|
+
if self.pattern:
|
|
28
|
+
matches = list(self.pattern.finditer(document.text_before_cursor))
|
|
29
|
+
if matches:
|
|
30
|
+
match = matches[-1]
|
|
31
|
+
if match.end() == len(document.text_before_cursor):
|
|
32
|
+
word_before_cursor = match.group()
|
|
33
|
+
|
|
34
|
+
if not word_before_cursor.startswith('/'):
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
yield from super().get_completions(document, complete_event)
|
|
38
|
+
|
|
39
|
+
# Initialize colorama for console color support (especially Windows)
|
|
40
|
+
colorama.init()
|
|
41
|
+
|
|
42
|
+
class ConsoleOutputWriter(OutputWriter):
|
|
43
|
+
"""Concrete terminal output writer using colorama and rich for Markdown (SRP)."""
|
|
44
|
+
def __init__(self):
|
|
45
|
+
self._first_text = True
|
|
46
|
+
self._console = Console(force_terminal=True)
|
|
47
|
+
self._text_buffer = ""
|
|
48
|
+
self._live = None
|
|
49
|
+
self._loading_active = False
|
|
50
|
+
self._loading_task = None
|
|
51
|
+
|
|
52
|
+
def _stop_live(self) -> None:
|
|
53
|
+
if self._live:
|
|
54
|
+
try:
|
|
55
|
+
self._live.stop()
|
|
56
|
+
except Exception:
|
|
57
|
+
pass
|
|
58
|
+
self._live = None
|
|
59
|
+
|
|
60
|
+
def start_loading(self, message: str = None) -> None:
|
|
61
|
+
"""Starts the visual processing indicator (animated via asyncio with static fallback)."""
|
|
62
|
+
self._stop_live()
|
|
63
|
+
self.stop_loading()
|
|
64
|
+
|
|
65
|
+
if message is None:
|
|
66
|
+
message = i18n.t("console_io", "thinking")
|
|
67
|
+
|
|
68
|
+
# Remove trailing periods from the message so the animation controls the flow of dots
|
|
69
|
+
message = message.rstrip(".")
|
|
70
|
+
self._loading_active = True
|
|
71
|
+
try:
|
|
72
|
+
self._loading_task = asyncio.create_task(self._animate_loading(message))
|
|
73
|
+
except RuntimeError:
|
|
74
|
+
print(f"{Fore.GREEN}{Style.BRIGHT}{message}...{Style.RESET_ALL}", end="", flush=True)
|
|
75
|
+
self._loading_task = None
|
|
76
|
+
|
|
77
|
+
async def _animate_loading(self, message: str) -> None:
|
|
78
|
+
"""Coroutine that animates the growing loading dots."""
|
|
79
|
+
dots = 0
|
|
80
|
+
try:
|
|
81
|
+
while self._loading_active:
|
|
82
|
+
dots_str = "." * dots + " " * (3 - dots)
|
|
83
|
+
print(f"\r{Fore.GREEN}{Style.BRIGHT}{message}{dots_str}{Style.RESET_ALL}", end="", flush=True)
|
|
84
|
+
await asyncio.sleep(0.5)
|
|
85
|
+
dots = (dots + 1) % 4
|
|
86
|
+
except asyncio.CancelledError:
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
def stop_loading(self) -> None:
|
|
90
|
+
"""Stops the visual processing indicator and clears the line."""
|
|
91
|
+
if self._loading_active:
|
|
92
|
+
self._loading_active = False
|
|
93
|
+
if self._loading_task:
|
|
94
|
+
self._loading_task.cancel()
|
|
95
|
+
self._loading_task = None
|
|
96
|
+
# Clear the line by overwriting it with spaces using carriage return
|
|
97
|
+
print("\r" + " " * 40 + "\r", end="", flush=True)
|
|
98
|
+
|
|
99
|
+
def write_thought(self, text: str) -> None:
|
|
100
|
+
self.stop_loading()
|
|
101
|
+
self._stop_live()
|
|
102
|
+
# Thoughts in dim/italic tone using rich
|
|
103
|
+
self._console.print(f"[dim]{text}[/dim]", end="", highlight=False)
|
|
104
|
+
|
|
105
|
+
def write_text(self, text: str) -> None:
|
|
106
|
+
self.stop_loading()
|
|
107
|
+
if self._first_text:
|
|
108
|
+
self._console.print(f"\n[bold magenta]AntGravity > [/bold magenta]")
|
|
109
|
+
self._first_text = False
|
|
110
|
+
self._text_buffer = ""
|
|
111
|
+
# Initializes real-time (Live) Markdown rendering
|
|
112
|
+
self._live = Live(Markdown(self._text_buffer), console=self._console, auto_refresh=True)
|
|
113
|
+
self._live.start()
|
|
114
|
+
|
|
115
|
+
self._text_buffer += text
|
|
116
|
+
if self._live:
|
|
117
|
+
self._live.update(Markdown(self._text_buffer))
|
|
118
|
+
|
|
119
|
+
def write_tool_call(self, name: str, args: dict) -> None:
|
|
120
|
+
self.stop_loading()
|
|
121
|
+
self._stop_live()
|
|
122
|
+
self._console.print(f"\n[yellow]{i18n.t('console_io', 'tool_calling', name=name, args=args)}[/yellow]")
|
|
123
|
+
|
|
124
|
+
def write_tool_result(self, name: str, result: str, error: str = None) -> None:
|
|
125
|
+
self.stop_loading()
|
|
126
|
+
self._stop_live()
|
|
127
|
+
if error:
|
|
128
|
+
self._console.print(f"\n[red]{i18n.t('console_io', 'tool_error', name=name, error=error)}[/red]")
|
|
129
|
+
else:
|
|
130
|
+
self._console.print(f"\n[blue]{i18n.t('console_io', 'tool_result', name=name, result=result)}[/blue]")
|
|
131
|
+
|
|
132
|
+
def reset(self) -> None:
|
|
133
|
+
"""Resets the writer state for a new text stream."""
|
|
134
|
+
self.stop_loading()
|
|
135
|
+
self._stop_live()
|
|
136
|
+
self._first_text = True
|
|
137
|
+
self._text_buffer = ""
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class ConsoleInputReader(InputReader):
|
|
141
|
+
"""Concrete terminal input reader using input() or prompt_toolkit (SRP)."""
|
|
142
|
+
def __init__(self):
|
|
143
|
+
self._session = None
|
|
144
|
+
|
|
145
|
+
async def read_input(self, prompt_text: str, suggestions: list[str] = None) -> str:
|
|
146
|
+
prompt_with_color = f"\n{Fore.CYAN}{prompt_text}{Style.RESET_ALL}"
|
|
147
|
+
|
|
148
|
+
if HAS_PROMPT_TOOLKIT and suggestions:
|
|
149
|
+
try:
|
|
150
|
+
# Initialize the session on-demand to avoid failure during instantiation in tests or CI/CD
|
|
151
|
+
if self._session is None:
|
|
152
|
+
self._session = PromptSession()
|
|
153
|
+
|
|
154
|
+
completer = CommandCompleter(suggestions, ignore_case=True, pattern=_PATTERN_CMD)
|
|
155
|
+
# Use prompt_toolkit's async session integrated with asyncio
|
|
156
|
+
return (await self._session.prompt_async(
|
|
157
|
+
ANSI(prompt_with_color),
|
|
158
|
+
completer=completer,
|
|
159
|
+
complete_while_typing=True
|
|
160
|
+
)).strip()
|
|
161
|
+
except (KeyboardInterrupt, EOFError):
|
|
162
|
+
raise
|
|
163
|
+
except Exception:
|
|
164
|
+
# Safe fallback to native input() in case of prompt_toolkit errors (e.g. NoConsoleScreenBufferError)
|
|
165
|
+
return input(prompt_with_color).strip()
|
|
166
|
+
else:
|
|
167
|
+
return input(prompt_with_color).strip()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import colorama
|
|
2
|
+
from colorama import Fore, Style
|
|
3
|
+
|
|
4
|
+
from . import i18n
|
|
5
|
+
|
|
6
|
+
# Initialize colorama for console color support (especially Windows)
|
|
7
|
+
colorama.init()
|
|
8
|
+
|
|
9
|
+
def cli_ask_user_handler(tool_call) -> bool:
|
|
10
|
+
"""Handler to request user approval in the terminal in safe mode."""
|
|
11
|
+
print(f"\n{Fore.LIGHTYELLOW_EX}{i18n.t('handlers', 'agent_wants_to_execute', name=tool_call.name)}{Style.RESET_ALL}")
|
|
12
|
+
print(f"{i18n.t('handlers', 'arguments', args=tool_call.args)}")
|
|
13
|
+
try:
|
|
14
|
+
ans = input(f"{Fore.CYAN}{i18n.t('handlers', 'allow_execution')}{Style.RESET_ALL}").strip().lower()
|
|
15
|
+
return ans in ('y', 'yes', 'sim', 's')
|
|
16
|
+
except (KeyboardInterrupt, EOFError):
|
|
17
|
+
print(f"\n{i18n.t('handlers', 'denied_by_interruption')}")
|
|
18
|
+
return False
|
antgravity_cli/i18n.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
from typing import Dict, Any
|
|
4
|
+
|
|
5
|
+
_current_language = "en-us"
|
|
6
|
+
_translation_cache: Dict[str, Dict[str, str]] = {}
|
|
7
|
+
|
|
8
|
+
def set_language(lang: str) -> None:
|
|
9
|
+
"""Sets the active language for CLI output messages."""
|
|
10
|
+
global _current_language
|
|
11
|
+
if lang:
|
|
12
|
+
_current_language = lang.lower().strip()
|
|
13
|
+
|
|
14
|
+
def get_language() -> str:
|
|
15
|
+
"""Returns the currently active language code."""
|
|
16
|
+
return _current_language
|
|
17
|
+
|
|
18
|
+
def _load_translations(lang: str, module: str) -> Dict[str, str]:
|
|
19
|
+
"""Loads translations from translate/{lang}/{module}.json with caching."""
|
|
20
|
+
cache_key = f"{lang}/{module}"
|
|
21
|
+
if cache_key in _translation_cache:
|
|
22
|
+
return _translation_cache[cache_key]
|
|
23
|
+
|
|
24
|
+
# Resolve translations directory relative to this file, compatible with PyInstaller
|
|
25
|
+
from .utils import get_base_path
|
|
26
|
+
base_dir = get_base_path()
|
|
27
|
+
json_path = os.path.join(base_dir, "translate", lang, f"{module}.json")
|
|
28
|
+
|
|
29
|
+
translations = {}
|
|
30
|
+
if os.path.isfile(json_path):
|
|
31
|
+
try:
|
|
32
|
+
with open(json_path, "r", encoding="utf-8") as f:
|
|
33
|
+
translations = json.load(f)
|
|
34
|
+
except Exception:
|
|
35
|
+
# Fallback to empty dict on load errors
|
|
36
|
+
translations = {}
|
|
37
|
+
|
|
38
|
+
_translation_cache[cache_key] = translations
|
|
39
|
+
return translations
|
|
40
|
+
|
|
41
|
+
def t(module: str, key: str, **kwargs: Any) -> str:
|
|
42
|
+
"""
|
|
43
|
+
Returns the translated string for a given module and key.
|
|
44
|
+
Falls back to en-us if the key is missing in the current language,
|
|
45
|
+
and returns the key itself if not found anywhere.
|
|
46
|
+
"""
|
|
47
|
+
# 1. Try to get translation in active language
|
|
48
|
+
lang_translations = _load_translations(_current_language, module)
|
|
49
|
+
message = lang_translations.get(key)
|
|
50
|
+
|
|
51
|
+
# 2. Fall back to en-us if not found and current is not en-us
|
|
52
|
+
if message is None and _current_language != "en-us":
|
|
53
|
+
en_translations = _load_translations("en-us", module)
|
|
54
|
+
message = en_translations.get(key)
|
|
55
|
+
|
|
56
|
+
# 3. Fall back to the key itself if still not found
|
|
57
|
+
if message is None:
|
|
58
|
+
message = key
|
|
59
|
+
|
|
60
|
+
# 4. Format string if arguments are passed
|
|
61
|
+
if kwargs:
|
|
62
|
+
try:
|
|
63
|
+
return message.format(**kwargs)
|
|
64
|
+
except (KeyError, ValueError, IndexError) as e:
|
|
65
|
+
import warnings
|
|
66
|
+
warnings.warn(
|
|
67
|
+
f"Translation formatting failed for key '{module}.{key}' (lang: '{_current_language}'). "
|
|
68
|
+
f"Format arguments mismatch. Error: {e}",
|
|
69
|
+
UserWarning,
|
|
70
|
+
stacklevel=2
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
return message
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Tuple, List
|
|
3
|
+
|
|
4
|
+
class DirectiveProcessor(ABC):
|
|
5
|
+
"""Base interface for prompt directive processors (LSP/OCP)."""
|
|
6
|
+
@abstractmethod
|
|
7
|
+
def process(self, prompt: str, skills_paths: list[str] = None) -> Tuple[str, list[str]]:
|
|
8
|
+
"""
|
|
9
|
+
Processes the prompt and returns a tuple containing the updated prompt and a list
|
|
10
|
+
of strings containing the extra context to be injected.
|
|
11
|
+
"""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
class OutputWriter(ABC):
|
|
15
|
+
"""Base interface for agent communication outputs (DIP/ISP)."""
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def write_thought(self, text: str) -> None:
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def write_text(self, text: str) -> None:
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def write_tool_call(self, name: str, args: dict) -> None:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def write_tool_result(self, name: str, result: str, error: str = None) -> None:
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
def start_loading(self, message: str = "Thinking...") -> None:
|
|
33
|
+
"""Starts a visual processing indicator (optional)."""
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
def stop_loading(self) -> None:
|
|
37
|
+
"""Stops the visual processing indicator (optional)."""
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
class InputReader(ABC):
|
|
41
|
+
"""Base interface for user text input (DIP/ISP)."""
|
|
42
|
+
@abstractmethod
|
|
43
|
+
async def read_input(self, prompt_text: str, suggestions: list[str] = None) -> str:
|
|
44
|
+
pass
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from . import i18n
|
|
3
|
+
|
|
4
|
+
def discover_skills_in_paths(paths: list[str]) -> list[str]:
|
|
5
|
+
"""Scans the provided directories for subfolders containing a SKILL.md file and returns sorted skill names."""
|
|
6
|
+
skills = []
|
|
7
|
+
if not paths:
|
|
8
|
+
return []
|
|
9
|
+
for path in paths:
|
|
10
|
+
if path and os.path.exists(path) and os.path.isdir(path):
|
|
11
|
+
try:
|
|
12
|
+
for entry in os.listdir(path):
|
|
13
|
+
entry_path = os.path.join(path, entry)
|
|
14
|
+
if os.path.isdir(entry_path):
|
|
15
|
+
if os.path.exists(os.path.join(entry_path, "SKILL.md")):
|
|
16
|
+
skills.append(entry)
|
|
17
|
+
except Exception:
|
|
18
|
+
pass
|
|
19
|
+
return sorted(list(set(skills)))
|
|
20
|
+
|
|
21
|
+
def get_skills() -> list[str]:
|
|
22
|
+
"""Returns a sorted list of directory names inside .agents/skills."""
|
|
23
|
+
from .utils import get_base_path
|
|
24
|
+
base_dir = get_base_path()
|
|
25
|
+
skills_dir = os.path.join(base_dir, ".agents", "skills")
|
|
26
|
+
return discover_skills_in_paths([skills_dir])
|
|
27
|
+
|
|
28
|
+
def main() -> None:
|
|
29
|
+
"""Lists the names of the directories (skills) located in .agents/skills."""
|
|
30
|
+
# Resolve the active language from ANTGRAVITY_LANG env var if present
|
|
31
|
+
lang = os.environ.get("ANTGRAVITY_LANG", "en-us")
|
|
32
|
+
i18n.set_language(lang)
|
|
33
|
+
|
|
34
|
+
# Path to the local agent skills directory
|
|
35
|
+
from .utils import get_base_path
|
|
36
|
+
base_dir = get_base_path()
|
|
37
|
+
skills_dir = os.path.join(base_dir, ".agents", "skills")
|
|
38
|
+
|
|
39
|
+
if not os.path.isdir(skills_dir):
|
|
40
|
+
print(i18n.t("list_skills", "directory_not_found", directory=skills_dir))
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
skills = get_skills()
|
|
44
|
+
if not skills:
|
|
45
|
+
print(i18n.t("list_skills", "no_skills_found"))
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
print(i18n.t("list_skills", "skills_header"))
|
|
49
|
+
for skill in skills:
|
|
50
|
+
print(f" - {skill}")
|
|
51
|
+
|
|
52
|
+
if __name__ == "__main__":
|
|
53
|
+
main()
|
antgravity_cli/main.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import click
|
|
5
|
+
from dotenv import load_dotenv
|
|
6
|
+
from .utils import get_base_path
|
|
7
|
+
|
|
8
|
+
# Load API keys and configurations from .env files
|
|
9
|
+
# Check if a custom env file was specified via CLI arguments before processing options
|
|
10
|
+
custom_env_path = None
|
|
11
|
+
for i, arg in enumerate(sys.argv):
|
|
12
|
+
if arg in ("--env-file", "-e") and i + 1 < len(sys.argv):
|
|
13
|
+
custom_env_path = sys.argv[i + 1]
|
|
14
|
+
break
|
|
15
|
+
|
|
16
|
+
if custom_env_path:
|
|
17
|
+
# If a custom .env path is provided, we load only it and let it override existing variables
|
|
18
|
+
if os.path.exists(custom_env_path):
|
|
19
|
+
load_dotenv(custom_env_path, override=True)
|
|
20
|
+
else:
|
|
21
|
+
# 1. Load configuration from the script's installation directory .env (global defaults)
|
|
22
|
+
base_env = os.path.join(get_base_path(), ".env")
|
|
23
|
+
if os.path.exists(base_env):
|
|
24
|
+
load_dotenv(base_env)
|
|
25
|
+
|
|
26
|
+
# 2. Load configuration from the current working directory (CWD) .env (workspace-specific override)
|
|
27
|
+
cwd_env = os.path.abspath(".env")
|
|
28
|
+
if os.path.exists(cwd_env) and os.path.normcase(cwd_env) != os.path.normcase(os.path.abspath(base_env)):
|
|
29
|
+
load_dotenv(cwd_env, override=True)
|
|
30
|
+
|
|
31
|
+
from .runner import run_cli
|
|
32
|
+
|
|
33
|
+
@click.command()
|
|
34
|
+
@click.argument('prompt', required=False)
|
|
35
|
+
@click.option('--model', '-m', envvar='GEMINI_MODEL', default='gemini-3.1-flash-lite', help='Gemini model to be used.')
|
|
36
|
+
@click.option('--yolo', '-y', is_flag=True, envvar='ANTGRAVITY_YOLO', help='Bypass safety confirmations and execute all actions automatically.')
|
|
37
|
+
@click.option('--workspace', '-w', multiple=True, type=click.Path(exists=True, file_okay=False, dir_okay=True), envvar='ANTGRAVITY_WORKSPACE', help='Restrict file tools to these directories (default: current directory).')
|
|
38
|
+
@click.option('--system-instruction', '-s', envvar='ANTGRAVITY_SYSTEM_INSTRUCTION', help='System instructions text or path to a file with instructions.')
|
|
39
|
+
@click.option('--api-key', envvar='GEMINI_API_KEY', help='Gemini API key.')
|
|
40
|
+
@click.option('--skills-path', '-k', multiple=True, type=click.Path(exists=True, file_okay=False, dir_okay=True), envvar='ANTGRAVITY_SKILLS_PATH', help='Path to skills folders (can be repeated).')
|
|
41
|
+
@click.option('--silent', is_flag=True, envvar='ANTGRAVITY_SILENT', help='Hide thoughts and tool executions in the terminal.')
|
|
42
|
+
@click.option('--verbose', '-v', is_flag=True, envvar='ANTGRAVITY_VERBOSE', help='Display the agent\'s internal reasoning thoughts in the console.')
|
|
43
|
+
@click.option('--language', '-l', envvar='ANTGRAVITY_LANG', default='en-us', help='Output language (e.g. en-us, pt-br).')
|
|
44
|
+
@click.option('--env-file', '-e', type=click.Path(exists=True, file_okay=True, dir_okay=False), help='Path to a custom .env file to load configurations from.')
|
|
45
|
+
def main(prompt, model, yolo, workspace, system_instruction, api_key, skills_path, silent, verbose, language, env_file):
|
|
46
|
+
"""AntGravity CLI - Terminal-based interface for Google Antigravity agents."""
|
|
47
|
+
asyncio.run(run_cli(prompt, model, yolo, workspace, system_instruction, api_key, skills_path, silent=silent, verbose=verbose, language=language))
|
|
48
|
+
|
|
49
|
+
if __name__ == "__main__":
|
|
50
|
+
main()
|
|
51
|
+
|
|
52
|
+
|
antgravity_cli/parser.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
from typing import Tuple, List
|
|
4
|
+
from . import i18n
|
|
5
|
+
from .interfaces import DirectiveProcessor
|
|
6
|
+
|
|
7
|
+
class FileDirectiveProcessor(DirectiveProcessor):
|
|
8
|
+
"""File and folder directive processor with the '@' prefix (SRP/OCP/LSP)."""
|
|
9
|
+
|
|
10
|
+
def process(self, prompt: str, skills_paths: list[str] = None) -> Tuple[str, list[str]]:
|
|
11
|
+
extra_context = []
|
|
12
|
+
# Accepts @[path with spaces] or @path_without_spaces (avoids emails)
|
|
13
|
+
file_matches = re.finditer(r'(?<!\w)@(?:\[([^\]]+)\]|([^\s\x00-\x1F\x7F]+))', prompt)
|
|
14
|
+
files_to_inject = []
|
|
15
|
+
for match in file_matches:
|
|
16
|
+
path = match.group(1) or match.group(2)
|
|
17
|
+
# Clean common punctuation at the end of the path without brackets
|
|
18
|
+
if not match.group(1):
|
|
19
|
+
path = re.sub(r'[.,;:!?)]+$', '', path)
|
|
20
|
+
files_to_inject.append(path)
|
|
21
|
+
|
|
22
|
+
for filepath in files_to_inject:
|
|
23
|
+
if os.path.isfile(filepath):
|
|
24
|
+
try:
|
|
25
|
+
with open(filepath, "r", encoding="utf-8") as f:
|
|
26
|
+
content = f.read()
|
|
27
|
+
extra_context.append(f"{i18n.t('parser', 'file_content_header', filename=os.path.basename(filepath), filepath=filepath)}\n{content}\n")
|
|
28
|
+
except Exception as e:
|
|
29
|
+
extra_context.append(f"{i18n.t('parser', 'error_reading_file', filepath=filepath, error=str(e))}\n")
|
|
30
|
+
elif os.path.isdir(filepath):
|
|
31
|
+
try:
|
|
32
|
+
entries = os.listdir(filepath)
|
|
33
|
+
content = "\n".join(entries)
|
|
34
|
+
extra_context.append(f"{i18n.t('parser', 'directory_listing_header', filepath=filepath)}\n{content}\n")
|
|
35
|
+
except Exception as e:
|
|
36
|
+
extra_context.append(f"{i18n.t('parser', 'error_listing_directory', filepath=filepath, error=str(e))}\n")
|
|
37
|
+
|
|
38
|
+
return prompt, extra_context
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class SkillDirectiveProcessor(DirectiveProcessor):
|
|
42
|
+
"""Skill directive processor with the '/' prefix (SRP/OCP/LSP)."""
|
|
43
|
+
|
|
44
|
+
def process(self, prompt: str, skills_paths: list[str] = None) -> Tuple[str, list[str]]:
|
|
45
|
+
extra_context = []
|
|
46
|
+
# Ignores special REPL commands (/exit, /quit, /reset)
|
|
47
|
+
skill_matches = re.finditer(r'(?<!\w)/([a-zA-Z0-9_-]+)', prompt)
|
|
48
|
+
skills_to_inject = []
|
|
49
|
+
for match in skill_matches:
|
|
50
|
+
skill_name = match.group(1)
|
|
51
|
+
if skill_name in ("exit", "quit", "reset"):
|
|
52
|
+
continue
|
|
53
|
+
skills_to_inject.append(skill_name)
|
|
54
|
+
|
|
55
|
+
from .list_skills import discover_skills_in_paths
|
|
56
|
+
|
|
57
|
+
for skill_name in skills_to_inject:
|
|
58
|
+
paths_to_search = list(skills_paths) if skills_paths else []
|
|
59
|
+
if not paths_to_search:
|
|
60
|
+
from .utils import get_base_path
|
|
61
|
+
base_dir = get_base_path()
|
|
62
|
+
cli_skills_dir = os.path.join(base_dir, ".agents", "skills")
|
|
63
|
+
paths_to_search = ["skills", ".agents/skills", cli_skills_dir]
|
|
64
|
+
|
|
65
|
+
# Unified dry discovery check
|
|
66
|
+
if skill_name in discover_skills_in_paths(paths_to_search):
|
|
67
|
+
for base_path in paths_to_search:
|
|
68
|
+
skill_dir = os.path.join(base_path, skill_name)
|
|
69
|
+
skill_md_path = os.path.join(skill_dir, "SKILL.md")
|
|
70
|
+
if os.path.isfile(skill_md_path):
|
|
71
|
+
try:
|
|
72
|
+
with open(skill_md_path, "r", encoding="utf-8") as f:
|
|
73
|
+
content = f.read()
|
|
74
|
+
extra_context.append(f"{i18n.t('parser', 'skill_instructions_header', skill_name=skill_name)}\n{content}\n")
|
|
75
|
+
break
|
|
76
|
+
except Exception as e:
|
|
77
|
+
extra_context.append(f"{i18n.t('parser', 'error_loading_skill', skill_name=skill_name, error=str(e))}\n")
|
|
78
|
+
|
|
79
|
+
return prompt, extra_context
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class PromptPreprocessor:
|
|
83
|
+
"""Orchestrator for prompt preprocessing (SRP/OCP)."""
|
|
84
|
+
|
|
85
|
+
def __init__(self, processors: list[DirectiveProcessor] = None):
|
|
86
|
+
self._processors = processors if processors is not None else [
|
|
87
|
+
FileDirectiveProcessor(),
|
|
88
|
+
SkillDirectiveProcessor()
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
def preprocess(self, prompt: str, skills_paths: list[str] = None) -> str:
|
|
92
|
+
aggregated_context = []
|
|
93
|
+
current_prompt = prompt
|
|
94
|
+
for processor in self._processors:
|
|
95
|
+
current_prompt, extra_context = processor.process(current_prompt, skills_paths)
|
|
96
|
+
aggregated_context.extend(extra_context)
|
|
97
|
+
|
|
98
|
+
if aggregated_context:
|
|
99
|
+
return current_prompt + "\n\n" + "\n".join(aggregated_context)
|
|
100
|
+
return current_prompt
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# Functional compatibility wrapper
|
|
104
|
+
def preprocess_prompt(prompt: str, skills_paths: list[str] = None) -> str:
|
|
105
|
+
"""Functional wrapper compatible with previous versions."""
|
|
106
|
+
return PromptPreprocessor().preprocess(prompt, skills_paths)
|
antgravity_cli/repl.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
try:
|
|
3
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
4
|
+
except Exception:
|
|
5
|
+
pass
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import os
|
|
9
|
+
import click
|
|
10
|
+
from colorama import Fore, Style
|
|
11
|
+
from google.antigravity.types import Thought, Text, ToolCall, ToolResult
|
|
12
|
+
from . import i18n
|
|
13
|
+
from .interfaces import OutputWriter, InputReader
|
|
14
|
+
from .console_io import ConsoleOutputWriter, ConsoleInputReader
|
|
15
|
+
from .parser import preprocess_prompt
|
|
16
|
+
|
|
17
|
+
async def stream_chat_response(agent, prompt, writer: OutputWriter = None, silent=False, verbose=False):
|
|
18
|
+
"""Runs the chat and streams the response (thoughts, tools, and text) in real time."""
|
|
19
|
+
if writer is None:
|
|
20
|
+
writer = ConsoleOutputWriter()
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
writer.reset()
|
|
24
|
+
writer.start_loading()
|
|
25
|
+
response = await agent.chat(prompt)
|
|
26
|
+
|
|
27
|
+
async for chunk in response.chunks:
|
|
28
|
+
if isinstance(chunk, Thought):
|
|
29
|
+
if verbose and not silent:
|
|
30
|
+
writer.write_thought(chunk.text)
|
|
31
|
+
elif isinstance(chunk, Text):
|
|
32
|
+
writer.write_text(chunk.text)
|
|
33
|
+
elif isinstance(chunk, ToolCall):
|
|
34
|
+
if not silent:
|
|
35
|
+
writer.write_tool_call(chunk.name, chunk.args)
|
|
36
|
+
elif isinstance(chunk, ToolResult):
|
|
37
|
+
if not silent:
|
|
38
|
+
writer.write_tool_result(chunk.name, chunk.result, chunk.error)
|
|
39
|
+
|
|
40
|
+
writer.stop_loading()
|
|
41
|
+
writer.reset()
|
|
42
|
+
print() # Final line break for alignment
|
|
43
|
+
except Exception as e:
|
|
44
|
+
writer.stop_loading()
|
|
45
|
+
click.echo(i18n.t("repl", "error_during_conversation", error=str(e)), err=True)
|
|
46
|
+
|
|
47
|
+
def _get_repl_suggestions(skills_paths: list[str]) -> list[str]:
|
|
48
|
+
"""Scans registered skills folders and returns formatted skill commands and triggers."""
|
|
49
|
+
from .list_skills import discover_skills_in_paths
|
|
50
|
+
|
|
51
|
+
suggestions = ["/exit", "/quit", "/reset"]
|
|
52
|
+
|
|
53
|
+
# Resolve script's installation folder directory to load internal CLI skills
|
|
54
|
+
from .utils import get_base_path
|
|
55
|
+
base_dir = get_base_path()
|
|
56
|
+
cli_skills_dir = os.path.join(base_dir, ".agents", "skills")
|
|
57
|
+
|
|
58
|
+
paths_to_search = list(skills_paths) if skills_paths else []
|
|
59
|
+
if not paths_to_search:
|
|
60
|
+
paths_to_search = ["skills", ".agents/skills", cli_skills_dir]
|
|
61
|
+
|
|
62
|
+
discovered = discover_skills_in_paths(paths_to_search)
|
|
63
|
+
for s in discovered:
|
|
64
|
+
suggestions.append(f"/{s}")
|
|
65
|
+
|
|
66
|
+
return sorted(list(set(suggestions)))
|
|
67
|
+
|
|
68
|
+
async def run_repl(agent, resolved_skills, reader: InputReader = None, writer: OutputWriter = None, silent=False, verbose=False):
|
|
69
|
+
"""Runs the interactive terminal (REPL) conversing with the agent."""
|
|
70
|
+
if reader is None:
|
|
71
|
+
reader = ConsoleInputReader()
|
|
72
|
+
if writer is None:
|
|
73
|
+
writer = ConsoleOutputWriter()
|
|
74
|
+
|
|
75
|
+
suggestions = _get_repl_suggestions(resolved_skills)
|
|
76
|
+
|
|
77
|
+
# Render Option 1 colorized solid block ant art logo
|
|
78
|
+
click.echo("")
|
|
79
|
+
click.echo(f" {Fore.CYAN}▄▀▀▄ ▄▀▀▄{Style.RESET_ALL}")
|
|
80
|
+
click.echo(f" {Fore.CYAN}▀▄ ▀▄ ▄▀ ▄▀{Style.RESET_ALL}")
|
|
81
|
+
click.echo(f" {Fore.BLUE}▄█████████▄{Style.RESET_ALL}")
|
|
82
|
+
click.echo(f" {Fore.BLUE}██{Fore.GREEN}███{Fore.BLUE}███{Fore.GREEN}███{Fore.BLUE}██{Style.RESET_ALL}")
|
|
83
|
+
click.echo(f" {Fore.BLUE}█████████████{Style.RESET_ALL}")
|
|
84
|
+
click.echo(f" {Fore.BLUE}▀█████████▀{Style.RESET_ALL}")
|
|
85
|
+
click.echo(f" {Fore.CYAN}▄█▀ ▀█▄{Style.RESET_ALL}")
|
|
86
|
+
|
|
87
|
+
click.echo(f"\n{Fore.MAGENTA}{Style.BRIGHT}{i18n.t('repl', 'repl_title')}{Style.RESET_ALL}")
|
|
88
|
+
click.echo(f"{Fore.CYAN}{i18n.t('repl', 'special_commands_label')}{Style.RESET_ALL}")
|
|
89
|
+
click.echo(f" {Fore.GREEN}/exit{Style.RESET_ALL} or {Fore.GREEN}/quit{Style.RESET_ALL} - {i18n.t('repl', 'command_exit_desc')}")
|
|
90
|
+
click.echo(f" {Fore.GREEN}/reset{Style.RESET_ALL} - {i18n.t('repl', 'command_reset_desc')}")
|
|
91
|
+
|
|
92
|
+
# Display active skills limited to 5 with an "and more" suffix
|
|
93
|
+
skills = [s.lstrip("/") for s in suggestions if s not in ("/exit", "/quit", "/reset")]
|
|
94
|
+
if skills:
|
|
95
|
+
formatted_skills = [f" {Fore.GREEN}/{s}{Style.RESET_ALL}" for s in skills]
|
|
96
|
+
if len(formatted_skills) > 5:
|
|
97
|
+
for fs in formatted_skills[:5]:
|
|
98
|
+
click.echo(fs)
|
|
99
|
+
click.echo(f" ... {i18n.t('repl', 'and_more')}")
|
|
100
|
+
else:
|
|
101
|
+
for fs in formatted_skills:
|
|
102
|
+
click.echo(fs)
|
|
103
|
+
|
|
104
|
+
click.echo(f"{Fore.MAGENTA}{'-' * 40}{Style.RESET_ALL}")
|
|
105
|
+
|
|
106
|
+
while True:
|
|
107
|
+
try:
|
|
108
|
+
user_input = await reader.read_input(i18n.t("repl", "prompt_you"), suggestions=suggestions)
|
|
109
|
+
except (KeyboardInterrupt, EOFError):
|
|
110
|
+
click.echo(f"\n{Fore.YELLOW}{i18n.t('repl', 'exiting')}{Style.RESET_ALL}")
|
|
111
|
+
break
|
|
112
|
+
|
|
113
|
+
if not user_input:
|
|
114
|
+
continue
|
|
115
|
+
|
|
116
|
+
if user_input in ("/exit", "/quit"):
|
|
117
|
+
click.echo(f"{Fore.YELLOW}{i18n.t('repl', 'exiting')}{Style.RESET_ALL}")
|
|
118
|
+
break
|
|
119
|
+
|
|
120
|
+
if user_input == "/reset":
|
|
121
|
+
agent.conversation.clear_history()
|
|
122
|
+
click.echo(i18n.t("repl", "conversation_history_cleared"))
|
|
123
|
+
continue
|
|
124
|
+
|
|
125
|
+
processed_input = preprocess_prompt(user_input, resolved_skills)
|
|
126
|
+
await stream_chat_response(agent, processed_input, writer, silent=silent, verbose=verbose)
|
antgravity_cli/runner.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from colorama import Fore, Style
|
|
3
|
+
from google.antigravity import Agent
|
|
4
|
+
|
|
5
|
+
from . import i18n
|
|
6
|
+
from .config import setup_agent_config
|
|
7
|
+
from .repl import run_repl, stream_chat_response
|
|
8
|
+
from .parser import preprocess_prompt
|
|
9
|
+
from .console_io import ConsoleOutputWriter, ConsoleInputReader
|
|
10
|
+
|
|
11
|
+
async def run_cli(prompt, model, yolo, workspace, system_instruction, api_key, skills_path, silent=False, verbose=False, language="en-us"):
|
|
12
|
+
"""Initializes the agent configurations and starts either a single execution or the REPL."""
|
|
13
|
+
i18n.set_language(language)
|
|
14
|
+
try:
|
|
15
|
+
config = setup_agent_config(model, yolo, workspace, system_instruction, api_key, skills_path)
|
|
16
|
+
except (ValueError, IOError) as e:
|
|
17
|
+
click.echo(f"{Fore.RED}{e}{Style.RESET_ALL}", err=True)
|
|
18
|
+
return
|
|
19
|
+
|
|
20
|
+
writer = ConsoleOutputWriter()
|
|
21
|
+
reader = ConsoleInputReader()
|
|
22
|
+
|
|
23
|
+
if prompt:
|
|
24
|
+
# Single Prompt
|
|
25
|
+
async with Agent(config) as agent:
|
|
26
|
+
processed_prompt = preprocess_prompt(prompt, config.skills_paths)
|
|
27
|
+
await stream_chat_response(agent, processed_prompt, writer, silent=silent, verbose=verbose)
|
|
28
|
+
else:
|
|
29
|
+
# Interactive Mode (REPL)
|
|
30
|
+
async with Agent(config) as agent:
|
|
31
|
+
await run_repl(agent, config.skills_paths, reader, writer, silent=silent, verbose=verbose)
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{
|
|
2
|
+
"api_key_not_found": "Error: Gemini API key not found.\nSet the GEMINI_API_KEY environment variable or use the --api-key flag.",
|
|
3
|
+
"yolo_warning": "[WARNING] Running in YOLO mode. All tools will be allowed without confirmation.",
|
|
4
|
+
"error_reading_instructions": "Error reading system instructions file: {error}"
|
|
5
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"file_content_header": "=== FILE CONTENT: {filename} ({filepath}) ===",
|
|
3
|
+
"error_reading_file": "[Error reading file {filepath}: {error}]",
|
|
4
|
+
"directory_listing_header": "=== DIRECTORY LISTING: {filepath} ===",
|
|
5
|
+
"error_listing_directory": "[Error listing directory {filepath}: {error}]",
|
|
6
|
+
"skill_instructions_header": "=== SKILL INSTRUCTIONS: {skill_name} ===",
|
|
7
|
+
"error_loading_skill": "[Error loading skill {skill_name}: {error}]"
|
|
8
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"repl_title": "=== AntGravity CLI (Interactive Mode) ===",
|
|
3
|
+
"special_commands_label": "Type your messages. Special commands:",
|
|
4
|
+
"command_exit_desc": "Exit the CLI",
|
|
5
|
+
"command_reset_desc": "Reset conversation history",
|
|
6
|
+
"prompt_you": "You > ",
|
|
7
|
+
"exiting": "Exiting...",
|
|
8
|
+
"conversation_history_cleared": "Conversation history cleared!",
|
|
9
|
+
"error_during_conversation": "Error during conversation: {error}",
|
|
10
|
+
"and_more": "and more",
|
|
11
|
+
"skills_label": "Skills"
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{
|
|
2
|
+
"api_key_not_found": "Erro: A chave de API do Gemini não foi encontrada.\nDefina a variável de ambiente GEMINI_API_KEY ou use a flag --api-key.",
|
|
3
|
+
"yolo_warning": "[AVISO] Executando em modo YOLO. Todas as ferramentas serão permitidas sem confirmação.",
|
|
4
|
+
"error_reading_instructions": "Erro ao ler arquivo de instruções: {error}"
|
|
5
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"file_content_header": "=== CONTEÚDO DO ARQUIVO: {filename} ({filepath}) ===",
|
|
3
|
+
"error_reading_file": "[Erro ao ler o arquivo {filepath}: {error}]",
|
|
4
|
+
"directory_listing_header": "=== LISTAGEM DO DIRETÓRIO: {filepath} ===",
|
|
5
|
+
"error_listing_directory": "[Erro ao listar diretório {filepath}: {error}]",
|
|
6
|
+
"skill_instructions_header": "=== INSTRUÇÕES DA SKILL: {skill_name} ===",
|
|
7
|
+
"error_loading_skill": "[Erro ao carregar a skill {skill_name}: {error}]"
|
|
8
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"repl_title": "=== AntGravity CLI (Modo Interativo) ===",
|
|
3
|
+
"special_commands_label": "Digite suas mensagens. Comandos especiais:",
|
|
4
|
+
"command_exit_desc": "Sair do CLI",
|
|
5
|
+
"command_reset_desc": "Reiniciar o histórico da conversa",
|
|
6
|
+
"prompt_you": "Você > ",
|
|
7
|
+
"exiting": "Saindo...",
|
|
8
|
+
"conversation_history_cleared": "Histórico da conversa limpo!",
|
|
9
|
+
"error_during_conversation": "Erro durante a conversação: {error}",
|
|
10
|
+
"and_more": "e mais",
|
|
11
|
+
"skills_label": "Skills"
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
antgravity_cli/utils.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
def get_base_path() -> str:
|
|
5
|
+
"""Returns the base path of the project, compatible with PyInstaller standalone executable format."""
|
|
6
|
+
if getattr(sys, 'frozen', False):
|
|
7
|
+
# PyInstaller temp folder where execution assets are unpacked
|
|
8
|
+
return sys._MEIPASS
|
|
9
|
+
# Normal execution, resolve folder relative to utils.py location
|
|
10
|
+
return os.path.dirname(os.path.abspath(__file__))
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: AntGravityCLI
|
|
3
|
+
Version: 1.0.2
|
|
4
|
+
Summary: A portable and interactive command-line interface (CLI) for the Google Antigravity ecosystem.
|
|
5
|
+
Author-email: Henrique Fazolo <henriquefazolo@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/henriquefazolo/AntGravityCLI
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.11
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
Requires-Dist: click>=8.4.2
|
|
15
|
+
Requires-Dist: colorama>=0.4.6
|
|
16
|
+
Requires-Dist: python-dotenv>=1.2.2
|
|
17
|
+
Requires-Dist: google-antigravity>=0.1.5
|
|
18
|
+
Requires-Dist: rich>=13.7.1
|
|
19
|
+
Requires-Dist: prompt-toolkit>=3.0.43
|
|
20
|
+
|
|
21
|
+
# AntGravity CLI
|
|
22
|
+
|
|
23
|
+
<p align="center">
|
|
24
|
+
<img src=".img/ant%20gravity.png" alt="AntGravity CLI Logo" width="400">
|
|
25
|
+
</p>
|
|
26
|
+
|
|
27
|
+
A portable and interactive command-line interface (CLI) developed in Python to flexibly interact with agents from the **Google Antigravity** ecosystem.
|
|
28
|
+
|
|
29
|
+
## Requirements
|
|
30
|
+
|
|
31
|
+
- Python 3.11.7 (as configured in the development environment).
|
|
32
|
+
- Installed dependencies (including `google-antigravity`, `click`, `colorama`, `python-dotenv`).
|
|
33
|
+
|
|
34
|
+
## Installation and Setup
|
|
35
|
+
|
|
36
|
+
1. Make sure the virtual environment is activated:
|
|
37
|
+
```powershell
|
|
38
|
+
.\.venv\Scripts\activate
|
|
39
|
+
```
|
|
40
|
+
2. Configure your environment using a `.env` file. You can copy the template from [.env.example](file:///.env.example) to get started:
|
|
41
|
+
```powershell
|
|
42
|
+
cp .env.example .env
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Environment Resolution and Default Values
|
|
46
|
+
|
|
47
|
+
The CLI resolves configuration by loading `.env` files. By default, it loads them in two stages:
|
|
48
|
+
1. **Physical CLI script installation folder**: Loads `.env` for global default configuration.
|
|
49
|
+
2. **Current Working Directory (CWD)**: Loads `.env` for workspace-specific configurations (which override the global ones).
|
|
50
|
+
|
|
51
|
+
Alternatively, you can load a custom `.env` file using the `--env-file` / `-e` option. When a custom `.env` file is specified, the default `.env` files (global and CWD) are **not** loaded automatically, giving you full control over the variables.
|
|
52
|
+
|
|
53
|
+
If a `.env` file is not present or a specific variable is not set, the CLI falls back to the following default values:
|
|
54
|
+
|
|
55
|
+
| Environment Variable | CLI Option | Description | Default Value |
|
|
56
|
+
| :--- | :--- | :--- | :--- |
|
|
57
|
+
| `GEMINI_API_KEY` | `--api-key` | Gemini API Key | *None (Required)* |
|
|
58
|
+
| `GEMINI_MODEL` | `--model` / `-m` | Gemini Model to be used | `gemini-3.1-flash-lite` |
|
|
59
|
+
| `ANTGRAVITY_LANG` | `--language` / `-l` | CLI UI Language | `en-us` |
|
|
60
|
+
| `ANTGRAVITY_YOLO` | `--yolo` / `-y` | Run in YOLO (Safe-bypass) Mode | `False` (Safe Mode) |
|
|
61
|
+
| `ANTGRAVITY_WORKSPACE` | `--workspace` / `-w` | Restricted workspace paths | *None (Current CWD)* |
|
|
62
|
+
| `ANTGRAVITY_SYSTEM_INSTRUCTION` | `--system-instruction` / `-s` | System instruction or path | *None* |
|
|
63
|
+
| `ANTGRAVITY_SKILLS_PATH` | `--skills-path` / `-k` | Custom workspace skills folders | *None* |
|
|
64
|
+
| `ANTGRAVITY_SILENT` | `--silent` | Hide thoughts and tool execution logs | `False` |
|
|
65
|
+
| `ANTGRAVITY_VERBOSE` | `--verbose` / `-v` | Show internal reasoning details | `False` |
|
|
66
|
+
|
|
67
|
+
*(You can always override these values dynamically by passing their respective flags on the command line).*
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## How to Use
|
|
73
|
+
|
|
74
|
+
### 1. Single Prompt Mode
|
|
75
|
+
To send a prompt and receive the response directly:
|
|
76
|
+
```powershell
|
|
77
|
+
python main.py "Write a greeting in Python"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 2. Interactive Mode (REPL)
|
|
81
|
+
To start a continuous chat with the agent (which maintains session history):
|
|
82
|
+
```powershell
|
|
83
|
+
python main.py
|
|
84
|
+
```
|
|
85
|
+
**Useful commands in Interactive Mode:**
|
|
86
|
+
- `/reset`: Clears conversation history and resets agent context.
|
|
87
|
+
- `/exit` or `/quit`: Closes the interactive terminal.
|
|
88
|
+
|
|
89
|
+
**Auto-completion and Active Skills:**
|
|
90
|
+
* **Real-time Auto-completion**: As you type `/` in the prompt (either at the start or mid-sentence), a dynamic dropdown menu displaying all special commands and active skills will instantly pop up. The auto-completion is case-insensitive, stays open stably when you delete characters (Backspace), and ignores regular chat text and trailing spaces to maintain a clean console.
|
|
91
|
+
* **Banner Skills Listing**: At REPL startup, a welcome banner displays all available active workspace skills one per line, limited to 5. If more than 5 exist, a localized ellipsis (`... and more` or `... e mais`) is appended.
|
|
92
|
+
* **Hybrid Skills Resolution**: By default (when `--skills-path` / `-k` is not specified), the welcome banner list, autocomplete menu, and prompt parser strictly load active skills from the CLI script's physical installation directory (`.agents/skills`), even when executing the CLI from or targeting a different CWD/workspace. This ensures that internal CLI utility skills (like `/gerar_skill_template` and `/gerenciar_deploy`) are always loaded and executable. If you explicitly pass one or more custom paths via the `--skills-path` / `-k` flag, the CLI dynamically loads **both** your custom/local workspace skills and the physical Ant installation skills simultaneously, giving you access to both sets of tools in the REPL session.
|
|
93
|
+
|
|
94
|
+
### 3. Safe Mode vs YOLO Mode
|
|
95
|
+
- **Safe Mode (Default)**: Whenever the agent tries to run risky terminal commands (like the `RUN_COMMAND` tool), the CLI will request your permission in the console (`[y/N]`) before proceeding.
|
|
96
|
+
- **YOLO Mode (`-y` / `--yolo`)**: Disables all safety confirmations and executes all actions autonomously.
|
|
97
|
+
```powershell
|
|
98
|
+
python main.py -y "Create a folder named test and list the directory"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### 4. Listing Available Skills
|
|
102
|
+
You can quickly scan and list the names of all active local agent skills folders registered in `.agents/skills/` using the utility script:
|
|
103
|
+
```powershell
|
|
104
|
+
python list_skills.py
|
|
105
|
+
```
|
|
106
|
+
*(This script supports localization, reading the system language from the `ANTGRAVITY_LANG` environment variable).*
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Available Options
|
|
111
|
+
|
|
112
|
+
You can customize the CLI behavior using flags:
|
|
113
|
+
|
|
114
|
+
| Flag | Shortcut | Env Variable | Description |
|
|
115
|
+
| :--- | :--- | :--- | :--- |
|
|
116
|
+
| `--model` | `-m` | `GEMINI_MODEL` | Specifies the Gemini model (default: `gemini-3.1-flash-lite`). |
|
|
117
|
+
| `--yolo` | `-y` | `ANTGRAVITY_YOLO` | Skips all permissions/confirmations and runs everything freely. |
|
|
118
|
+
| `--workspace` | `-w` | `ANTGRAVITY_WORKSPACE` | Restricts file tools to a specific directory (can be repeated). |
|
|
119
|
+
| `--system-instruction` | `-s` | `ANTGRAVITY_SYSTEM_INSTRUCTION` | Text with custom system instructions or path to a text file. |
|
|
120
|
+
| `--api-key` | | `GEMINI_API_KEY` | Passes the API key directly on the command line. |
|
|
121
|
+
| `--skills-path` | `-k` | `ANTGRAVITY_SKILLS_PATH` | Path to skills folders (can be repeated). If not provided and the `./skills` folder exists, it will be loaded by default. |
|
|
122
|
+
| `--silent` | | `ANTGRAVITY_SILENT` | Hides internal thoughts and tool calls in the terminal. |
|
|
123
|
+
| `--verbose` | `-v` | `ANTGRAVITY_VERBOSE` | Displays the AI's internal reasoning thoughts (Chain of Thought) in gray on the console. |
|
|
124
|
+
| `--language` | `-l` | `ANTGRAVITY_LANG` | Specifies the output translation language (default: `en-us`, e.g. `pt-br`, `en-us`). |
|
|
125
|
+
| `--env-file` | `-e` | | Path to a custom `.env` file to load configurations from (bypasses default global and local .env files). |
|
|
126
|
+
|
|
127
|
+
Advanced usage example:
|
|
128
|
+
```powershell
|
|
129
|
+
python main.py -m gemini-3.5-flash -y -s "You are a concise assistant speaking Spanish" "Hello!"
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Localization (i18n)
|
|
135
|
+
|
|
136
|
+
The AntGravity CLI features built-in internationalization (i18n). Output messages, error states, and terminal prompts are decoupled from the core source code and stored as localized translation files inside the `translate/` directory.
|
|
137
|
+
|
|
138
|
+
### Structure of `translate/`
|
|
139
|
+
```text
|
|
140
|
+
translate/
|
|
141
|
+
├── pt-br/ # Brazilian Portuguese translations
|
|
142
|
+
│ ├── config.json
|
|
143
|
+
│ ├── console_io.json
|
|
144
|
+
│ ├── handlers.json
|
|
145
|
+
│ ├── parser.json
|
|
146
|
+
│ └── repl.json
|
|
147
|
+
└── en-us/ # English translations (Default)
|
|
148
|
+
├── config.json
|
|
149
|
+
├── console_io.json
|
|
150
|
+
├── handlers.json
|
|
151
|
+
├── parser.json
|
|
152
|
+
└── repl.json
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Adding New Languages
|
|
156
|
+
To support a new language (e.g. `es-es` for Spanish):
|
|
157
|
+
1. Create a new directory named after the locale code inside `translate/` (e.g. `translate/es-es/`).
|
|
158
|
+
2. Copy the JSON files from `translate/en-us/` into the new folder and translate their text values.
|
|
159
|
+
3. Switch the CLI runtime language using the `--language es-es` flag.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Customization and Settings (`.agents`)
|
|
164
|
+
|
|
165
|
+
The `.agents` folder at the root of your project is the **Local Workspace Customization** folder. Through it, you can define rules, create new skills, and customize agent responses.
|
|
166
|
+
|
|
167
|
+
### Complete Structure of `.agents`
|
|
168
|
+
|
|
169
|
+
```text
|
|
170
|
+
my-project/
|
|
171
|
+
└── .agents/
|
|
172
|
+
├── AGENTS.md # Project rules and guidelines
|
|
173
|
+
├── skills.json # (Optional) Skills configuration and registration
|
|
174
|
+
└── skills/ # Folder containing specific skills
|
|
175
|
+
└── gerenciar_deploy/ # Example of a skill
|
|
176
|
+
├── SKILL.md # Skill instructions and triggers (Required)
|
|
177
|
+
├── scripts/ # Supporting scripts
|
|
178
|
+
├── examples/ # Usage/code examples
|
|
179
|
+
└── references/ # Reference documentation
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### 1. Project Rules (`AGENTS.md`)
|
|
183
|
+
The `.agents/AGENTS.md` file defines the general rules of behavior, style, and technical constraints that the agent must follow in this project (e.g., code pattern, language, preferred frameworks).
|
|
184
|
+
|
|
185
|
+
*There is also the global scope at `C:\Users\<user>\.gemini\config\AGENTS.md` for rules that apply to the entire machine.*
|
|
186
|
+
|
|
187
|
+
### 2. Skills (`skills/`)
|
|
188
|
+
**Skills** are packages of behavior and tools dynamically loaded depending on conversation context or user request.
|
|
189
|
+
|
|
190
|
+
* **The `SKILL.md` file (Required)**:
|
|
191
|
+
Defines YAML metadata (used by the AI for auto-activation) and the body with the skill instructions:
|
|
192
|
+
```markdown
|
|
193
|
+
---
|
|
194
|
+
name: "Utility Development Tools"
|
|
195
|
+
description: "Useful for any general development, automation, scripting, or workspace query task."
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
# Skill Instructions
|
|
199
|
+
Whenever the user requests an automation or script execution:
|
|
200
|
+
1. Analyze the prompt and use the corresponding tools in `scripts/`.
|
|
201
|
+
2. Explain the execution result at the end.
|
|
202
|
+
```
|
|
203
|
+
* **Scripts (`scripts/`)**:
|
|
204
|
+
Any executable script inserted in the `scripts/` folder will be automatically exposed as a tool that the agent can run (e.g., `gerenciar_deploy.script_name`).
|
|
205
|
+
|
|
206
|
+
### 3. Skills Registration (`skills.json`)
|
|
207
|
+
The `skills.json` file at the root of the `.agents/` folder allows inheriting skills from other shared directories or disabling default skills:
|
|
208
|
+
```json
|
|
209
|
+
{
|
|
210
|
+
"entries": [
|
|
211
|
+
{ "path": "path/to/external/skills" }
|
|
212
|
+
],
|
|
213
|
+
"exclude": [
|
|
214
|
+
"skill_name_to_ignore"
|
|
215
|
+
]
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Agent Personality and Instructions
|
|
222
|
+
|
|
223
|
+
The agent's personality, tone of voice, and behavioral guidelines are resolved at multiple levels of precedence:
|
|
224
|
+
|
|
225
|
+
1. **Initialization Flag (`-s` / `--system-instruction`)**:
|
|
226
|
+
Defines system instructions directly in command execution (free text or file path).
|
|
227
|
+
2. **Local and Global Rules (`AGENTS.md`)**:
|
|
228
|
+
Read from `.agents/AGENTS.md` (local) and `C:\Users\<user>\.gemini\config\AGENTS.md` (global).
|
|
229
|
+
3. **Skill Instructions (`SKILL.md`)**:
|
|
230
|
+
Merged into the chat context when the corresponding skill is activated.
|
|
231
|
+
4. **Default Personality (Fallback)**:
|
|
232
|
+
If no instruction is provided, assumes the default profile of the Google Antigravity ecosystem.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
antgravity_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
antgravity_cli/config.py,sha256=OFoDKgC8QAN-x9VFuc50w00u_teKEumxPtvowqs66Nw,2635
|
|
3
|
+
antgravity_cli/console_io.py,sha256=-MBiKfMAQqt3FlBu5XPgB7jR0gukUrV0P3i0ZoZnOgw,6692
|
|
4
|
+
antgravity_cli/handlers.py,sha256=nTUxxXqeOrnx2oKnAugo2sHAYcl9qJO4P5ZPM7IyMOY,759
|
|
5
|
+
antgravity_cli/i18n.py,sha256=DOuWah-phrj3cNbAFpWX7KzHoe5vDA_BY9myhqP03qo,2538
|
|
6
|
+
antgravity_cli/interfaces.py,sha256=mInXwjWvCVbaAoB0dlulQ9f9oaz8H3oav1rN14_4EI4,1399
|
|
7
|
+
antgravity_cli/list_skills.py,sha256=n9EAGmbokBlwz5ItA3lJXOquxqpKm3AeQlbtQPKAwlw,1856
|
|
8
|
+
antgravity_cli/main.py,sha256=tb_d7E2ZhISyjSevo8D_AjG_wCp0tmIt-VazLI2qfZ4,3103
|
|
9
|
+
antgravity_cli/parser.py,sha256=d4z52Kk9N9Il99u4BF_Q2TIDbho-5XvF2nO4YO4cFs0,4978
|
|
10
|
+
antgravity_cli/repl.py,sha256=f-6l1HWxU3B0Lv8OqH_mZ0jTDvBq6v9z7EO0LSaqhUo,5411
|
|
11
|
+
antgravity_cli/runner.py,sha256=fuZolo8vVDj1SxLeKhXboGMSyi1hebIUvjbBc40Hj6I,1317
|
|
12
|
+
antgravity_cli/utils.py,sha256=OsCOV6Jiqv9r--r1VjL5I-zCshOSkU-oK8NBLYndVCM,415
|
|
13
|
+
antgravity_cli/translate/en-us/config.json,sha256=2aBF4VFJ-42yBBDC_cKHCeiYeuvm6ZtPr3htif_yLcQ,318
|
|
14
|
+
antgravity_cli/translate/en-us/console_io.json,sha256=8s8SSd-xR-0gtbvR4lm4mJ69pF8mwFV-iGpQpdJmK4M,192
|
|
15
|
+
antgravity_cli/translate/en-us/handlers.json,sha256=7YZ87aMaLAi0tEDQUxrgAVu9pZr7Dmomi35N0TvyGv0,234
|
|
16
|
+
antgravity_cli/translate/en-us/interfaces.json,sha256=yj0WO6sFU4GCciYUBWjzvvfqrBh869doeOC2Pp5EI1Y,3
|
|
17
|
+
antgravity_cli/translate/en-us/list_skills.json,sha256=I4JaubRVcb4DZP3o5No6DD_I0ETY0-gfNkQqIXw8t8A,181
|
|
18
|
+
antgravity_cli/translate/en-us/main.json,sha256=yj0WO6sFU4GCciYUBWjzvvfqrBh869doeOC2Pp5EI1Y,3
|
|
19
|
+
antgravity_cli/translate/en-us/parser.json,sha256=Lo4Ytm1-xuwXiDyO4FAh0fovf-0thHBZgKKpMCaptmA,441
|
|
20
|
+
antgravity_cli/translate/en-us/repl.json,sha256=0qw-nz5sAxo--a7EHXeUf2gk_uLwfeXnHyQp8VglCLE,469
|
|
21
|
+
antgravity_cli/translate/en-us/runner.json,sha256=yj0WO6sFU4GCciYUBWjzvvfqrBh869doeOC2Pp5EI1Y,3
|
|
22
|
+
antgravity_cli/translate/en-us/test_cli.json,sha256=yj0WO6sFU4GCciYUBWjzvvfqrBh869doeOC2Pp5EI1Y,3
|
|
23
|
+
antgravity_cli/translate/pt-br/config.json,sha256=01WnE9g5XkoV_0wWY7yn33cIsAH4yYXMYqqRv5P34F8,345
|
|
24
|
+
antgravity_cli/translate/pt-br/console_io.json,sha256=3fQrzAwNHZz1hBSUwulIwfrBC_eKKyJj34eSzFQtu_8,210
|
|
25
|
+
antgravity_cli/translate/pt-br/handlers.json,sha256=r1B6VmZgCHyh6nvw5LxBFgnfV0Rkq_PhBely2_9a0pw,243
|
|
26
|
+
antgravity_cli/translate/pt-br/interfaces.json,sha256=yj0WO6sFU4GCciYUBWjzvvfqrBh869doeOC2Pp5EI1Y,3
|
|
27
|
+
antgravity_cli/translate/pt-br/list_skills.json,sha256=LVBQCfj0yAYxDnCJCLUicoczxwEsztB7XRThO8ZsmRc,200
|
|
28
|
+
antgravity_cli/translate/pt-br/main.json,sha256=yj0WO6sFU4GCciYUBWjzvvfqrBh869doeOC2Pp5EI1Y,3
|
|
29
|
+
antgravity_cli/translate/pt-br/parser.json,sha256=zZn5NzwifjF8LW3e-pSzk5JvkfVmBWRE3Wm4KJFFHQQ,467
|
|
30
|
+
antgravity_cli/translate/pt-br/repl.json,sha256=bp0uymet8QM8rVdJJvvXAXh8N-vhdY0IikXxp3-psII,482
|
|
31
|
+
antgravity_cli/translate/pt-br/runner.json,sha256=yj0WO6sFU4GCciYUBWjzvvfqrBh869doeOC2Pp5EI1Y,3
|
|
32
|
+
antgravity_cli/translate/pt-br/test_cli.json,sha256=yj0WO6sFU4GCciYUBWjzvvfqrBh869doeOC2Pp5EI1Y,3
|
|
33
|
+
antgravitycli-1.0.2.dist-info/METADATA,sha256=elU7qQnC9MDqJ_h1i3rFLEXS2RmfI2DOtQ6mbF10cmY,11638
|
|
34
|
+
antgravitycli-1.0.2.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
35
|
+
antgravitycli-1.0.2.dist-info/entry_points.txt,sha256=XP9NCKyiDhNKC_riEIxtFoNJhtN14PZNRTuYIdplJmc,56
|
|
36
|
+
antgravitycli-1.0.2.dist-info/top_level.txt,sha256=VPnvmi_6wOwpIs5yjQb7id4AKM3HhExHtIv9xPN82YM,15
|
|
37
|
+
antgravitycli-1.0.2.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
antgravity_cli
|