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