omni-ai-cli 0.0.0-stage → 1.0.0

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 (177) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +615 -2
  3. package/bin/ai.js +84 -0
  4. package/docs/README.en.md +615 -0
  5. package/docs/README.es.md +618 -0
  6. package/docs/README.fr.md +630 -0
  7. package/omni_ai/__init__.py +1 -0
  8. package/omni_ai/__main__.py +6 -0
  9. package/omni_ai/chat.py +1458 -0
  10. package/omni_ai/cli.py +991 -0
  11. package/omni_ai/config.py +531 -0
  12. package/omni_ai/deepseek.py +57 -0
  13. package/omni_ai/grok.py +111 -0
  14. package/omni_ai/onboarding.py +196 -0
  15. package/omni_ai/openrouter.py +335 -0
  16. package/omni_ai/osutils.py +86 -0
  17. package/omni_ai/pii.py +281 -0
  18. package/omni_ai/profiles.json +69 -0
  19. package/omni_ai/skills.py +124 -0
  20. package/omni_ai/sync.py +199 -0
  21. package/omni_ai/tools.py +75 -0
  22. package/omni_ai/ui.py +595 -0
  23. package/omni_ai/usage.py +305 -0
  24. package/omni_ai/vendor/prompt_toolkit/LICENSE +27 -0
  25. package/omni_ai/vendor/prompt_toolkit/__init__.py +52 -0
  26. package/omni_ai/vendor/prompt_toolkit/application/__init__.py +32 -0
  27. package/omni_ai/vendor/prompt_toolkit/application/application.py +1626 -0
  28. package/omni_ai/vendor/prompt_toolkit/application/current.py +195 -0
  29. package/omni_ai/vendor/prompt_toolkit/application/dummy.py +55 -0
  30. package/omni_ai/vendor/prompt_toolkit/application/run_in_terminal.py +114 -0
  31. package/omni_ai/vendor/prompt_toolkit/auto_suggest.py +177 -0
  32. package/omni_ai/vendor/prompt_toolkit/buffer.py +2029 -0
  33. package/omni_ai/vendor/prompt_toolkit/cache.py +127 -0
  34. package/omni_ai/vendor/prompt_toolkit/clipboard/__init__.py +17 -0
  35. package/omni_ai/vendor/prompt_toolkit/clipboard/base.py +109 -0
  36. package/omni_ai/vendor/prompt_toolkit/clipboard/in_memory.py +44 -0
  37. package/omni_ai/vendor/prompt_toolkit/clipboard/pyperclip.py +42 -0
  38. package/omni_ai/vendor/prompt_toolkit/completion/__init__.py +43 -0
  39. package/omni_ai/vendor/prompt_toolkit/completion/base.py +438 -0
  40. package/omni_ai/vendor/prompt_toolkit/completion/deduplicate.py +45 -0
  41. package/omni_ai/vendor/prompt_toolkit/completion/filesystem.py +118 -0
  42. package/omni_ai/vendor/prompt_toolkit/completion/fuzzy_completer.py +213 -0
  43. package/omni_ai/vendor/prompt_toolkit/completion/nested.py +109 -0
  44. package/omni_ai/vendor/prompt_toolkit/completion/word_completer.py +94 -0
  45. package/omni_ai/vendor/prompt_toolkit/contrib/__init__.py +0 -0
  46. package/omni_ai/vendor/prompt_toolkit/contrib/completers/__init__.py +5 -0
  47. package/omni_ai/vendor/prompt_toolkit/contrib/completers/system.py +64 -0
  48. package/omni_ai/vendor/prompt_toolkit/contrib/regular_languages/__init__.py +80 -0
  49. package/omni_ai/vendor/prompt_toolkit/contrib/regular_languages/compiler.py +579 -0
  50. package/omni_ai/vendor/prompt_toolkit/contrib/regular_languages/completion.py +100 -0
  51. package/omni_ai/vendor/prompt_toolkit/contrib/regular_languages/lexer.py +94 -0
  52. package/omni_ai/vendor/prompt_toolkit/contrib/regular_languages/regex_parser.py +279 -0
  53. package/omni_ai/vendor/prompt_toolkit/contrib/regular_languages/validation.py +60 -0
  54. package/omni_ai/vendor/prompt_toolkit/contrib/ssh/__init__.py +8 -0
  55. package/omni_ai/vendor/prompt_toolkit/contrib/ssh/server.py +178 -0
  56. package/omni_ai/vendor/prompt_toolkit/contrib/telnet/__init__.py +7 -0
  57. package/omni_ai/vendor/prompt_toolkit/contrib/telnet/log.py +13 -0
  58. package/omni_ai/vendor/prompt_toolkit/contrib/telnet/protocol.py +209 -0
  59. package/omni_ai/vendor/prompt_toolkit/contrib/telnet/server.py +428 -0
  60. package/omni_ai/vendor/prompt_toolkit/cursor_shapes.py +117 -0
  61. package/omni_ai/vendor/prompt_toolkit/data_structures.py +18 -0
  62. package/omni_ai/vendor/prompt_toolkit/document.py +1182 -0
  63. package/omni_ai/vendor/prompt_toolkit/enums.py +19 -0
  64. package/omni_ai/vendor/prompt_toolkit/eventloop/__init__.py +31 -0
  65. package/omni_ai/vendor/prompt_toolkit/eventloop/async_generator.py +125 -0
  66. package/omni_ai/vendor/prompt_toolkit/eventloop/inputhook.py +191 -0
  67. package/omni_ai/vendor/prompt_toolkit/eventloop/utils.py +101 -0
  68. package/omni_ai/vendor/prompt_toolkit/eventloop/win32.py +72 -0
  69. package/omni_ai/vendor/prompt_toolkit/filters/__init__.py +71 -0
  70. package/omni_ai/vendor/prompt_toolkit/filters/app.py +419 -0
  71. package/omni_ai/vendor/prompt_toolkit/filters/base.py +261 -0
  72. package/omni_ai/vendor/prompt_toolkit/filters/cli.py +65 -0
  73. package/omni_ai/vendor/prompt_toolkit/filters/utils.py +41 -0
  74. package/omni_ai/vendor/prompt_toolkit/formatted_text/__init__.py +59 -0
  75. package/omni_ai/vendor/prompt_toolkit/formatted_text/ansi.py +297 -0
  76. package/omni_ai/vendor/prompt_toolkit/formatted_text/base.py +179 -0
  77. package/omni_ai/vendor/prompt_toolkit/formatted_text/html.py +145 -0
  78. package/omni_ai/vendor/prompt_toolkit/formatted_text/pygments.py +32 -0
  79. package/omni_ai/vendor/prompt_toolkit/formatted_text/utils.py +103 -0
  80. package/omni_ai/vendor/prompt_toolkit/history.py +306 -0
  81. package/omni_ai/vendor/prompt_toolkit/input/__init__.py +14 -0
  82. package/omni_ai/vendor/prompt_toolkit/input/ansi_escape_sequences.py +344 -0
  83. package/omni_ai/vendor/prompt_toolkit/input/base.py +153 -0
  84. package/omni_ai/vendor/prompt_toolkit/input/defaults.py +79 -0
  85. package/omni_ai/vendor/prompt_toolkit/input/posix_pipe.py +118 -0
  86. package/omni_ai/vendor/prompt_toolkit/input/posix_utils.py +97 -0
  87. package/omni_ai/vendor/prompt_toolkit/input/typeahead.py +78 -0
  88. package/omni_ai/vendor/prompt_toolkit/input/vt100.py +309 -0
  89. package/omni_ai/vendor/prompt_toolkit/input/vt100_parser.py +250 -0
  90. package/omni_ai/vendor/prompt_toolkit/input/win32.py +749 -0
  91. package/omni_ai/vendor/prompt_toolkit/input/win32_pipe.py +156 -0
  92. package/omni_ai/vendor/prompt_toolkit/key_binding/__init__.py +22 -0
  93. package/omni_ai/vendor/prompt_toolkit/key_binding/bindings/__init__.py +0 -0
  94. package/omni_ai/vendor/prompt_toolkit/key_binding/bindings/auto_suggest.py +66 -0
  95. package/omni_ai/vendor/prompt_toolkit/key_binding/bindings/basic.py +257 -0
  96. package/omni_ai/vendor/prompt_toolkit/key_binding/bindings/completion.py +206 -0
  97. package/omni_ai/vendor/prompt_toolkit/key_binding/bindings/cpr.py +30 -0
  98. package/omni_ai/vendor/prompt_toolkit/key_binding/bindings/emacs.py +563 -0
  99. package/omni_ai/vendor/prompt_toolkit/key_binding/bindings/focus.py +26 -0
  100. package/omni_ai/vendor/prompt_toolkit/key_binding/bindings/mouse.py +348 -0
  101. package/omni_ai/vendor/prompt_toolkit/key_binding/bindings/named_commands.py +691 -0
  102. package/omni_ai/vendor/prompt_toolkit/key_binding/bindings/open_in_editor.py +52 -0
  103. package/omni_ai/vendor/prompt_toolkit/key_binding/bindings/page_navigation.py +85 -0
  104. package/omni_ai/vendor/prompt_toolkit/key_binding/bindings/scroll.py +190 -0
  105. package/omni_ai/vendor/prompt_toolkit/key_binding/bindings/search.py +96 -0
  106. package/omni_ai/vendor/prompt_toolkit/key_binding/bindings/vi.py +2233 -0
  107. package/omni_ai/vendor/prompt_toolkit/key_binding/defaults.py +63 -0
  108. package/omni_ai/vendor/prompt_toolkit/key_binding/digraphs.py +1378 -0
  109. package/omni_ai/vendor/prompt_toolkit/key_binding/emacs_state.py +36 -0
  110. package/omni_ai/vendor/prompt_toolkit/key_binding/key_bindings.py +670 -0
  111. package/omni_ai/vendor/prompt_toolkit/key_binding/key_processor.py +526 -0
  112. package/omni_ai/vendor/prompt_toolkit/key_binding/vi_state.py +107 -0
  113. package/omni_ai/vendor/prompt_toolkit/keys.py +222 -0
  114. package/omni_ai/vendor/prompt_toolkit/layout/__init__.py +147 -0
  115. package/omni_ai/vendor/prompt_toolkit/layout/containers.py +2738 -0
  116. package/omni_ai/vendor/prompt_toolkit/layout/controls.py +945 -0
  117. package/omni_ai/vendor/prompt_toolkit/layout/dimension.py +220 -0
  118. package/omni_ai/vendor/prompt_toolkit/layout/dummy.py +40 -0
  119. package/omni_ai/vendor/prompt_toolkit/layout/layout.py +412 -0
  120. package/omni_ai/vendor/prompt_toolkit/layout/margins.py +304 -0
  121. package/omni_ai/vendor/prompt_toolkit/layout/menus.py +748 -0
  122. package/omni_ai/vendor/prompt_toolkit/layout/mouse_handlers.py +56 -0
  123. package/omni_ai/vendor/prompt_toolkit/layout/processors.py +1010 -0
  124. package/omni_ai/vendor/prompt_toolkit/layout/screen.py +323 -0
  125. package/omni_ai/vendor/prompt_toolkit/layout/scrollable_pane.py +494 -0
  126. package/omni_ai/vendor/prompt_toolkit/layout/utils.py +80 -0
  127. package/omni_ai/vendor/prompt_toolkit/lexers/__init__.py +21 -0
  128. package/omni_ai/vendor/prompt_toolkit/lexers/base.py +85 -0
  129. package/omni_ai/vendor/prompt_toolkit/lexers/pygments.py +328 -0
  130. package/omni_ai/vendor/prompt_toolkit/log.py +13 -0
  131. package/omni_ai/vendor/prompt_toolkit/mouse_events.py +85 -0
  132. package/omni_ai/vendor/prompt_toolkit/output/__init__.py +15 -0
  133. package/omni_ai/vendor/prompt_toolkit/output/base.py +332 -0
  134. package/omni_ai/vendor/prompt_toolkit/output/color_depth.py +64 -0
  135. package/omni_ai/vendor/prompt_toolkit/output/conemu.py +65 -0
  136. package/omni_ai/vendor/prompt_toolkit/output/defaults.py +106 -0
  137. package/omni_ai/vendor/prompt_toolkit/output/flush_stdout.py +87 -0
  138. package/omni_ai/vendor/prompt_toolkit/output/plain_text.py +143 -0
  139. package/omni_ai/vendor/prompt_toolkit/output/vt100.py +748 -0
  140. package/omni_ai/vendor/prompt_toolkit/output/win32.py +683 -0
  141. package/omni_ai/vendor/prompt_toolkit/output/windows10.py +128 -0
  142. package/omni_ai/vendor/prompt_toolkit/patch_stdout.py +297 -0
  143. package/omni_ai/vendor/prompt_toolkit/py.typed +0 -0
  144. package/omni_ai/vendor/prompt_toolkit/renderer.py +814 -0
  145. package/omni_ai/vendor/prompt_toolkit/search.py +226 -0
  146. package/omni_ai/vendor/prompt_toolkit/selection.py +58 -0
  147. package/omni_ai/vendor/prompt_toolkit/shortcuts/__init__.py +46 -0
  148. package/omni_ai/vendor/prompt_toolkit/shortcuts/dialogs.py +330 -0
  149. package/omni_ai/vendor/prompt_toolkit/shortcuts/progress_bar/__init__.py +33 -0
  150. package/omni_ai/vendor/prompt_toolkit/shortcuts/progress_bar/base.py +449 -0
  151. package/omni_ai/vendor/prompt_toolkit/shortcuts/progress_bar/formatters.py +431 -0
  152. package/omni_ai/vendor/prompt_toolkit/shortcuts/prompt.py +1513 -0
  153. package/omni_ai/vendor/prompt_toolkit/shortcuts/utils.py +239 -0
  154. package/omni_ai/vendor/prompt_toolkit/styles/__init__.py +67 -0
  155. package/omni_ai/vendor/prompt_toolkit/styles/base.py +184 -0
  156. package/omni_ai/vendor/prompt_toolkit/styles/defaults.py +236 -0
  157. package/omni_ai/vendor/prompt_toolkit/styles/named_colors.py +162 -0
  158. package/omni_ai/vendor/prompt_toolkit/styles/pygments.py +70 -0
  159. package/omni_ai/vendor/prompt_toolkit/styles/style.py +401 -0
  160. package/omni_ai/vendor/prompt_toolkit/styles/style_transformation.py +374 -0
  161. package/omni_ai/vendor/prompt_toolkit/token.py +9 -0
  162. package/omni_ai/vendor/prompt_toolkit/utils.py +327 -0
  163. package/omni_ai/vendor/prompt_toolkit/validation.py +192 -0
  164. package/omni_ai/vendor/prompt_toolkit/widgets/__init__.py +63 -0
  165. package/omni_ai/vendor/prompt_toolkit/widgets/base.py +995 -0
  166. package/omni_ai/vendor/prompt_toolkit/widgets/dialogs.py +108 -0
  167. package/omni_ai/vendor/prompt_toolkit/widgets/menus.py +374 -0
  168. package/omni_ai/vendor/prompt_toolkit/widgets/toolbars.py +370 -0
  169. package/omni_ai/vendor/prompt_toolkit/win32_types.py +229 -0
  170. package/omni_ai/vendor/wcwidth/LICENSE +27 -0
  171. package/omni_ai/vendor/wcwidth/__init__.py +29 -0
  172. package/omni_ai/vendor/wcwidth/table_vs16.py +125 -0
  173. package/omni_ai/vendor/wcwidth/table_wide.py +1493 -0
  174. package/omni_ai/vendor/wcwidth/table_zero.py +4843 -0
  175. package/omni_ai/vendor/wcwidth/unicode_versions.py +38 -0
  176. package/omni_ai/vendor/wcwidth/wcwidth.py +345 -0
  177. package/package.json +40 -4
