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.
Files changed (37) hide show
  1. antgravity_cli/__init__.py +0 -0
  2. antgravity_cli/config.py +68 -0
  3. antgravity_cli/console_io.py +167 -0
  4. antgravity_cli/handlers.py +18 -0
  5. antgravity_cli/i18n.py +73 -0
  6. antgravity_cli/interfaces.py +44 -0
  7. antgravity_cli/list_skills.py +53 -0
  8. antgravity_cli/main.py +52 -0
  9. antgravity_cli/parser.py +106 -0
  10. antgravity_cli/repl.py +126 -0
  11. antgravity_cli/runner.py +31 -0
  12. antgravity_cli/translate/en-us/config.json +5 -0
  13. antgravity_cli/translate/en-us/console_io.json +6 -0
  14. antgravity_cli/translate/en-us/handlers.json +6 -0
  15. antgravity_cli/translate/en-us/interfaces.json +1 -0
  16. antgravity_cli/translate/en-us/list_skills.json +5 -0
  17. antgravity_cli/translate/en-us/main.json +1 -0
  18. antgravity_cli/translate/en-us/parser.json +8 -0
  19. antgravity_cli/translate/en-us/repl.json +12 -0
  20. antgravity_cli/translate/en-us/runner.json +1 -0
  21. antgravity_cli/translate/en-us/test_cli.json +1 -0
  22. antgravity_cli/translate/pt-br/config.json +5 -0
  23. antgravity_cli/translate/pt-br/console_io.json +6 -0
  24. antgravity_cli/translate/pt-br/handlers.json +6 -0
  25. antgravity_cli/translate/pt-br/interfaces.json +1 -0
  26. antgravity_cli/translate/pt-br/list_skills.json +5 -0
  27. antgravity_cli/translate/pt-br/main.json +1 -0
  28. antgravity_cli/translate/pt-br/parser.json +8 -0
  29. antgravity_cli/translate/pt-br/repl.json +12 -0
  30. antgravity_cli/translate/pt-br/runner.json +1 -0
  31. antgravity_cli/translate/pt-br/test_cli.json +1 -0
  32. antgravity_cli/utils.py +10 -0
  33. antgravitycli-1.0.2.dist-info/METADATA +232 -0
  34. antgravitycli-1.0.2.dist-info/RECORD +37 -0
  35. antgravitycli-1.0.2.dist-info/WHEEL +5 -0
  36. antgravitycli-1.0.2.dist-info/entry_points.txt +2 -0
  37. antgravitycli-1.0.2.dist-info/top_level.txt +1 -0
File without changes
@@ -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
+
@@ -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)
@@ -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,6 @@
1
+ {
2
+ "thinking": "Thinking",
3
+ "tool_calling": "[Tool] Calling: {name} with {args}",
4
+ "tool_error": "[Tool] Error in {name}: {error}",
5
+ "tool_result": "[Tool] Result from {name}: {result}"
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "agent_wants_to_execute": "[?] The agent wants to execute the tool: {name}",
3
+ "arguments": " Arguments: {args}",
4
+ "allow_execution": " Allow execution? [y/N]: ",
5
+ "denied_by_interruption": " Denied by interruption."
6
+ }
@@ -0,0 +1 @@
1
+ {}
@@ -0,0 +1,5 @@
1
+ {
2
+ "skills_header": "Available Skills in .agents/skills:",
3
+ "no_skills_found": "No skills found in .agents/skills.",
4
+ "directory_not_found": "Directory not found: {directory}"
5
+ }
@@ -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,6 @@
1
+ {
2
+ "thinking": "Pensando",
3
+ "tool_calling": "[Ferramenta] Chamando: {name} com {args}",
4
+ "tool_error": "[Ferramenta] Erro em {name}: {error}",
5
+ "tool_result": "[Ferramenta] Resultado de {name}: {result}"
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "agent_wants_to_execute": "[?] O agente deseja executar a ferramenta: {name}",
3
+ "arguments": " Argumentos: {args}",
4
+ "allow_execution": " Permitir execução? [y/N]: ",
5
+ "denied_by_interruption": " Negado por interrupção."
6
+ }
@@ -0,0 +1 @@
1
+ {}
@@ -0,0 +1,5 @@
1
+ {
2
+ "skills_header": "Skills disponíveis em .agents/skills:",
3
+ "no_skills_found": "Nenhuma skill encontrada em .agents/skills.",
4
+ "directory_not_found": "Diretório não encontrado: {directory}"
5
+ }
@@ -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
+ {}
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ antgravity = antgravity_cli.main:main
@@ -0,0 +1 @@
1
+ antgravity_cli