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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 samuelrms
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,564 @@
1
- # Temporary Holding Version
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>
2
4
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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
+ ![Plataformas](https://img.shields.io/badge/macOS%20·%20Linux%20·%20Windows-1A1714)
12
+ ![Idiomas](https://img.shields.io/badge/pt--BR%20·%20en%20·%20es%20·%20fr-C4552F)
13
+
14
+ > [English](docs/README.en.md) · [Español](docs/README.es.md) · [Français](docs/README.fr.md)
15
+
16
+ Um terminal de chat que junta **Claude Code, Codex, Cursor, Antigravity, Grok, DeepSeek e
17
+ OpenRouter** num lugar só. Você escreve a pergunta, o `ai` decide quem responde e, se a
18
+ ferramenta falhar ou estourar a cota, passa para a próxima sem você fazer nada.
19
+
20
+ Enquanto uma resposta está sendo gerada, a caixa de entrada continua livre: você segue
21
+ mandando pedidos, eles entram numa fila visível acima da caixa e rodam em ordem.
22
+
23
+ Perguntas simples vão para as ferramentas mais baratas. Tarefas pesadas vão para o
24
+ Claude, com troca automática entre várias contas quando uma bate no limite. O histórico
25
+ fica salvo, a interface fala português, inglês, espanhol e francês, e modelos e contas
26
+ se configuram de dentro do próprio chat.
27
+
28
+ ---
29
+
30
+ ## Índice
31
+
32
+ - [Como funciona](#como-funciona)
33
+ - [Instalação](#instalação)
34
+ - [Como usar](#como-usar)
35
+ - [Comandos do chat](#comandos-do-chat)
36
+ - [Modelos](#modelos)
37
+ - [Contas Claude](#contas-claude)
38
+ - [Chaves de API](#chaves-de-api)
39
+ - [Modos](#modos)
40
+ - [Skills e MCP](#skills-e-mcp)
41
+ - [Uso direto, sem chat](#uso-direto-sem-chat)
42
+ - [Onde ficam os arquivos](#onde-ficam-os-arquivos)
43
+ - [Estrutura do projeto](#estrutura-do-projeto)
44
+ - [Testes](#testes)
45
+ - [Troubleshooting](#troubleshooting)
46
+ - [Limitações conhecidas](#limitações-conhecidas)
47
+ - [Privacidade](#privacidade)
48
+ - [Licença](#licença)
49
+
50
+ ---
51
+
52
+ ## Como funciona
53
+
54
+ Da linha digitada até a resposta salva. O chat (`chat.py`) não chama nenhuma IA
55
+ direto: ele dispara o roteador (`cli.py`) como subprocesso e lê os eventos que ele
56
+ emite, linha a linha, para mostrar quem está atendendo e o que está fazendo.
57
+
58
+ ![Fluxo do pedido até a resposta](https://raw.githubusercontent.com/samuelrms/omni-ai-cli/main/docs/diagrams/flow1.pt.png)
59
+
60
+ Três detalhes que o diagrama torna explícitos:
61
+
62
+ - **Cada conta Claude tem sua própria pasta de login.** Contas extras rodam com
63
+ `CLAUDE_CONFIG_DIR` apontando para `~/.claude-accounts/<nome>`. A conta principal
64
+ nunca é deslogada, e nenhuma credencial é copiada de um lugar para outro. Copiar o
65
+ `.credentials.json` não funciona de forma confiável no macOS, onde o login fica no
66
+ Keychain.
67
+ - **Ctrl-C mata a árvore inteira.** O roteador roda num grupo de processos próprio, então
68
+ cancelar encerra também o `claude`, o `codex` ou o `agy` que estiver rodando embaixo,
69
+ em vez de deixar processos órfãos gastando cota.
70
+ - **A fila não bloqueia nada.** Acima da caixa aparece o pedido em execução, o tempo e
71
+ os próximos da fila. Um pedido que esperou passa por um classificador grátis antes de
72
+ rodar: se a resposta anterior já o resolve, ele é pulado. Comandos e skills sempre
73
+ rodam. Ctrl-C com a fila ativa cancela tudo sem sair do chat. Se você fechar o chat com
74
+ pedidos na fila, eles ficam salvos na sessão e o `ai --resume ID` pergunta se quer continuar.
75
+ - **Cada resposta mostra quanto custou.** No fim de cada pedido aparecem os tokens e, quando
76
+ a ferramenta informa (OpenRouter, Grok, Claude com chave de API), o custo em dólar. O
77
+ `/usage` soma o gasto do dia por ferramenta.
78
+ - **Você vê por que a ferramenta foi escolhida.** No modo `auto`, uma linha curta diz se o
79
+ pedido foi tratado como tarefa de código ou pergunta simples e quem classificou (Groq,
80
+ Cerebras, OpenRouter, Grok ou as palavras-chave).
81
+ - **Só a resposta entra no histórico.** Linhas de status e diagnóstico aparecem na tela,
82
+ mas não são enviadas como contexto na próxima pergunta. Raciocínio interno dos modelos
83
+ também é descartado.
84
+
85
+ ---
86
+
87
+ ## Instalação
88
+
89
+ Um comando instala tudo e, rodado de novo, atualiza. Suas contas, chaves e modelos ficam em `~/.config/smart-ai` e não são tocados.
90
+
91
+ **macOS e 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
+ **No npm, yarn, pnpm ou 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
+ Qualquer um dos comandos acima (script ou gerenciador de pacotes) instala o mesmo `ai`.
113
+
114
+ Para atualizar depois: `ai --update` (ou `/update` no chat).
115
+
116
+ Pelo código-fonte, para desenvolver:
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
+ O instalador:
124
+
125
+ 1. instala o **Claude Code** pelo instalador oficial se ele ainda não estiver na máquina;
126
+ 2. abre o login da conta principal se ainda não houver um;
127
+ 3. cria o comando `ai` em `~/.local/bin`;
128
+ 4. mostra as contas Claude configuradas.
129
+
130
+ Requisitos: nenhum além de `curl` (macOS/Linux) ou PowerShell (Windows). Se não houver Python 3.11+, o instalador baixa um Python 3.12 só para o Omni AI, sem sudo e sem mexer no Python do sistema.
131
+ Codex (`codex`) e Antigravity (`agy`) são opcionais e entram na fila de alternativas quando estão instalados.
132
+
133
+ Se `~/.local/bin` não estiver no `PATH`, adicione ao `~/.zshrc`:
134
+
135
+ ```bash
136
+ export PATH="$HOME/.local/bin:$PATH"
137
+ ```
138
+
139
+ No Windows, o instalador cria `ai.cmd` em `%USERPROFILE%\.local\bin` e adiciona essa pasta ao `PATH`; abra um terminal novo depois.
140
+
141
+ ---
142
+
143
+ ## Como usar
144
+
145
+ ```bash
146
+ ai
147
+ ```
148
+
149
+ Na primeira vez ele pergunta o idioma e oferece instalar as ferramentas que faltam
150
+ (Claude Code, Codex, Cursor, Grok Build) com os instaladores oficiais. O Antigravity se
151
+ instala pela página dele. Depois é só escrever e teclar Enter.
152
+
153
+ | Tecla | Ação |
154
+ | --- | --- |
155
+ | Enter | Envia |
156
+ | ↑ / ↓ | Linhas anteriores |
157
+ | Ctrl-C | Durante a resposta: cancela. No prompt: salva e sai |
158
+ | Ctrl-D | Salva e sai |
159
+ | `\` + Enter · Shift+Enter · Option+Enter | Nova linha sem enviar (Shift+Enter no iTerm2, Ghostty, Kitty, WezTerm e VS Code; no Terminal do Mac use `\` + Enter) |
160
+ | Tab | Completa comandos e skills |
161
+
162
+ No menu do Tab, cada item termina dizendo de quem é: `omni`, `claude`, `codex` ou `agy`. Ao escolher
163
+ uma skill que tem argumentos (como `/pr-review [pr-number | --branch]`), o chat pergunta os
164
+ argumentos: Tab mostra as opções, dá para escrever o próprio texto, e Enter sem nada roda sem argumentos.
165
+
166
+ A tela segue o estilo do Claude Code e do Codex: caixa de entrada no rodapé com a barra de status
167
+ (ferramenta, modelo, modo e pasta), Tab para completar comandos e skills, cada ação da IA numa linha
168
+ (`● Ran …`, `● Edited …`, `● Called …`) com a saída recolhida, respostas em markdown e um indicador
169
+ "Trabalhando…" com o tempo. Ao abrir, uma splash de 1,5 s em tela cheia (Ctrl-C pula;
170
+ `SMART_AI_NO_SPLASH=1` desliga).
171
+
172
+ Ao sair, ele mostra o comando para voltar à mesma conversa:
173
+
174
+ ```text
175
+ Sessão salva. Retome com: ai --resume 1f3a9c2b7d40
176
+ ```
177
+
178
+ ---
179
+
180
+ ## Comandos do chat
181
+
182
+ | Comando | O que faz |
183
+ | --- | --- |
184
+ | `/tool auto\|claude\|codex\|cursor\|agy\|openrouter\|grok\|deepseek` | Fixa uma ferramenta (`auto` decide sozinho) |
185
+ | `/model` | Lista os modelos de cada ferramenta |
186
+ | `/model codex gpt-6-astra` | Troca um modelo |
187
+ | `/model codex default` | Volta ao padrão |
188
+ | `/account` | Lista as contas Claude e se estão logadas |
189
+ | `/account add pessoal` | Cria uma conta e abre o login do Claude ali mesmo |
190
+ | `/account login NOME` | Refaz o login de uma conta |
191
+ | `/account sync NOME` | Recompartilha skills, plugins e MCPs da conta principal |
192
+ | `/account disable\|enable\|remove NOME` | Desativa, reativa ou remove |
193
+ | `/key` | Lista as chaves de API configuradas (mascaradas) |
194
+ | `/key claude\|codex\|cursor\|agy\|openrouter\|grok\|deepseek\|groq\|cerebras` | Adiciona uma chave, sem mostrar o que você digita |
195
+ | `/key add FERRAMENTA [NOME]` | Mais uma chave para a mesma ferramenta (vale para todas, inclusive Claude); as chaves são tentadas em ordem quando uma falha ou bate no limite |
196
+ | `/key remove FERRAMENTA [NOME]` | Remove a chave (sem nome, a principal) |
197
+ | `/status` · `/reset` | Status das contas · zera cooldowns |
198
+ | `/lang pt\|en\|es\|fr` | Muda o idioma |
199
+ | `/mode default\|plan\|edits\|auto\|yolo` | Modo de permissão, igual nas três ferramentas |
200
+ | `/sync` | Compartilha MCPs e skills entre Claude, Codex e Antigravity |
201
+ | `/usage [watch]` · `/limits` | Uso real de cada conta em barras (e-mail, plano, % da janela de 5 h e da semana, horário de reset) para Claude e Codex; gasto da chave no OpenRouter; nas demais, o aviso de limite que a ferramenta deu. `watch` atualiza a cada 30 s |
202
+ | `/queue [rm\|up\|down N\|clear]` | Mostra a fila; tira (`rm`), sobe (`up`), desce (`down`) um pedido ou esvazia (`clear`) o que espera |
203
+ | `/versions [N]` | O que mudou em cada versão, mais recente primeiro; `N` abre a nota técnica. Antes da primeira release, mostra os `feat`/`fix` do `main`. Os títulos saem no idioma do chat (traduzidos pelos classificadores grátis e guardados). Ao abrir o chat, uma linha avisa quando há versão nova. |
204
+ | `/clear` · `/compact` | Limpar a conversa · resumir a conversa num único bloco para economizar contexto |
205
+ | `/claude:usage` · `/codex:status` · `/agy:usage` … | Comandos nativos de cada ferramenta: abre a tela dela e volta ao Omni ao sair |
206
+ | `/setup [install]` | Guia de primeiro acesso: o que falta instalar, logar e configurar; `install` instala as ferramentas que faltam |
207
+ | `/terminal-setup` | Mostra como ter Shift+Enter (ou Option+Enter) para nova linha no seu terminal |
208
+ | `/skills` | Lista skills e comandos do projeto e do usuário, de Claude, Codex e Antigravity |
209
+ | `/nome args` | Roda a skill ou comando (ex.: `/pr-review 237`). Tab completa |
210
+ | `/history` · `/sent` · `/trace` | Conversa · o que foi enviado · eventos da execução |
211
+ | `/sessions` · `/resume ID` · `/new` | Sessões salvas |
212
+ | `/parallel codex: tarefa \| deepseek: tarefa` | 2 a 4 análises em paralelo, somente leitura (codex, openrouter, grok, deepseek) |
213
+ | `/help` · `/exit` | Ajuda · sair |
214
+
215
+ Enquanto a fila roda, `/clear`, `/new`, `/resume`, `/key`, `/usage` e os comandos nativos
216
+ esperam: eles tomam o terminal ou trocam a sessão. O resto funciona na hora.
217
+
218
+ Os comandos de configuração também funcionam fora do chat: `ai --model`,
219
+ `ai --account add pessoal`, `ai --key claude`, `ai --status`, `ai --reset`.
220
+
221
+ ---
222
+
223
+ ## Modelos
224
+
225
+ | Chave | Padrão | Valores aceitos |
226
+ | --- | --- | --- |
227
+ | `claude` | `opus` (o mais capaz) | `opus`, `sonnet`, `haiku` ou ID completo |
228
+ | `codex` | `gpt-6-astra` | ID de modelo do Codex |
229
+ | `codex_effort` | `high` | `minimal`, `low`, `medium`, `high`, `xhigh` |
230
+ | `agy` | padrão do Antigravity | ID de modelo do Antigravity |
231
+ | `openrouter` | automático (gratuito) | ID do modelo; pago só com `openrouter_paid on` |
232
+ | `openrouter_paid` | `off` | `on` autoriza modelos pagos do OpenRouter |
233
+ | `grok` | padrão do Grok | ID de modelo da xAI |
234
+ | `deepseek` | `deepseek-chat` | `deepseek-chat`, `deepseek-reasoner` |
235
+ | `cursor` | padrão da conta Cursor | ID de modelo do Cursor CLI |
236
+
237
+ A ordem de prioridade é: variável de ambiente, depois o que foi salvo com `/model` (em
238
+ `~/.config/smart-ai/models.json`), depois o padrão. As variáveis são
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` e `SMART_AI_CURSOR_MODEL`. Quando uma delas está definida, o
242
+ `/model` avisa que ela tem precedência.
243
+
244
+ ---
245
+
246
+ ## Contas Claude
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
+ As contas são tentadas por ordem de prioridade. A que bate no limite entra em cooldown
256
+ (60 minutos por padrão, ajustável em `~/.config/smart-ai/profiles.json`) e a próxima assume. A conta
257
+ `primary` usa o login normal do Claude (`claude auth login`).
258
+
259
+ Skills, plugins, agentes, `CLAUDE.md`, `settings.json` e os servidores MCP da conta
260
+ principal são compartilhados com cada conta extra no login. Só o login fica separado.
261
+ Se você instalar uma skill ou um MCP novo depois, rode `/account sync NOME`.
262
+
263
+
264
+ ---
265
+
266
+ ## Chaves de API
267
+
268
+ Além das assinaturas, cada ferramenta aceita uma chave de API. **A assinatura vem
269
+ primeiro**; a chave só entra quando a assinatura falha ou bate no limite, porque chave é
270
+ cobrada por uso. O OpenRouter funciona só com chave.
271
+
272
+ | Ferramenta | Comando | Variável que o CLI recebe | Onde criar |
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 (classificador) | `/key groq` | `GROQ_API_KEY` | console.groq.com/keys |
282
+ | Cerebras (classificador) | `/key cerebras` | `CEREBRAS_API_KEY` | cloud.cerebras.ai |
283
+
284
+ O Cursor usa primeiro o login da conta (`cursor-agent login`) e só depois as chaves. O
285
+ DeepSeek, como o OpenRouter, funciona só com chave e mascara dados pessoais antes do
286
+ envio. Groq e Cerebras não respondem perguntas: só classificam cada pedido (grátis) e
287
+ checam se um pedido da fila já foi resolvido.
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
+ As chaves ficam em `~/.config/smart-ai/keys/`, uma por arquivo, com permissão 600, e
297
+ são passadas só ao processo da ferramenta, nunca ao histórico nem ao log. Se a variável
298
+ já estiver definida no ambiente, ela tem precedência. `/key` lista o que está
299
+ configurado, mostrando só o início e o fim de cada chave.
300
+
301
+ No Claude, a chave funciona como uma conta extra depois de todas as assinaturas: se ela
302
+ também bater no limite, entra em cooldown como as outras.
303
+
304
+ > **Nunca cole uma chave como comando no shell.** Ela fica gravada no histórico do zsh.
305
+ > Se isso acontecer, revogue a chave no site da ferramenta e apague a linha de
306
+ > `~/.zsh_history`.
307
+
308
+ No OpenRouter, o padrão é usar só modelos gratuitos: o cliente escolhe até três
309
+ variantes `:free` do catálogo e usa `openrouter/free` como última alternativa. Modelos
310
+ pagos ficam bloqueados até você autorizar:
311
+
312
+ ```text
313
+ /model openrouter_paid on # autoriza; avisa que será cobrado na sua conta
314
+ /model openrouter anthropic/claude-opus-5.5
315
+ /model openrouter_paid off # bloqueia de novo e volta ao gratuito
316
+ ```
317
+
318
+ Com a autorização ligada, o modelo escolhido vem primeiro e os gratuitos ficam como
319
+ reserva. Desligada, o limite de preço zero é enviado em toda chamada, então nenhum
320
+ modelo pago responde.
321
+
322
+ ---
323
+
324
+ ## Modos
325
+
326
+ O modo vale para Claude, Codex e Antigravity ao mesmo tempo e aparece no prompt
327
+ (`ai [auto · plan] ›`). Cada sessão guarda o seu.
328
+
329
+ | Modo | O que faz | Claude | Codex | Antigravity |
330
+ | --- | --- | --- | --- | --- |
331
+ | `default` | Padrão de cada ferramenta | — | — | — |
332
+ | `plan` | Só lê e planeja, não altera nada | `plan` | `--sandbox read-only` | `--mode plan` |
333
+ | `edits` | Aceita edições de arquivo | `acceptEdits` | `--sandbox workspace-write` | `--mode accept-edits` |
334
+ | `auto` | Age sozinho, com as proteções da ferramenta | `auto` | `workspace-write`, sem aprovação | `--mode auto` |
335
+ | `yolo` | Sem permissões nem sandbox | `bypassPermissions` | `--dangerously-bypass-approvals-and-sandbox` | `--dangerously-skip-permissions` |
336
+
337
+ `/mode yolo` pede confirmação, porque a IA passa a poder apagar arquivos e rodar qualquer
338
+ comando. Fora do chat: `ai --mode plan "pergunta"`. O `/parallel` roda sempre em `plan`.
339
+
340
+ ---
341
+
342
+ ## Skills e MCP
343
+
344
+ O `ai` chama os próprios CLIs, então cada um usa as skills e os MCPs que já tem. O
345
+ `/sync` junta tudo e instala em cada ferramenta o que só existe nas outras:
346
+
347
+ ```text
348
+ ai [auto] › /sync
349
+ Ferramentas encontradas: claude, codex, agy
350
+ MCP github codex → claude
351
+ MCP obsidian claude → codex
352
+ skill humanizer claude → codex (~/.agents/skills/humanizer)
353
+ Aplicar? [s/N]
354
+ ```
355
+
356
+ - Os MCPs são adicionados pelo comando `mcp add` de cada CLI, não editando arquivos na mão.
357
+ - O mesmo servidor com nomes diferentes é reconhecido pelo pacote ou URL e não é duplicado.
358
+ - MCPs que vêm dentro de um app (como os do Codex.app) ficam só nele.
359
+ - As skills são compartilhadas por atalho: `~/.claude/skills`, `~/.agents/skills` (Codex) e
360
+ `~/.gemini/antigravity/skills`. Editar uma vale para as três.
361
+ - As contas Claude extras são atualizadas junto.
362
+ - Variáveis de ambiente dos MCPs, que podem ter tokens, são copiadas para a config da
363
+ outra ferramenta. O `/sync` avisa antes.
364
+ - `/sync --dry-run` só mostra o que faria.
365
+
366
+ **Skills e comandos do projeto:** o chat lê `.claude/commands`, `.claude/skills`, `.agents/skills` e
367
+ `.agent/workflows` do projeto aberto, além das pastas do usuário. `/pr-review 237` vai para o
368
+ Claude do jeito que foi digitado, e ele executa o comando nativamente. Uma skill que só existe
369
+ no Codex ou no Antigravity vai para essa ferramenta, com as instruções do arquivo no pedido.
370
+
371
+ Como o chat não repassa pedidos de aprovação, uma ferramenta de MCP só roda se já estiver
372
+ liberada nas permissões do CLI ou se o modo for `auto` ou `yolo`. O OpenRouter não tem
373
+ skills nem MCP.
374
+
375
+ ---
376
+
377
+ ## Uso direto, sem chat
378
+
379
+ ```bash
380
+ ai "o que é um design pattern?" # roteamento automático
381
+ ai --claude "refatorar o módulo X" # força Claude
382
+ ai --codex "explicar REST" # força Codex
383
+ ai --agy "pesquisar React hooks" # força Antigravity
384
+ ai --openrouter "explicar Docker" # força OpenRouter
385
+ ai --cursor "corrigir o teste X" # força Cursor
386
+ ai --deepseek "explicar CAP theorem" # força DeepSeek
387
+ ai --install # instala as ferramentas que faltam (pergunta antes)
388
+ ai --classify "prompt" # mostra como seria classificado
389
+ ai --log # últimas chamadas
390
+ ai doctor # diagnóstico de instalação, contas e chaves
391
+ ```
392
+
393
+ ---
394
+
395
+ ## Onde ficam os arquivos
396
+
397
+ | Caminho | Conteúdo |
398
+ | --- | --- |
399
+ | `~/.config/smart-ai/profiles.json` | Contas Claude e regras de roteamento |
400
+ | `~/.config/smart-ai/models.json` | Modelos escolhidos com `/model` |
401
+ | `~/.config/smart-ai/lang` | Idioma do chat |
402
+ | `~/.config/smart-ai/keys/<ferramenta>` | Chaves de API, uma por arquivo (permissão 600) |
403
+ | `~/.claude-accounts/<nome>/` | Login isolado de cada conta Claude extra |
404
+ | `~/.local/share/smart-ai/sessions/` | Sessões do chat |
405
+ | `~/.cache/smart-ai/state.json` | Cooldowns ativos |
406
+ | `~/.cache/smart-ai/usage.log` | Log de uso, só metadados |
407
+ | `~/.cache/smart-ai/costs.jsonl` | Custo e tokens de cada pedido do chat, sem pergunta nem resposta (soma do dia no `/usage`) |
408
+
409
+ ---
410
+
411
+ ## Estrutura do projeto
412
+
413
+ ```text
414
+ omni-ai-cli/
415
+ ├── omni_ai/ # o programa (pacote Python)
416
+ │ ├── tools.py # cadastro único das ferramentas: nome, cor, CLI, modelo, chave
417
+ │ ├── cli.py # comando ai: classifica, roteia e executa cada ferramenta
418
+ │ ├── chat.py # chat: sessões, eventos, Ctrl-C, idiomas
419
+ │ ├── ui.py # visual: caixa de entrada, barra de status, ações e respostas
420
+ │ ├── skills.py # skills e comandos do projeto, do usuário e nativos
421
+ │ ├── config.py # modelos, chaves e contas Claude (/model, /key, /account)
422
+ │ ├── sync.py # /sync: MCPs e skills entre as três ferramentas
423
+ │ ├── openrouter.py # cliente OpenRouter: gratuitos, pagos só se autorizados
424
+ │ ├── grok.py # cliente xAI, reaproveitado pelo DeepSeek
425
+ │ ├── deepseek.py # cliente DeepSeek com dados pessoais mascarados
426
+ │ ├── pii.py # mascara e restaura dados pessoais
427
+ │ ├── osutils.py # diferenças entre macOS, Linux e Windows
428
+ │ ├── profiles.json # modelo inicial; o seu fica em ~/.config/smart-ai
429
+ │ └── vendor/ # prompt_toolkit e wcwidth embutidos (sem pip)
430
+ ├── bin/ai.js # lançador npm: acha ou instala o Python e roda o pacote
431
+ ├── scripts/ # install.sh, install.ps1 e ai.cmd
432
+ ├── docs/ # README em inglês, espanhol e francês
433
+ ├── tests/ # executáveis fictícios, sem consumir cota
434
+ ├── postman/ # simulação HTTP do OpenRouter
435
+ ├── get.sh / get.ps1 # instalação com um comando
436
+ └── package.json · LICENSE
437
+ ```
438
+
439
+ O roteador não sabe nada de chat: ele só executa uma pergunta e, com
440
+ `SMART_AI_EVENTS=1`, emite eventos JSON. O chat não sabe nada de credenciais: contas e
441
+ modelos passam pelo `config.py`.
442
+
443
+ ---
444
+
445
+ ## Testes
446
+
447
+ ```bash
448
+ python3 -m unittest discover -s tests -v
449
+ bash -n scripts/install.sh
450
+ ```
451
+
452
+ **106 testes**: persistência e retomada de sessão, limite de contexto, permissão dos
453
+ arquivos, sessão aberta em dois terminais, leitura de eventos, cancelamento da árvore de
454
+ processos, paralelismo real, sandbox somente leitura, mascaramento de dados pessoais,
455
+ fallback OpenRouter → Codex, validação de modelos, armazenamento das chaves e o ciclo de vida das contas. Todos os
456
+ executáveis são fictícios: **nenhum teste autentica nem consome assinatura.**
457
+
458
+ Para testar o HTTP do OpenRouter contra a simulação Postman:
459
+
460
+ ```bash
461
+ postman simulation run postman/simulations/openrouter.sim.yaml --port 4901 --no-history --output ndjson
462
+ SMART_AI_SIM_URL=http://127.0.0.1:4901/openrouter python3 -m unittest discover -s tests -v
463
+ ```
464
+
465
+ ---
466
+
467
+ ## Releases e branches
468
+
469
+ Publicação no npm é automática, feita pelo GitHub Actions com semantic-release. A versão
470
+ sai das mensagens de commit (Conventional Commits) — ninguém edita `version` no
471
+ `package.json` à mão.
472
+
473
+ | Branch | Publica como | Exemplo |
474
+ |---|---|---|
475
+ | `main` | `latest` | `1.4.0` |
476
+ | `next` | `next` (release candidate) | `1.5.0-rc.1` |
477
+ | `beta` | `beta` | `2.0.0-beta.1` |
478
+ | outras / PR | nada, só CI | — |
479
+
480
+ | Commit | Efeito na versão |
481
+ |---|---|
482
+ | `fix: ...` | patch (`1.4.0` → `1.4.1`) |
483
+ | `feat: ...` | minor (`1.4.0` → `1.5.0`) |
484
+ | `feat!: ...` ou `BREAKING CHANGE:` no corpo | major (`1.4.0` → `2.0.0`) |
485
+ | `docs:`, `ci:`, `chore:`, `test:`, `refactor:` | sem release |
486
+
487
+ Use squash merge: o título do PR vira o commit, e o workflow `PR title` bloqueia título
488
+ fora do padrão.
489
+
490
+ ### Mensagens de commit
491
+
492
+ O `/versions` mostra o título de cada `feat`/`fix` como a "visão simples" e o corpo como
493
+ "nota técnica". Por isso:
494
+
495
+ - **Título:** o que muda para quem usa, em inglês, no presente, sem nome de arquivo ou
496
+ função. `feat: keep the input free and queue new requests while one runs`, não
497
+ `feat: add Queue class to chat.py`.
498
+ - **Corpo:** o porquê e o comportamento novo em frases curtas; detalhes técnicos só no
499
+ fim, se ajudarem.
500
+ - Um commit por mudança perceptível. Refatoração e testes vão em `refactor:`/`test:`,
501
+ que não aparecem em `/versions`.
502
+
503
+ ![Fluxo de release e publicação](https://raw.githubusercontent.com/samuelrms/omni-ai-cli/main/docs/diagrams/flow2.pt.png)
504
+
505
+ A publicação usa **OIDC / trusted publishing**: o GitHub Actions se autentica no npm sem
506
+ token, e o pacote sai com *provenance*. Configuração única:
507
+
508
+ 1. **npm → página do pacote → Settings → Trusted Publisher**: adicionar *GitHub Actions*
509
+ com `samuelrms/omni-ai-cli` e o workflow `release.yml`.
510
+ 2. **GitHub → Settings → Branches**: proteger `main` (e `next`/`beta`, se usar) exigindo PR
511
+ e os checks `Tests (...)`, `npm package` e `conventional`.
512
+ 3. **GitHub → Settings → General → Pull Requests**: deixar só *Allow squash merging* e
513
+ marcar *Default to pull request title*.
514
+
515
+ O workflow já pede `id-token: write` e `NPM_CONFIG_PROVENANCE`; não há `NPM_TOKEN` para
516
+ guardar nem rotacionar.
517
+
518
+ ---
519
+
520
+ ## Troubleshooting
521
+
522
+ | Problema | Solução |
523
+ | --- | --- |
524
+ | `OPENROUTER_API_KEY não configurada` | `/key openrouter` no chat ou `ai --key openrouter`. |
525
+ | `Arquivo de credenciais não encontrado` ou `Conta 'X' sem login` | `/account login X`. |
526
+ | `You've hit your session limit` | Cota da conta. O `ai` passa para a próxima; adicione outra com `/account add` ou uma chave com `/key claude`. |
527
+ | `Individual quota reached` (Antigravity) | Cota do Antigravity; ele é pulado até a renovação. |
528
+ | `Not inside a trusted directory` (Codex) | Instalação antiga. Rode o instalador de novo. |
529
+ | `Claude Code não instalado` | `./scripts/install.sh` (ou `scripts\install.ps1` no Windows) instala pelo instalador oficial. |
530
+ | Windows: atalhos de skills falham | Ative o Modo de Desenvolvedor ou rode como administrador; sem isso o `ai` usa junções de pasta. |
531
+ | `/model` não muda nada | Uma variável `SMART_AI_*_MODEL` está definida e tem precedência. |
532
+ | `/key` não muda nada | A variável da ferramenta (`ANTHROPIC_API_KEY` etc.) está definida e tem precedência. |
533
+ | `Sessão já aberta em outro terminal` | Feche o outro terminal ou use `/new`. |
534
+ | `ai: command not found` | Adicione `~/.local/bin` ao `PATH`. |
535
+
536
+ ---
537
+
538
+ ## Limitações conhecidas
539
+
540
+ - O chat usa os modos não interativos dos CLIs. Pedidos de aprovação nativos (por
541
+ exemplo, "posso editar este arquivo?") não aparecem; nesses casos, use o CLI nativo.
542
+ - `/parallel` só analisa: Codex em `--sandbox read-only` ou OpenRouter, que não acessa os
543
+ arquivos. Os agentes não editam o projeto ao mesmo tempo.
544
+ - A classificação simples/complexa é por palavras-chave e tamanho, não por um modelo.
545
+ - Trocar de modelo não renova a cota da conta.
546
+ - O OpenRouter não tem streaming nesta versão: a resposta aparece inteira no final.
547
+ - No Windows, o código usa os equivalentes nativos (grupo de processos e `taskkill`, `msvcrt`, junções de pasta), mas foi testado de verdade só no macOS.
548
+
549
+ ---
550
+
551
+ ## Privacidade
552
+
553
+ - Sessões, chaves de API e logs ficam só na sua máquina, com permissão restrita ao seu usuário.
554
+ - O log de uso grava ferramenta, conta e status, **nunca o texto da pergunta**.
555
+ - Antes de ir para o OpenRouter, e-mails, telefones, documentos e outros dados pessoais
556
+ são trocados por marcadores e restaurados na resposta. O mascaramento é por padrões e
557
+ não garante anonimização total.
558
+ - Claude, Codex e Antigravity recebem o texto original, sob os termos de cada serviço.
559
+
560
+ ---
561
+
562
+ ## Licença
563
+
564
+ [MIT](LICENSE). Bibliotecas embutidas em `vendor/`: prompt_toolkit (BSD-3-Clause) e wcwidth (MIT), com as licenças em cada pasta.
package/bin/ai.js ADDED
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ // npm launcher: finds Python 3.11+ and runs the bundled omni_ai package with the same arguments.
3
+ 'use strict';
4
+ const { spawnSync, spawn } = require('child_process');
5
+ const path = require('path');
6
+
7
+ const root = path.resolve(__dirname, '..');
8
+ const candidates = process.env.SMART_AI_PYTHON
9
+ ? [[process.env.SMART_AI_PYTHON]]
10
+ : process.platform === 'win32'
11
+ ? [['py', '-3'], ['python'], ['python3']]
12
+ : [['python3'], ['python']];
13
+
14
+ const fs = require('fs');
15
+ const os = require('os');
16
+ const home = os.homedir();
17
+ const dataDir = process.platform === 'win32'
18
+ ? path.join(process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local'), 'smart-ai')
19
+ : path.join(home, '.local', 'share', 'smart-ai');
20
+ const cachedPython = path.join(home, '.config', 'smart-ai', 'python-path');
21
+
22
+ function works(cmd, pre) {
23
+ const probe = spawnSync(cmd, [...pre, '-c', 'import sys; print(sys.version_info >= (3, 11))'], { encoding: 'utf8' });
24
+ return probe.status === 0 && probe.stdout.trim() === 'True';
25
+ }
26
+
27
+ function findPython() {
28
+ try {
29
+ const saved = fs.readFileSync(cachedPython, 'utf8').trim();
30
+ if (saved && works(saved, [])) return [saved, []];
31
+ } catch (_) { /* first run */ }
32
+ for (const [cmd, ...pre] of candidates) {
33
+ if (works(cmd, pre)) return [cmd, pre];
34
+ }
35
+ return null;
36
+ }
37
+
38
+ // No usable Python: fetch a private Python 3.12 with uv (no sudo, system Python untouched)
39
+ function installPython() {
40
+ if (process.env.SMART_AI_NO_AUTO_PYTHON === '1') return null;
41
+ console.error('Python 3.11+ não encontrado. Instalando um Python 3.12 só para o Omni AI (sem sudo)...');
42
+ const uvDir = path.join(dataDir, 'uv');
43
+ const uv = path.join(uvDir, process.platform === 'win32' ? 'uv.exe' : 'uv');
44
+ const env = { ...process.env, UV_INSTALL_DIR: uvDir, UV_NO_MODIFY_PATH: '1',
45
+ UV_PYTHON_INSTALL_DIR: path.join(dataDir, 'python') };
46
+ if (!fs.existsSync(uv)) {
47
+ const setup = process.platform === 'win32'
48
+ ? spawnSync('powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command',
49
+ 'irm https://astral.sh/uv/install.ps1 | iex'], { stdio: 'inherit', env })
50
+ : spawnSync('sh', ['-c', 'curl -LsSf https://astral.sh/uv/install.sh | sh'], { stdio: 'inherit', env });
51
+ if (setup.status !== 0 || !fs.existsSync(uv)) return null;
52
+ }
53
+ // --no-bin: keep the private Python out of the user's PATH
54
+ if (spawnSync(uv, ['python', 'install', '--no-bin', '3.12'], { stdio: 'inherit', env }).status !== 0 &&
55
+ spawnSync(uv, ['python', 'install', '3.12'], { stdio: 'inherit', env }).status !== 0) return null;
56
+ const found = spawnSync(uv, ['python', 'find', '3.12'], { encoding: 'utf8', env });
57
+ const exe = found.stdout.trim();
58
+ if (found.status !== 0 || !exe || !works(exe, [])) return null;
59
+ fs.mkdirSync(path.dirname(cachedPython), { recursive: true });
60
+ fs.writeFileSync(cachedPython, exe);
61
+ console.error(`✓ Python pronto: ${exe}`);
62
+ return [exe, []];
63
+ }
64
+
65
+ const python = findPython() || installPython();
66
+ if (!python) {
67
+ console.error('Omni AI precisa do Python 3.11 ou mais novo e não conseguiu instalá-lo sozinho.');
68
+ console.error(process.platform === 'win32'
69
+ ? 'Instale em https://www.python.org/downloads/ (marque "Add to PATH").'
70
+ : 'macOS: brew install python · Ubuntu/Debian: sudo apt install python3');
71
+ process.exit(1);
72
+ }
73
+
74
+ // Claude Code installed as this package's optional dependency is used when there's no global one
75
+ const env = { ...process.env, SMART_AI_INSTALLER: 'npm',
76
+ PYTHONPATH: [root, process.env.PYTHONPATH].filter(Boolean).join(path.delimiter) };
77
+ env.PATH = [process.env.PATH, path.join(root, 'node_modules', '.bin')].join(path.delimiter);
78
+
79
+ const [cmd, pre] = python;
80
+ const child = spawn(cmd, [...pre, '-m', 'omni_ai', ...process.argv.slice(2)], { stdio: 'inherit', env });
81
+ // Ctrl-C reaches the child through the shared terminal; just don't die before it does
82
+ process.on('SIGINT', () => {});
83
+ process.on('SIGTERM', () => child.kill('SIGTERM'));
84
+ child.on('exit', (code, signal) => process.exit(signal ? 130 : code ?? 1));