@@ -0,0 +1,615 @@
1
+ # Omni AI CLI
2
+
3
+ ![npm](https://img.shields.io/badge/npm-coming%20soon-1A1714)
4
+ [![License: MIT](https://img.shields.io/badge/license-MIT-C4552F.svg)](../LICENSE)
5
+ ![Platforms](https://img.shields.io/badge/macOS%20·%20Linux%20·%20Windows-1A1714)
6
+ ![Languages](https://img.shields.io/badge/pt--BR%20·%20en%20·%20es%20·%20fr-C4552F)
7
+
8
+ > [Português](../README.md) · [Español](README.es.md) · [Français](README.fr.md)
9
+
10
+ A chat terminal that brings **Claude Code, Codex, Cursor, Antigravity, Grok, DeepSeek and
11
+ OpenRouter** together in one place. You type your question, `ai` decides who answers, and
12
+ if a tool fails or runs out of quota, it moves on to the next one without you doing anything.
13
+
14
+ While an answer is being generated, the input box stays free: you keep sending requests,
15
+ they go into a queue shown above the box and run in order.
16
+
17
+ Simple questions go to the cheaper tools. Heavy tasks go to Claude, with automatic
18
+ switching between several accounts when one hits its limit. History is saved, the
19
+ interface speaks Portuguese, English, Spanish and French, and models and accounts are
20
+ configured from inside the chat itself.
21
+
22
+ ---
23
+
24
+ ## Contents
25
+
26
+ - [How it works](#how-it-works)
27
+ - [Installation](#installation)
28
+ - [How to use](#how-to-use)
29
+ - [Chat commands](#chat-commands)
30
+ - [Models](#models)
31
+ - [Claude accounts](#claude-accounts)
32
+ - [API keys](#api-keys)
33
+ - [Modes](#modes)
34
+ - [Skills and MCP](#skills-and-mcp)
35
+ - [Direct use, no chat](#direct-use-no-chat)
36
+ - [Where files live](#where-files-live)
37
+ - [Project structure](#project-structure)
38
+ - [Tests](#tests)
39
+ - [Troubleshooting](#troubleshooting)
40
+ - [Known limitations](#known-limitations)
41
+ - [Privacy](#privacy)
42
+ - [License](#license)
43
+
44
+ ---
45
+
46
+ ## How it works
47
+
48
+ From the typed line to the saved answer. The chat (`chat.py`) does not call any AI
49
+ directly: it launches the router (`cli.py`) as a subprocess and reads the events it
50
+ emits, line by line, to show who is answering and what they are doing.
51
+
52
+ ```mermaid
53
+ flowchart TD
54
+ A["You type and press Enter<br/>(the box never locks)"] --> B{"Starts with / ?"}
55
+ B -- yes --> C["Internal command<br/>/model · /account · /key · /lang · /versions"]
56
+ C --> C1["config.py writes to<br/>models.json, profiles.json or keys/"]
57
+ B -- no --> FQ["Goes into the queue<br/>with the tool selected at send time"]
58
+
59
+ subgraph CHAT ["chat.py — chat"]
60
+ FQ --> FW{"Waited in the queue and the previous<br/>answer already covers the request?"}
61
+ FW -- "yes (free classifier)" --> FS["Skipped, shown on screen"]
62
+ FW -- no --> D["Builds the context: latest messages<br/>of the session up to 48 thousand characters + language"]
63
+ D --> E["Thread worker launches the router<br/>in its own process group"]
64
+ E -. "JSON events line by line" .-> F["Shows provider, commands and answer"]
65
+ X["Ctrl-C"] -. "empties the queue; SIGTERM to the group,<br/>after 5 s, SIGKILL" .-> E
66
+ end
67
+
68
+ subgraph ROUTER ["cli.py — router"]
69
+ E --> G["Loads models:<br/>env > models.json > default"]
70
+ G --> H{"Tool pinned<br/>with /tool?"}
71
+ H -- yes --> H1["Only the chosen tool"]
72
+ H -- auto --> I{"Classifies: Groq → Cerebras →<br/>free OpenRouter → Grok;<br/>no answer, keywords"}
73
+ I -- simple --> J["OpenRouter: free, or paid<br/>if authorized · personal data masked"]
74
+ J -- "no key or failed" --> K["Codex: subscription,<br/>then API key"]
75
+ K -- failed --> L["Antigravity: subscription,<br/>then API key"]
76
+ L -- failed --> LP["Paid ones only if listed in fallback_order:<br/>Cursor, Grok, DeepSeek"]
77
+ LP -- failed --> M
78
+ I -- complex --> M["Claude: next active account<br/>by priority"]
79
+ M --> N{"Rate limit?"}
80
+ N -- yes --> O["Account on 60 min cooldown"]
81
+ O --> P{"Another account<br/>available?"}
82
+ P -- yes --> M
83
+ P -- no --> PK{"Anthropic<br/>API key?"}
84
+ PK -- yes --> PK1["Claude with ANTHROPIC_API_KEY"]
85
+ PK1 -- ok --> R
86
+ PK1 -- failed --> Q
87
+ PK -- no --> Q["Last resort:<br/>Codex, then Antigravity"]
88
+ end
89
+
90
+ N -- no --> R["Answer"]
91
+ J -- ok --> R
92
+ K -- ok --> R
93
+ L -- ok --> R
94
+ LP -- ok --> R
95
+ Q --> R
96
+ H1 --> R
97
+ R --> F
98
+ F --> S[("Session saved to disk<br/>with atomic write")]
99
+ S --> FQ
100
+ ```
101
+
102
+ Three details the diagram makes explicit:
103
+
104
+ - **Each Claude account has its own login folder.** Extra accounts run with
105
+ `CLAUDE_CONFIG_DIR` pointing to `~/.claude-accounts/<nome>`. The main account is
106
+ never logged out, and no credential is copied from one place to another. Copying
107
+ `.credentials.json` does not work reliably on macOS, where the login lives in the
108
+ Keychain.
109
+ - **Ctrl-C kills the whole tree.** The router runs in its own process group, so
110
+ canceling also ends the `claude`, `codex` or `agy` running underneath, instead of
111
+ leaving orphan processes burning quota.
112
+ - **The queue blocks nothing.** Above the box you see the running request, the elapsed
113
+ time and the next ones in line. A request that waited goes through a free classifier
114
+ before running: if the previous answer already covers it, it is skipped. Commands and
115
+ skills always run. Ctrl-C with the queue active cancels everything without leaving the chat. If you
116
+ close the chat with requests in line, they stay saved in the session and `ai --resume ID` offers to run them.
117
+ - **Each answer shows what it cost.** At the end of each request you see the tokens and,
118
+ when the tool reports it (OpenRouter, Grok, Claude on an API key), the cost in dollars.
119
+ `/usage` adds up the day's spend per tool.
120
+ - **You see why a tool was picked.** In `auto` mode, one short line says whether the request
121
+ was treated as a coding task or a simple question and who labelled it (Groq, Cerebras,
122
+ OpenRouter, Grok or the keywords).
123
+ - **Only the answer goes into the history.** Status and diagnostic lines appear on
124
+ screen, but are not sent as context with the next question. The models' internal
125
+ reasoning is also discarded.
126
+
127
+ ---
128
+
129
+ ## Installation
130
+
131
+ One command installs everything and, run again, updates it. Your accounts, keys and models live in `~/.config/smart-ai` and are left untouched.
132
+
133
+ **macOS and Linux**:
134
+
135
+ ```bash
136
+ curl -fsSL https://raw.githubusercontent.com/samuelrms/omni-ai-cli/main/get.sh | bash
137
+ ```
138
+
139
+ **Windows** (PowerShell):
140
+
141
+ ```powershell
142
+ irm https://raw.githubusercontent.com/samuelrms/omni-ai-cli/main/get.ps1 | iex
143
+ ```
144
+
145
+ **Coming soon to npm, yarn, pnpm and bun.** The `omni-ai-cli` package is ready and ships as soon as publishing is unlocked:
146
+
147
+ ```bash
148
+ npm install -g omni-ai-cli
149
+ yarn global add omni-ai-cli
150
+ pnpm add -g omni-ai-cli
151
+ bun add -g omni-ai-cli
152
+ ```
153
+
154
+ For now, use the macOS/Linux or Windows command above.
155
+
156
+ To update later: `ai --update` (or `/update` in the chat).
157
+
158
+ From source, for development:
159
+
160
+ ```bash
161
+ git clone https://github.com/samuelrms/omni-ai-cli ~/dev/omni-ai-cli
162
+ cd ~/dev/omni-ai-cli && ./scripts/install.sh # Windows: powershell -File scripts\install.ps1
163
+ ```
164
+
165
+ The installer:
166
+
167
+ 1. installs **Claude Code** through the official installer if it is not on the machine yet;
168
+ 2. opens the main account login if there isn't one yet;
169
+ 3. creates the `ai` command in `~/.local/bin`;
170
+ 4. shows the configured Claude accounts.
171
+
172
+ Requirements: nothing beyond `curl` (macOS/Linux) or PowerShell (Windows). If Python 3.11+ is missing, the installer downloads a Python 3.12 just for Omni AI, without sudo and without touching the system Python.
173
+ Codex (`codex`) and Antigravity (`agy`) are optional and join the fallback queue when they are installed.
174
+
175
+ If `~/.local/bin` is not in your `PATH`, add this to `~/.zshrc`:
176
+
177
+ ```bash
178
+ export PATH="$HOME/.local/bin:$PATH"
179
+ ```
180
+
181
+ On Windows, the installer creates `ai.cmd` in `%USERPROFILE%\.local\bin` and adds that folder to `PATH`; open a new terminal afterwards.
182
+
183
+ ---
184
+
185
+ ## How to use
186
+
187
+ ```bash
188
+ ai
189
+ ```
190
+
191
+ The first time, it asks for the language and offers to install the missing tools
192
+ (Claude Code, Codex, Cursor, Grok Build) with the official installers. Antigravity is
193
+ installed from its own page. After that, just type and press Enter.
194
+
195
+ | Key | Action |
196
+ | --- | --- |
197
+ | Enter | Sends |
198
+ | ↑ / ↓ | Previous lines |
199
+ | Ctrl-C | During an answer: cancels. At the prompt: saves and exits |
200
+ | Ctrl-D | Saves and exits |
201
+ | `\` + Enter · Shift+Enter · Option+Enter | New line without sending (Shift+Enter in iTerm2, Ghostty, Kitty, WezTerm and VS Code; in macOS Terminal use `\` + Enter) |
202
+ | Tab | Completes commands and skills |
203
+
204
+ In the Tab menu every item ends with its owner: `omni`, `claude`, `codex` or `agy`. When you pick a
205
+ skill that takes arguments (like `/pr-review [pr-number | --branch]`), the chat asks for them: Tab
206
+ shows the options, you can type your own text, and Enter alone runs it without arguments.
207
+
208
+ The screen follows Claude Code and Codex: an input box at the bottom with a status bar (tool,
209
+ model, mode and folder), Tab to complete commands and skills, each AI action on one line
210
+ (`● Ran …`, `● Edited …`, `● Called …`) with its output collapsed, markdown answers and a
211
+ "Working…" indicator with elapsed time. On start, a 1.5s full-screen splash (Ctrl-C skips;
212
+ `SMART_AI_NO_SPLASH=1` turns it off).
213
+
214
+ On exit, it shows the command to return to the same conversation:
215
+
216
+ ```text
217
+ Sessão salva. Retome com: ai --resume 1f3a9c2b7d40
218
+ ```
219
+
220
+ ---
221
+
222
+ ## Chat commands
223
+
224
+ | Command | What it does |
225
+ | --- | --- |
226
+ | `/tool auto\|claude\|codex\|cursor\|agy\|openrouter\|grok\|deepseek` | Pins a tool (`auto` decides on its own) |
227
+ | `/model` | Lists the models of each tool |
228
+ | `/model codex gpt-6-astra` | Changes a model |
229
+ | `/model codex default` | Goes back to the default |
230
+ | `/account` | Lists the Claude accounts and whether they are logged in |
231
+ | `/account add pessoal` | Creates an account and opens the Claude login right there |
232
+ | `/account login NOME` | Redoes the login of an account |
233
+ | `/account sync NAME` | Re-shares skills, plugins and MCPs from the main account |
234
+ | `/account disable\|enable\|remove NOME` | Disables, re-enables or removes |
235
+ | `/key` | Lists the configured API keys (masked) |
236
+ | `/key claude\|codex\|cursor\|agy\|openrouter\|grok\|deepseek\|groq\|cerebras` | Adds a key, without showing what you type |
237
+ | `/key add TOOL [NAME]` | One more key for the same tool (any tool, Claude included); keys are tried in order when one fails or hits its limit |
238
+ | `/key remove TOOL [NAME]` | Removes the key (no name: the main one) |
239
+ | `/status` · `/reset` | Account status · clears cooldowns |
240
+ | `/lang pt\|en\|es\|fr` | Changes the language |
241
+ | `/mode default\|plan\|edits\|auto\|yolo` | Permission mode, the same on all three tools |
242
+ | `/sync` | Shares MCPs and skills across Claude, Codex and Antigravity |
243
+ | `/usage [watch]` · `/limits` | Real usage of each account as bars (e-mail, plan, % of the 5-hour and weekly windows, reset time) for Claude and Codex; key spend on OpenRouter; for the others, the limit message the tool gave. `watch` refreshes every 30 s |
244
+ | `/queue [rm\|up\|down N\|clear]` | Shows the queue; removes (`rm`), moves up (`up`) or down (`down`) a request, or empties (`clear`) what waits |
245
+ | `/versions [N]` | What changed in each version, newest first; `N` opens the technical note. Before the first release, shows the `feat`/`fix` commits on `main`. Titles come in the chat's language (translated by the free classifiers and cached). When the chat opens, one line tells you about a new version. |
246
+ | `/clear` · `/compact` | Clear the conversation · summarize it into one block to save context |
247
+ | `/claude:usage` · `/codex:status` · `/agy:usage` … | Each tool's native commands: opens its screen and returns to Omni on exit |
248
+ | `/setup [install]` | First-run guide: what is left to install, log in and set up; `install` installs the missing tools |
249
+ | `/terminal-setup` | Shows how to get Shift+Enter (or Option+Enter) for new lines in your terminal |
250
+ | `/skills` | Lists project and user skills and commands from Claude, Codex and Antigravity |
251
+ | `/name args` | Runs the skill or command (e.g. `/pr-review 237`). Tab completes |
252
+ | `/history` · `/sent` · `/trace` | Conversation · what was sent · run events |
253
+ | `/sessions` · `/resume ID` · `/new` | Saved sessions |
254
+ | `/parallel codex: tarefa \| deepseek: tarefa` | 2 to 4 analyses in parallel, read-only (codex, openrouter, grok, deepseek) |
255
+ | `/help` · `/exit` | Help · exit |
256
+
257
+ While the queue is running, `/clear`, `/new`, `/resume`, `/key`, `/usage` and the native
258
+ commands wait: they take over the terminal or switch the session. Everything else works right away.
259
+
260
+ The configuration commands also work outside the chat: `ai --model`,
261
+ `ai --account add pessoal`, `ai --key claude`, `ai --status`, `ai --reset`.
262
+
263
+ ---
264
+
265
+ ## Models
266
+
267
+ | Key | Default | Accepted values |
268
+ | --- | --- | --- |
269
+ | `claude` | `opus` (the most capable) | `opus`, `sonnet`, `haiku` or full ID |
270
+ | `codex` | `gpt-6-astra` | Codex model ID |
271
+ | `codex_effort` | `high` | `minimal`, `low`, `medium`, `high`, `xhigh` |
272
+ | `agy` | Antigravity default | Antigravity model ID |
273
+ | `openrouter` | automatic (free) | model ID; paid only with `openrouter_paid on` |
274
+ | `openrouter_paid` | `off` | `on` authorizes paid OpenRouter models |
275
+ | `grok` | Grok default | xAI model ID |
276
+ | `deepseek` | `deepseek-chat` | `deepseek-chat`, `deepseek-reasoner` |
277
+ | `cursor` | Cursor account default | Cursor CLI model ID |
278
+
279
+ The priority order is: environment variable, then what was saved with `/model` (in
280
+ `~/.config/smart-ai/models.json`), then the default. The variables are
281
+ `SMART_AI_CLAUDE_MODEL`, `SMART_AI_CODEX_MODEL`, `SMART_AI_CODEX_EFFORT`,
282
+ `SMART_AI_AGY_MODEL`, `SMART_AI_OPENROUTER_MODEL`, `SMART_AI_GROK_MODEL`,
283
+ `SMART_AI_DEEPSEEK_MODEL` and `SMART_AI_CURSOR_MODEL`. When one of them is set,
284
+ `/model` warns that it takes precedence.
285
+
286
+ ---
287
+
288
+ ## Claude accounts
289
+
290
+ ```text
291
+ ai [auto] › /account add trabalho
292
+ Abrindo login do Claude para a conta "trabalho" (pasta isolada ~/.claude-accounts/trabalho).
293
+ Sua conta principal não é afetada.
294
+ ✓ Conta "trabalho" adicionada com prioridade 3.
295
+ ```
296
+
297
+ Accounts are tried in priority order. The one that hits its limit goes on cooldown
298
+ (60 minutes by default, adjustable in `profiles.json`) and the next one takes over. The
299
+ `primary` account uses the normal Claude login (`claude auth login`).
300
+
301
+ Skills, plugins, agents, `CLAUDE.md`, `settings.json` and the main account's MCP servers
302
+ are shared with every extra account at login. Only the login stays separate. If you
303
+ install a new skill or MCP later, run `/account sync NAME`.
304
+
305
+
306
+ ---
307
+
308
+ ## API keys
309
+
310
+ Besides subscriptions, each tool accepts an API key. **The subscription comes
311
+ first**; the key only comes in when the subscription fails or hits its limit, because a
312
+ key is billed per use. OpenRouter works with a key only.
313
+
314
+ | Tool | Command | Variable the CLI receives | Where to create |
315
+ | --- | --- | --- | --- |
316
+ | Claude | `/key claude` | `ANTHROPIC_API_KEY` | console.anthropic.com/settings/keys |
317
+ | Codex | `/key codex` | `CODEX_API_KEY` | platform.openai.com/api-keys |
318
+ | Antigravity | `/key agy` | `GEMINI_API_KEY` | aistudio.google.com/apikey |
319
+ | OpenRouter | `/key openrouter` | `OPENROUTER_API_KEY` | openrouter.ai/keys |
320
+ | Cursor | `/key cursor` | `CURSOR_API_KEY` | cursor.com/dashboard → Integrations |
321
+ | Grok | `/key grok` | `XAI_API_KEY` | console.x.ai |
322
+ | DeepSeek | `/key deepseek` | `DEEPSEEK_API_KEY` | platform.deepseek.com/api_keys |
323
+ | Groq (classifier) | `/key groq` | `GROQ_API_KEY` | console.groq.com/keys |
324
+ | Cerebras (classifier) | `/key cerebras` | `CEREBRAS_API_KEY` | cloud.cerebras.ai |
325
+
326
+ Cursor uses the account login first (`cursor-agent login`) and only then the keys.
327
+ DeepSeek, like OpenRouter, works only with a key and masks personal data before sending.
328
+ Groq and Cerebras don't answer questions: they only classify each request (for free) and
329
+ check whether a queued request has already been answered.
330
+
331
+ ```text
332
+ ai [auto] › /key claude
333
+ Chave de claude (console.anthropic.com/settings/keys). Cole e tecle Enter; ela não aparece na tela.
334
+ >
335
+ ✓ Chave de claude salva em ~/.config/smart-ai/keys/claude (permissão 600).
336
+ ```
337
+
338
+ Keys are stored in `~/.config/smart-ai/keys/`, one per file, with permission 600, and
339
+ are passed only to the tool's process, never to the history or the log. If the variable
340
+ is already set in the environment, it takes precedence. `/key` lists what is
341
+ configured, showing only the start and end of each key.
342
+
343
+ On Claude, the key works as an extra account after all the subscriptions: if it also
344
+ hits its limit, it goes on cooldown like the others.
345
+
346
+ > **Never paste a key as a shell command.** It gets recorded in the zsh history.
347
+ > If that happens, revoke the key on the tool's website and delete the line from
348
+ > `~/.zsh_history`.
349
+
350
+ On OpenRouter the default is free models only: the client picks up to three `:free`
351
+ variants from the catalog and uses `openrouter/free` as a last alternative. Paid models
352
+ stay blocked until you authorize them:
353
+
354
+ ```text
355
+ /model openrouter_paid on # authorizes; warns it will be billed to your account
356
+ /model openrouter anthropic/claude-opus-5.5
357
+ /model openrouter_paid off # blocks again and goes back to free
358
+ ```
359
+
360
+ With authorization on, the chosen model goes first and free models stay as backup. With
361
+ it off, a zero price cap is sent on every call, so no paid model answers.
362
+
363
+ ---
364
+
365
+ ## Modes
366
+
367
+ The mode applies to Claude, Codex and Antigravity at once and shows in the prompt
368
+ (`ai [auto · plan] ›`). Each session keeps its own.
369
+
370
+ | Mode | What it does | Claude | Codex | Antigravity |
371
+ | --- | --- | --- | --- | --- |
372
+ | `default` | Each tool's default | — | — | — |
373
+ | `plan` | Reads and plans only, changes nothing | `plan` | `--sandbox read-only` | `--mode plan` |
374
+ | `edits` | Accepts file edits | `acceptEdits` | `--sandbox workspace-write` | `--mode accept-edits` |
375
+ | `auto` | Acts on its own, with the tool's safeguards | `auto` | `workspace-write`, no approval | `--mode auto` |
376
+ | `yolo` | No permissions or sandbox | `bypassPermissions` | `--dangerously-bypass-approvals-and-sandbox` | `--dangerously-skip-permissions` |
377
+
378
+ `/mode yolo` asks for confirmation, because the AI can then delete files and run any
379
+ command. Outside the chat: `ai --mode plan "question"`. `/parallel` always runs in `plan`.
380
+
381
+ ---
382
+
383
+ ## Skills and MCP
384
+
385
+ `ai` calls the CLIs themselves, so each one uses the skills and MCPs it already has.
386
+ `/sync` gathers everything and installs in each tool what only exists in the others:
387
+
388
+ ```text
389
+ ai [auto] › /sync
390
+ Ferramentas encontradas: claude, codex, agy
391
+ MCP github codex → claude
392
+ MCP obsidian claude → codex
393
+ skill humanizer claude → codex (~/.agents/skills/humanizer)
394
+ Aplicar? [s/N]
395
+ ```
396
+
397
+ - MCPs are added through each CLI's own `mcp add` command, not by editing files by hand.
398
+ - The same server under different names is recognized by package or URL and not duplicated.
399
+ - MCPs that ship inside an app (like Codex.app's) stay there.
400
+ - Skills are shared by link: `~/.claude/skills`, `~/.agents/skills` (Codex) and
401
+ `~/.gemini/antigravity/skills`. Editing one applies to all three.
402
+ - Extra Claude accounts are refreshed too.
403
+ - MCP environment variables, which may hold tokens, are copied into the other tool's
404
+ config. `/sync` warns before doing it.
405
+ - `/sync --dry-run` only shows what it would do.
406
+
407
+ **Project skills and commands:** the chat reads `.claude/commands`, `.claude/skills`, `.agents/skills`
408
+ and `.agent/workflows` from the open project, plus the user folders. `/pr-review 237` goes to
409
+ Claude exactly as typed, and Claude runs the command natively. A skill that only exists in
410
+ Codex or Antigravity goes to that tool, with the file's instructions in the request.
411
+
412
+ Since the chat doesn't forward approval prompts, an MCP tool only runs if it is already
413
+ allowed in the CLI's permissions or the mode is `auto` or `yolo`. OpenRouter has no skills
414
+ or MCP.
415
+
416
+ ---
417
+
418
+ ## Direct use, no chat
419
+
420
+ ```bash
421
+ ai "o que é um design pattern?" # automatic routing
422
+ ai --claude "refatorar o módulo X" # forces Claude
423
+ ai --codex "explicar REST" # forces Codex
424
+ ai --agy "pesquisar React hooks" # forces Antigravity
425
+ ai --openrouter "explicar Docker" # forces OpenRouter
426
+ ai --cursor "corrigir o teste X" # forces Cursor
427
+ ai --deepseek "explicar CAP theorem" # forces DeepSeek
428
+ ai --install # installs the missing tools (asks first)
429
+ ai --classify "prompt" # shows how it would be classified
430
+ ai --log # latest calls
431
+ ```
432
+
433
+ ---
434
+
435
+ ## Where files live
436
+
437
+ | Path | Contents |
438
+ | --- | --- |
439
+ | `~/.config/smart-ai/profiles.json` | Claude accounts and routing rules |
440
+ | `~/.config/smart-ai/models.json` | Models chosen with `/model` |
441
+ | `~/.config/smart-ai/lang` | Chat language |
442
+ | `~/.config/smart-ai/keys/<ferramenta>` | API keys, one per file (permission 600) |
443
+ | `~/.claude-accounts/<nome>/` | Isolated login of each extra Claude account |
444
+ | `~/.local/share/smart-ai/sessions/` | Chat sessions |
445
+ | `~/.cache/smart-ai/state.json` | Active cooldowns |
446
+ | `~/.cache/smart-ai/usage.log` | Usage log, metadata only |
447
+ | `~/.cache/smart-ai/costs.jsonl` | Cost and tokens of each chat request, no prompt or answer (day total in `/usage`) |
448
+
449
+ ---
450
+
451
+ ## Project structure
452
+
453
+ ```text
454
+ omni-ai-cli/
455
+ ├── omni_ai/ # the program (Python package)
456
+ │ ├── tools.py # single tool registry: name, color, CLI, model, key
457
+ │ ├── cli.py # ai command: classifies, routes and runs each tool
458
+ │ ├── chat.py # chat: sessions, events, Ctrl-C, languages
459
+ │ ├── ui.py # look: input box, status bar, actions and answers
460
+ │ ├── skills.py # project, user and native skills/commands
461
+ │ ├── config.py # models, keys and Claude accounts (/model, /key, /account)
462
+ │ ├── sync.py # /sync: MCPs and skills across the three tools
463
+ │ ├── openrouter.py # OpenRouter client: free, paid only if authorized
464
+ │ ├── grok.py # xAI client, reused by DeepSeek
465
+ │ ├── deepseek.py # DeepSeek client with personal data masked
466
+ │ ├── pii.py # masks and restores personal data
467
+ │ ├── osutils.py # differences between macOS, Linux and Windows
468
+ │ ├── profiles.json # starting template; yours lives in ~/.config/smart-ai
469
+ │ └── vendor/ # bundled prompt_toolkit and wcwidth (no pip)
470
+ ├── bin/ai.js # npm launcher: finds or installs Python and runs the package
471
+ ├── scripts/ # install.sh, install.ps1 and ai.cmd
472
+ ├── docs/ # README in English, Spanish and French
473
+ ├── tests/ # fake executables, no quota used
474
+ ├── postman/ # OpenRouter HTTP simulation
475
+ ├── get.sh / get.ps1 # one-command install
476
+ └── package.json · LICENSE
477
+ ```
478
+
479
+ The router knows nothing about chat: it only runs a question and, with
480
+ `SMART_AI_EVENTS=1`, emits JSON events. The chat knows nothing about credentials:
481
+ accounts and models go through `config.py`.
482
+
483
+ ---
484
+
485
+ ## Tests
486
+
487
+ ```bash
488
+ python3 -m unittest discover -s tests -v
489
+ bash -n scripts/install.sh
490
+ ```
491
+
492
+ **38 tests**: session persistence and resume, context limit, file permissions, session
493
+ open in two terminals, event reading, process tree cancellation, real parallelism,
494
+ read-only sandbox, personal data masking, OpenRouter → Codex fallback, model
495
+ validation, key storage and the account lifecycle. All the
496
+ executables are fake: **no test authenticates or uses up a subscription.**
497
+
498
+ To test OpenRouter's HTTP against the Postman simulation:
499
+
500
+ ```bash
501
+ postman simulation run postman/simulations/openrouter.sim.yaml --port 4901 --no-history --output ndjson
502
+ SMART_AI_SIM_URL=http://127.0.0.1:4901/openrouter python3 -m unittest discover -s tests -v
503
+ ```
504
+
505
+ ---
506
+
507
+ ## Releases and branches
508
+
509
+ Publishing to npm is automatic, run by GitHub Actions with semantic-release. The
510
+ version comes from commit messages (Conventional Commits) — nobody edits `version` in
511
+ `package.json` by hand.
512
+
513
+ | Branch | Publishes as | Example |
514
+ |---|---|---|
515
+ | `main` | `latest` | `1.4.0` |
516
+ | `next` | `next` (release candidate) | `1.5.0-rc.1` |
517
+ | `beta` | `beta` | `2.0.0-beta.1` |
518
+ | others / PR | nothing, CI only | — |
519
+
520
+ | Commit | Version effect |
521
+ |---|---|
522
+ | `fix: ...` | patch (`1.4.0` → `1.4.1`) |
523
+ | `feat: ...` | minor (`1.4.0` → `1.5.0`) |
524
+ | `feat!: ...` or `BREAKING CHANGE:` in the body | major (`1.4.0` → `2.0.0`) |
525
+ | `docs:`, `ci:`, `chore:`, `test:`, `refactor:` | no release |
526
+
527
+ Use squash merge: the PR title becomes the commit, and the `PR title` workflow blocks
528
+ titles that don't follow the convention.
529
+
530
+ ### Commit messages
531
+
532
+ `/versions` shows the title of each `feat`/`fix` as the "simple view" and the body as the
533
+ "technical note". So:
534
+
535
+ - **Title:** what changes for the user, in English, present tense, no file or function
536
+ names. `feat: keep the input free and queue new requests while one runs`, not
537
+ `feat: add Queue class to chat.py`.
538
+ - **Body:** the why and the new behavior in short sentences; technical details only at
539
+ the end, if they help.
540
+ - One commit per noticeable change. Refactoring and tests go in `refactor:`/`test:`,
541
+ which don't show up in `/versions`.
542
+
543
+ ```mermaid
544
+ flowchart TD
545
+ A[PR opened] --> B[CI: Python 3.11-3.13 tests on Linux and macOS<br/>+ npm pack + launcher]
546
+ A --> C[PR title: Conventional Commits?]
547
+ B -->|failed| X[Merge blocked]
548
+ C -->|invalid| X
549
+ B -->|ok| D[Squash merge into main / next / beta]
550
+ C -->|ok| D
551
+ D --> E[Release: CI runs again]
552
+ E -->|failed| Y[Nothing is published]
553
+ E -->|ok| F{semantic-release:<br/>any feat/fix/breaking<br/>since last tag?}
554
+ F -->|no| Z[No release]
555
+ F -->|yes| G[Compute version and tag vX.Y.Z]
556
+ G --> H[npm publish with provenance<br/>on the branch dist-tag]
557
+ G --> I[GitHub Release with notes]
558
+ ```
559
+
560
+ One-time GitHub setup:
561
+
562
+ 1. **Settings → Secrets and variables → Actions**: add `NPM_TOKEN` (an *Automation* or
563
+ *Granular* npmjs.com token with publish permission).
564
+ 2. **Settings → Branches**: protect `main` (and `next`/`beta` if used), requiring a PR and
565
+ the `Tests (...)`, `npm package` and `conventional` checks.
566
+ 3. **Settings → General → Pull Requests**: allow only *squash merging* and tick *Default
567
+ to pull request title*.
568
+
569
+ ---
570
+
571
+ ## Troubleshooting
572
+
573
+ | Problem | Solution |
574
+ | --- | --- |
575
+ | `OPENROUTER_API_KEY não configurada` | `/key openrouter` in the chat or `ai --key openrouter`. |
576
+ | `Arquivo de credenciais não encontrado` or `Conta 'X' sem login` | `/account login X`. |
577
+ | `You've hit your session limit` | Account quota. `ai` moves to the next one; add another with `/account add` or a key with `/key claude`. |
578
+ | `Individual quota reached` (Antigravity) | Antigravity quota; it is skipped until it renews. |
579
+ | `Not inside a trusted directory` (Codex) | Old install. Run the installer again. |
580
+ | `Claude Code não instalado` | `./scripts/install.sh` (or `scripts\install.ps1` on Windows) installs it with the official installer. |
581
+ | Windows: skill links fail | Turn on Developer Mode or run as administrator; otherwise `ai` uses folder junctions. |
582
+ | `/model` changes nothing | A `SMART_AI_*_MODEL` variable is set and takes precedence. |
583
+ | `/key` changes nothing | The tool's variable (`ANTHROPIC_API_KEY` etc.) is set and takes precedence. |
584
+ | `Sessão já aberta em outro terminal` | Close the other terminal or use `/new`. |
585
+ | `ai: command not found` | Add `~/.local/bin` to the `PATH`. |
586
+
587
+ ---
588
+
589
+ ## Known limitations
590
+
591
+ - The chat uses the CLIs' non-interactive modes. Native approval prompts (for
592
+ example, "can I edit this file?") do not appear; in those cases, use the native CLI.
593
+ - `/parallel` only analyzes: Codex in `--sandbox read-only` or OpenRouter, which does not
594
+ access the files. The agents do not edit the project at the same time.
595
+ - The simple/complex classification is by keywords and length, not by a model.
596
+ - Switching models does not renew the account's quota.
597
+ - OpenRouter has no streaming in this version: the answer appears whole at the end.
598
+ - On Windows the code uses the native equivalents (process groups and `taskkill`, `msvcrt`, folder junctions), but it has only been tested for real on macOS.
599
+
600
+ ---
601
+
602
+ ## Privacy
603
+
604
+ - Sessions, API keys and logs stay only on your machine, with permissions restricted to your user.
605
+ - The usage log records tool, account and status, **never the text of the question**.
606
+ - Before going to OpenRouter, emails, phone numbers, documents and other personal data
607
+ are swapped for placeholders and restored in the answer. Masking is pattern-based and
608
+ does not guarantee full anonymization.
609
+ - Claude, Codex and Antigravity receive the original text, under each service's terms.
610
+
611
+ ---
612
+
613
+ ## License
614
+
615
+ [MIT](../LICENSE). Bundled in `vendor/`: prompt_toolkit (BSD-3-Clause) and wcwidth (MIT), each with its license.