omni-ai-cli 1.0.0 → 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.
- package/README.md +24 -76
- package/docs/README.en.md +24 -76
- package/docs/README.es.md +23 -75
- package/docs/README.fr.md +24 -76
- package/docs/diagrams/flow1.en.mmd +47 -0
- package/docs/diagrams/flow1.es.mmd +47 -0
- package/docs/diagrams/flow1.fr.mmd +47 -0
- package/docs/diagrams/flow1.pt.mmd +47 -0
- package/docs/diagrams/flow2.en.mmd +14 -0
- package/docs/diagrams/flow2.es.mmd +14 -0
- package/docs/diagrams/flow2.fr.mmd +14 -0
- package/docs/diagrams/flow2.pt.mmd +14 -0
- package/omni_ai/cli.py +95 -3
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
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
|
+
|
|
1
5
|
# Omni AI CLI
|
|
2
6
|
|
|
3
|
-
](https://www.npmjs.com/package/omni-ai-cli)
|
|
8
|
+
[](https://www.npmjs.com/package/omni-ai-cli)
|
|
9
|
+
[](https://docs.npmjs.com/trusted-publishers)
|
|
4
10
|
[](LICENSE)
|
|
5
11
|

|
|
6
12
|

|
|
@@ -49,55 +55,7 @@ Da linha digitada até a resposta salva. O chat (`chat.py`) não chama nenhuma I
|
|
|
49
55
|
direto: ele dispara o roteador (`cli.py`) como subprocesso e lê os eventos que ele
|
|
50
56
|
emite, linha a linha, para mostrar quem está atendendo e o que está fazendo.
|
|
51
57
|
|
|
52
|
-
|
|
53
|
-
flowchart TD
|
|
54
|
-
A["Você digita e tecla Enter<br/>(a caixa nunca trava)"] --> B{"Começa com / ?"}
|
|
55
|
-
B -- sim --> C["Comando interno<br/>/model · /account · /key · /lang · /versions"]
|
|
56
|
-
C --> C1["config.py grava em<br/>models.json, profiles.json ou keys/"]
|
|
57
|
-
B -- não --> FQ["Entra na fila<br/>com a ferramenta do momento do envio"]
|
|
58
|
-
|
|
59
|
-
subgraph CHAT ["chat.py — chat"]
|
|
60
|
-
FQ --> FW{"Esperou na fila e a resposta<br/>anterior já resolve o pedido?"}
|
|
61
|
-
FW -- "sim (classificador grátis)" --> FS["Pulado, avisa na tela"]
|
|
62
|
-
FW -- não --> D["Monta o contexto: últimas mensagens<br/>da sessão até 48 mil caracteres + idioma"]
|
|
63
|
-
D --> E["Worker em thread dispara o roteador<br/>em grupo de processos próprio"]
|
|
64
|
-
E -. "eventos JSON linha a linha" .-> F["Mostra provedor, comandos e resposta"]
|
|
65
|
-
X["Ctrl-C"] -. "esvazia a fila; SIGTERM no grupo,<br/>após 5 s, SIGKILL" .-> E
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
subgraph ROUTER ["cli.py — roteador"]
|
|
69
|
-
E --> G["Carrega modelos:<br/>env > models.json > padrão"]
|
|
70
|
-
G --> H{"Ferramenta fixada<br/>com /tool?"}
|
|
71
|
-
H -- sim --> H1["Só a ferramenta escolhida"]
|
|
72
|
-
H -- auto --> I{"Classifica: Groq → Cerebras →<br/>OpenRouter grátis → Grok;<br/>sem resposta, palavras-chave"}
|
|
73
|
-
I -- simples --> J["OpenRouter: gratuitos, ou pagos<br/>se autorizados · dados pessoais mascarados"]
|
|
74
|
-
J -- "sem chave ou falhou" --> K["Codex: assinatura,<br/>depois chave de API"]
|
|
75
|
-
K -- falhou --> L["Antigravity: assinatura,<br/>depois chave de API"]
|
|
76
|
-
L -- falhou --> LP["Pagos só se listados em fallback_order:<br/>Cursor, Grok, DeepSeek"]
|
|
77
|
-
LP -- falhou --> M
|
|
78
|
-
I -- complexa --> M["Claude: próxima conta ativa<br/>por prioridade"]
|
|
79
|
-
M --> N{"Rate limit?"}
|
|
80
|
-
N -- sim --> O["Conta em cooldown de 60 min"]
|
|
81
|
-
O --> P{"Outra conta<br/>disponível?"}
|
|
82
|
-
P -- sim --> M
|
|
83
|
-
P -- não --> PK{"Chave de API<br/>da Anthropic?"}
|
|
84
|
-
PK -- sim --> PK1["Claude com ANTHROPIC_API_KEY"]
|
|
85
|
-
PK1 -- ok --> R
|
|
86
|
-
PK1 -- falhou --> Q
|
|
87
|
-
PK -- não --> Q["Último recurso:<br/>Codex, depois Antigravity"]
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
N -- não --> R["Resposta"]
|
|
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[("Sessão salva em disco<br/>com escrita atômica")]
|
|
99
|
-
S --> FQ
|
|
100
|
-
```
|
|
58
|
+

|
|
101
59
|
|
|
102
60
|
Três detalhes que o diagrama torna explícitos:
|
|
103
61
|
|
|
@@ -142,7 +100,7 @@ curl -fsSL https://raw.githubusercontent.com/samuelrms/omni-ai-cli/main/get.sh |
|
|
|
142
100
|
irm https://raw.githubusercontent.com/samuelrms/omni-ai-cli/main/get.ps1 | iex
|
|
143
101
|
```
|
|
144
102
|
|
|
145
|
-
**
|
|
103
|
+
**No npm, yarn, pnpm ou bun:**
|
|
146
104
|
|
|
147
105
|
```bash
|
|
148
106
|
npm install -g omni-ai-cli
|
|
@@ -151,7 +109,7 @@ pnpm add -g omni-ai-cli
|
|
|
151
109
|
bun add -g omni-ai-cli
|
|
152
110
|
```
|
|
153
111
|
|
|
154
|
-
|
|
112
|
+
Qualquer um dos comandos acima (script ou gerenciador de pacotes) instala o mesmo `ai`.
|
|
155
113
|
|
|
156
114
|
Para atualizar depois: `ai --update` (ou `/update` no chat).
|
|
157
115
|
|
|
@@ -429,6 +387,7 @@ ai --deepseek "explicar CAP theorem" # força DeepSeek
|
|
|
429
387
|
ai --install # instala as ferramentas que faltam (pergunta antes)
|
|
430
388
|
ai --classify "prompt" # mostra como seria classificado
|
|
431
389
|
ai --log # últimas chamadas
|
|
390
|
+
ai doctor # diagnóstico de instalação, contas e chaves
|
|
432
391
|
```
|
|
433
392
|
|
|
434
393
|
---
|
|
@@ -490,7 +449,7 @@ python3 -m unittest discover -s tests -v
|
|
|
490
449
|
bash -n scripts/install.sh
|
|
491
450
|
```
|
|
492
451
|
|
|
493
|
-
**
|
|
452
|
+
**106 testes**: persistência e retomada de sessão, limite de contexto, permissão dos
|
|
494
453
|
arquivos, sessão aberta em dois terminais, leitura de eventos, cancelamento da árvore de
|
|
495
454
|
processos, paralelismo real, sandbox somente leitura, mascaramento de dados pessoais,
|
|
496
455
|
fallback OpenRouter → Codex, validação de modelos, armazenamento das chaves e o ciclo de vida das contas. Todos os
|
|
@@ -541,31 +500,20 @@ O `/versions` mostra o título de cada `feat`/`fix` como a "visão simples" e o
|
|
|
541
500
|
- Um commit por mudança perceptível. Refatoração e testes vão em `refactor:`/`test:`,
|
|
542
501
|
que não aparecem em `/versions`.
|
|
543
502
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
B -->|falhou| X[Merge bloqueado]
|
|
549
|
-
C -->|fora do padrão| X
|
|
550
|
-
B -->|ok| D[Squash merge em main / next / beta]
|
|
551
|
-
C -->|ok| D
|
|
552
|
-
D --> E[Release: CI roda de novo]
|
|
553
|
-
E -->|falhou| Y[Nada é publicado]
|
|
554
|
-
E -->|ok| F{semantic-release:<br/>há feat/fix/breaking<br/>desde a última tag?}
|
|
555
|
-
F -->|não| Z[Sem release]
|
|
556
|
-
F -->|sim| G[Calcula versão e cria tag vX.Y.Z]
|
|
557
|
-
G --> H[npm publish com provenance<br/>no dist-tag da branch]
|
|
558
|
-
G --> I[GitHub Release com notas]
|
|
559
|
-
```
|
|
503
|
+

|
|
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:
|
|
560
507
|
|
|
561
|
-
|
|
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*.
|
|
562
514
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
2. **Settings → Branches**: proteger `main` (e `next`/`beta`, se usar) exigindo PR e os
|
|
566
|
-
checks `Tests (...)`, `npm package` e `conventional`.
|
|
567
|
-
3. **Settings → General → Pull Requests**: deixar só *Allow squash merging* e marcar
|
|
568
|
-
*Default to pull request title*.
|
|
515
|
+
O workflow já pede `id-token: write` e `NPM_CONFIG_PROVENANCE`; não há `NPM_TOKEN` para
|
|
516
|
+
guardar nem rotacionar.
|
|
569
517
|
|
|
570
518
|
---
|
|
571
519
|
|
package/docs/README.en.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
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
|
+
|
|
1
5
|
# Omni AI CLI
|
|
2
6
|
|
|
3
|
-
](https://www.npmjs.com/package/omni-ai-cli)
|
|
8
|
+
[](https://www.npmjs.com/package/omni-ai-cli)
|
|
9
|
+
[](https://docs.npmjs.com/trusted-publishers)
|
|
4
10
|
[](../LICENSE)
|
|
5
11
|

|
|
6
12
|

|
|
@@ -49,55 +55,7 @@ From the typed line to the saved answer. The chat (`chat.py`) does not call any
|
|
|
49
55
|
directly: it launches the router (`cli.py`) as a subprocess and reads the events it
|
|
50
56
|
emits, line by line, to show who is answering and what they are doing.
|
|
51
57
|
|
|
52
|
-
|
|
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
|
-
```
|
|
58
|
+

|
|
101
59
|
|
|
102
60
|
Three details the diagram makes explicit:
|
|
103
61
|
|
|
@@ -142,7 +100,7 @@ curl -fsSL https://raw.githubusercontent.com/samuelrms/omni-ai-cli/main/get.sh |
|
|
|
142
100
|
irm https://raw.githubusercontent.com/samuelrms/omni-ai-cli/main/get.ps1 | iex
|
|
143
101
|
```
|
|
144
102
|
|
|
145
|
-
**
|
|
103
|
+
**On npm, yarn, pnpm or bun:**
|
|
146
104
|
|
|
147
105
|
```bash
|
|
148
106
|
npm install -g omni-ai-cli
|
|
@@ -151,7 +109,7 @@ pnpm add -g omni-ai-cli
|
|
|
151
109
|
bun add -g omni-ai-cli
|
|
152
110
|
```
|
|
153
111
|
|
|
154
|
-
|
|
112
|
+
Any of the commands above (script or package manager) installs the same `ai`.
|
|
155
113
|
|
|
156
114
|
To update later: `ai --update` (or `/update` in the chat).
|
|
157
115
|
|
|
@@ -428,6 +386,7 @@ ai --deepseek "explicar CAP theorem" # forces DeepSeek
|
|
|
428
386
|
ai --install # installs the missing tools (asks first)
|
|
429
387
|
ai --classify "prompt" # shows how it would be classified
|
|
430
388
|
ai --log # latest calls
|
|
389
|
+
ai doctor # diagnose install, accounts and keys
|
|
431
390
|
```
|
|
432
391
|
|
|
433
392
|
---
|
|
@@ -489,7 +448,7 @@ python3 -m unittest discover -s tests -v
|
|
|
489
448
|
bash -n scripts/install.sh
|
|
490
449
|
```
|
|
491
450
|
|
|
492
|
-
**
|
|
451
|
+
**106 tests**: session persistence and resume, context limit, file permissions, session
|
|
493
452
|
open in two terminals, event reading, process tree cancellation, real parallelism,
|
|
494
453
|
read-only sandbox, personal data masking, OpenRouter → Codex fallback, model
|
|
495
454
|
validation, key storage and the account lifecycle. All the
|
|
@@ -540,31 +499,20 @@ titles that don't follow the convention.
|
|
|
540
499
|
- One commit per noticeable change. Refactoring and tests go in `refactor:`/`test:`,
|
|
541
500
|
which don't show up in `/versions`.
|
|
542
501
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
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
|
-
```
|
|
502
|
+

|
|
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:
|
|
559
506
|
|
|
560
|
-
|
|
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*.
|
|
561
513
|
|
|
562
|
-
|
|
563
|
-
|
|
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*.
|
|
514
|
+
The workflow already requests `id-token: write` and `NPM_CONFIG_PROVENANCE`; there is no
|
|
515
|
+
`NPM_TOKEN` to store or rotate.
|
|
568
516
|
|
|
569
517
|
---
|
|
570
518
|
|
package/docs/README.es.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
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
|
+
|
|
1
5
|
# Omni AI CLI
|
|
2
6
|
|
|
3
|
-
](https://www.npmjs.com/package/omni-ai-cli)
|
|
8
|
+
[](https://www.npmjs.com/package/omni-ai-cli)
|
|
9
|
+
[](https://docs.npmjs.com/trusted-publishers)
|
|
4
10
|
[](../LICENSE)
|
|
5
11
|

|
|
6
12
|

|
|
@@ -50,55 +56,7 @@ ninguna IA directamente: lanza el enrutador (`cli.py`) como subproceso y lee los
|
|
|
50
56
|
eventos que este emite, línea por línea, para mostrar quién está atendiendo y qué está
|
|
51
57
|
haciendo.
|
|
52
58
|
|
|
53
|
-
|
|
54
|
-
flowchart TD
|
|
55
|
-
A["Escribes y presionas Enter<br/>(la caja nunca se bloquea)"] --> B{"¿Empieza con / ?"}
|
|
56
|
-
B -- sí --> C["Comando interno<br/>/model · /account · /key · /lang · /versions"]
|
|
57
|
-
C --> C1["config.py guarda en<br/>models.json, profiles.json o keys/"]
|
|
58
|
-
B -- no --> FQ["Entra en la cola<br/>con la herramienta del momento del envío"]
|
|
59
|
-
|
|
60
|
-
subgraph CHAT ["chat.py — chat"]
|
|
61
|
-
FQ --> FW{"¿Esperó en la cola y la respuesta<br/>anterior ya resuelve el pedido?"}
|
|
62
|
-
FW -- "sí (clasificador gratis)" --> FS["Omitido, avisa en pantalla"]
|
|
63
|
-
FW -- no --> D["Arma el contexto: últimos mensajes<br/>de la sesión hasta 48 mil caracteres + idioma"]
|
|
64
|
-
D --> E["Worker en hilo lanza el enrutador<br/>en un grupo de procesos propio"]
|
|
65
|
-
E -. "eventos JSON línea por línea" .-> F["Muestra proveedor, comandos y respuesta"]
|
|
66
|
-
X["Ctrl-C"] -. "vacía la cola; SIGTERM al grupo,<br/>tras 5 s, SIGKILL" .-> E
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
subgraph ROUTER ["cli.py — enrutador"]
|
|
70
|
-
E --> G["Carga modelos:<br/>env > models.json > predeterminado"]
|
|
71
|
-
G --> H{"¿Herramienta fijada<br/>con /tool?"}
|
|
72
|
-
H -- sí --> H1["Solo la herramienta elegida"]
|
|
73
|
-
H -- auto --> I{"Clasifica: Groq → Cerebras →<br/>OpenRouter gratis → Grok;<br/>sin respuesta, palabras clave"}
|
|
74
|
-
I -- simple --> J["OpenRouter: gratuitos, o pagos<br/>si se autorizan · datos personales enmascarados"]
|
|
75
|
-
J -- "sin clave o falló" --> K["Codex: suscripción,<br/>luego clave de API"]
|
|
76
|
-
K -- falló --> L["Antigravity: suscripción,<br/>luego clave de API"]
|
|
77
|
-
L -- falló --> LP["De pago solo si están en fallback_order:<br/>Cursor, Grok, DeepSeek"]
|
|
78
|
-
LP -- falló --> M
|
|
79
|
-
I -- compleja --> M["Claude: siguiente cuenta activa<br/>por prioridad"]
|
|
80
|
-
M --> N{"¿Rate limit?"}
|
|
81
|
-
N -- sí --> O["Cuenta en cooldown de 60 min"]
|
|
82
|
-
O --> P{"¿Otra cuenta<br/>disponible?"}
|
|
83
|
-
P -- sí --> M
|
|
84
|
-
P -- no --> PK{"¿Clave de API<br/>de Anthropic?"}
|
|
85
|
-
PK -- sí --> PK1["Claude con ANTHROPIC_API_KEY"]
|
|
86
|
-
PK1 -- ok --> R
|
|
87
|
-
PK1 -- falló --> Q
|
|
88
|
-
PK -- no --> Q["Último recurso:<br/>Codex, luego Antigravity"]
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
N -- no --> R["Respuesta"]
|
|
92
|
-
J -- ok --> R
|
|
93
|
-
K -- ok --> R
|
|
94
|
-
L -- ok --> R
|
|
95
|
-
LP -- ok --> R
|
|
96
|
-
Q --> R
|
|
97
|
-
H1 --> R
|
|
98
|
-
R --> F
|
|
99
|
-
F --> S[("Sesión guardada en disco<br/>con escritura atómica")]
|
|
100
|
-
S --> FQ
|
|
101
|
-
```
|
|
59
|
+

|
|
102
60
|
|
|
103
61
|
Tres detalles que el diagrama deja explícitos:
|
|
104
62
|
|
|
@@ -143,7 +101,7 @@ curl -fsSL https://raw.githubusercontent.com/samuelrms/omni-ai-cli/main/get.sh |
|
|
|
143
101
|
irm https://raw.githubusercontent.com/samuelrms/omni-ai-cli/main/get.ps1 | iex
|
|
144
102
|
```
|
|
145
103
|
|
|
146
|
-
**
|
|
104
|
+
**En npm, yarn, pnpm o bun:**
|
|
147
105
|
|
|
148
106
|
```bash
|
|
149
107
|
npm install -g omni-ai-cli
|
|
@@ -152,7 +110,7 @@ pnpm add -g omni-ai-cli
|
|
|
152
110
|
bun add -g omni-ai-cli
|
|
153
111
|
```
|
|
154
112
|
|
|
155
|
-
|
|
113
|
+
Cualquiera de los comandos de arriba (script o gestor de paquetes) instala el mismo `ai`.
|
|
156
114
|
|
|
157
115
|
Para actualizar después: `ai --update` (o `/update` en el chat).
|
|
158
116
|
|
|
@@ -429,6 +387,7 @@ ai --deepseek "explicar CAP theorem" # fuerza DeepSeek
|
|
|
429
387
|
ai --install # instala las herramientas que faltan (pregunta antes)
|
|
430
388
|
ai --classify "prompt" # muestra cómo se clasificaría
|
|
431
389
|
ai --log # últimas llamadas
|
|
390
|
+
ai doctor # diagnóstico de instalación, cuentas y claves
|
|
432
391
|
```
|
|
433
392
|
|
|
434
393
|
---
|
|
@@ -490,7 +449,7 @@ python3 -m unittest discover -s tests -v
|
|
|
490
449
|
bash -n scripts/install.sh
|
|
491
450
|
```
|
|
492
451
|
|
|
493
|
-
**
|
|
452
|
+
**106 pruebas**: persistencia y reanudación de sesión, límite de contexto, permisos de
|
|
494
453
|
los archivos, sesión abierta en dos terminales, lectura de eventos, cancelación del árbol
|
|
495
454
|
de procesos, paralelismo real, sandbox de solo lectura, enmascaramiento de datos
|
|
496
455
|
personales, fallback OpenRouter → Codex, validación de modelos, almacenamiento de las
|
|
@@ -542,32 +501,21 @@ títulos fuera del estándar.
|
|
|
542
501
|
- Un commit por cambio perceptible. Refactorizaciones y tests van en `refactor:`/`test:`,
|
|
543
502
|
que no aparecen en `/versions`.
|
|
544
503
|
|
|
545
|
-
|
|
546
|
-
flowchart TD
|
|
547
|
-
A[PR abierto] --> B[CI: tests Python 3.11-3.13 en Linux y macOS<br/>+ npm pack + launcher]
|
|
548
|
-
A --> C[PR title: ¿Conventional Commits?]
|
|
549
|
-
B -->|falló| X[Merge bloqueado]
|
|
550
|
-
C -->|inválido| X
|
|
551
|
-
B -->|ok| D[Squash merge en main / next / beta]
|
|
552
|
-
C -->|ok| D
|
|
553
|
-
D --> E[Release: CI corre de nuevo]
|
|
554
|
-
E -->|falló| Y[No se publica nada]
|
|
555
|
-
E -->|ok| F{semantic-release:<br/>¿hay feat/fix/breaking<br/>desde la última tag?}
|
|
556
|
-
F -->|no| Z[Sin release]
|
|
557
|
-
F -->|sí| G[Calcula versión y crea tag vX.Y.Z]
|
|
558
|
-
G --> H[npm publish con provenance<br/>en el dist-tag de la rama]
|
|
559
|
-
G --> I[GitHub Release con notas]
|
|
560
|
-
```
|
|
504
|
+

|
|
561
505
|
|
|
562
|
-
|
|
506
|
+
La publicación usa **OIDC / trusted publishing**: GitHub Actions se autentica en npm sin
|
|
507
|
+
token y el paquete sale con *provenance*. Configuración única:
|
|
563
508
|
|
|
564
|
-
1. **
|
|
565
|
-
|
|
566
|
-
2. **Settings → Branches**: proteger `main` (y `next`/`beta` si se usan) exigiendo
|
|
567
|
-
los checks `Tests (...)`, `npm package` y `conventional`.
|
|
568
|
-
3. **Settings → General → Pull Requests**: permitir solo *squash merging* y marcar
|
|
509
|
+
1. **npm → página del paquete → Settings → Trusted Publisher**: añadir *GitHub Actions* con
|
|
510
|
+
`samuelrms/omni-ai-cli` y el workflow `release.yml`.
|
|
511
|
+
2. **GitHub → Settings → Branches**: proteger `main` (y `next`/`beta` si se usan) exigiendo
|
|
512
|
+
PR y los checks `Tests (...)`, `npm package` y `conventional`.
|
|
513
|
+
3. **GitHub → Settings → General → Pull Requests**: permitir solo *squash merging* y marcar
|
|
569
514
|
*Default to pull request title*.
|
|
570
515
|
|
|
516
|
+
El workflow ya pide `id-token: write` y `NPM_CONFIG_PROVENANCE`; no hay `NPM_TOKEN` que
|
|
517
|
+
guardar ni rotar.
|
|
518
|
+
|
|
571
519
|
---
|
|
572
520
|
|
|
573
521
|
## Troubleshooting
|
package/docs/README.fr.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
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
|
+
|
|
1
5
|
# Omni AI CLI
|
|
2
6
|
|
|
3
|
-
](https://www.npmjs.com/package/omni-ai-cli)
|
|
8
|
+
[](https://www.npmjs.com/package/omni-ai-cli)
|
|
9
|
+
[](https://docs.npmjs.com/trusted-publishers)
|
|
4
10
|
[](../LICENSE)
|
|
5
11
|

|
|
6
12
|

|
|
@@ -50,55 +56,7 @@ De la ligne saisie jusqu'à la réponse sauvegardée. Le chat (`chat.py`) n'appe
|
|
|
50
56
|
aucune IA directement : il lance le routeur (`cli.py`) comme sous-processus et lit
|
|
51
57
|
les événements qu'il émet, ligne par ligne, pour afficher qui répond et ce qu'il fait.
|
|
52
58
|
|
|
53
|
-
|
|
54
|
-
flowchart TD
|
|
55
|
-
A["Vous saisissez et appuyez sur Entrée<br/>(la zone ne se bloque jamais)"] --> B{"Commence par / ?"}
|
|
56
|
-
B -- oui --> C["Commande interne<br/>/model · /account · /key · /lang · /versions"]
|
|
57
|
-
C --> C1["config.py écrit dans<br/>models.json, profiles.json ou keys/"]
|
|
58
|
-
B -- non --> FQ["Entre dans la file<br/>avec l'outil choisi au moment de l'envoi"]
|
|
59
|
-
|
|
60
|
-
subgraph CHAT ["chat.py — chat"]
|
|
61
|
-
FQ --> FW{"A attendu dans la file et la réponse<br/>précédente règle déjà la demande ?"}
|
|
62
|
-
FW -- "oui (classifieur gratuit)" --> FS["Ignorée, signalé à l'écran"]
|
|
63
|
-
FW -- non --> D["Construit le contexte : derniers messages<br/>de la session jusqu'à 48 mille caractères + langue"]
|
|
64
|
-
D --> E["Worker en thread lance le routeur<br/>dans son propre groupe de processus"]
|
|
65
|
-
E -. "événements JSON ligne par ligne" .-> F["Affiche le fournisseur, les commandes et la réponse"]
|
|
66
|
-
X["Ctrl-C"] -. "vide la file ; SIGTERM sur le groupe,<br/>après 5 s, SIGKILL" .-> E
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
subgraph ROUTER ["cli.py — routeur"]
|
|
70
|
-
E --> G["Charge les modèles :<br/>env > models.json > défaut"]
|
|
71
|
-
G --> H{"Outil fixé<br/>avec /tool ?"}
|
|
72
|
-
H -- oui --> H1["Seulement l'outil choisi"]
|
|
73
|
-
H -- auto --> I{"Classe : Groq → Cerebras →<br/>OpenRouter gratuit → Grok ;<br/>sans réponse, mots-clés"}
|
|
74
|
-
I -- simple --> J["OpenRouter : gratuits, ou payants<br/>si autorisés · données personnelles masquées"]
|
|
75
|
-
J -- "sans clé ou échec" --> K["Codex : abonnement,<br/>puis clé d'API"]
|
|
76
|
-
K -- échec --> L["Antigravity : abonnement,<br/>puis clé d'API"]
|
|
77
|
-
L -- échec --> LP["Payants seulement si listés dans fallback_order :<br/>Cursor, Grok, DeepSeek"]
|
|
78
|
-
LP -- échec --> M
|
|
79
|
-
I -- complexe --> M["Claude : prochain compte actif<br/>par priorité"]
|
|
80
|
-
M --> N{"Rate limit ?"}
|
|
81
|
-
N -- oui --> O["Compte en cooldown de 60 min"]
|
|
82
|
-
O --> P{"Un autre compte<br/>disponible ?"}
|
|
83
|
-
P -- oui --> M
|
|
84
|
-
P -- non --> PK{"Clé d'API<br/>Anthropic ?"}
|
|
85
|
-
PK -- oui --> PK1["Claude avec ANTHROPIC_API_KEY"]
|
|
86
|
-
PK1 -- ok --> R
|
|
87
|
-
PK1 -- échec --> Q
|
|
88
|
-
PK -- non --> Q["Dernier recours :<br/>Codex, puis Antigravity"]
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
N -- non --> R["Réponse"]
|
|
92
|
-
J -- ok --> R
|
|
93
|
-
K -- ok --> R
|
|
94
|
-
L -- ok --> R
|
|
95
|
-
LP -- ok --> R
|
|
96
|
-
Q --> R
|
|
97
|
-
H1 --> R
|
|
98
|
-
R --> F
|
|
99
|
-
F --> S[("Session sauvegardée sur disque<br/>avec écriture atomique")]
|
|
100
|
-
S --> FQ
|
|
101
|
-
```
|
|
59
|
+

|
|
102
60
|
|
|
103
61
|
Trois détails que le diagramme rend explicites :
|
|
104
62
|
|
|
@@ -145,7 +103,7 @@ curl -fsSL https://raw.githubusercontent.com/samuelrms/omni-ai-cli/main/get.sh |
|
|
|
145
103
|
irm https://raw.githubusercontent.com/samuelrms/omni-ai-cli/main/get.ps1 | iex
|
|
146
104
|
```
|
|
147
105
|
|
|
148
|
-
**
|
|
106
|
+
**Sur npm, yarn, pnpm ou bun :**
|
|
149
107
|
|
|
150
108
|
```bash
|
|
151
109
|
npm install -g omni-ai-cli
|
|
@@ -154,7 +112,7 @@ pnpm add -g omni-ai-cli
|
|
|
154
112
|
bun add -g omni-ai-cli
|
|
155
113
|
```
|
|
156
114
|
|
|
157
|
-
|
|
115
|
+
N'importe laquelle des commandes ci-dessus (script ou gestionnaire de paquets) installe le même `ai`.
|
|
158
116
|
|
|
159
117
|
Pour mettre à jour ensuite : `ai --update` (ou `/update` dans le chat).
|
|
160
118
|
|
|
@@ -438,6 +396,7 @@ ai --deepseek "explicar CAP theorem" # force DeepSeek
|
|
|
438
396
|
ai --install # installe les outils manquants (demande avant)
|
|
439
397
|
ai --classify "prompt" # montre la classification
|
|
440
398
|
ai --log # derniers appels
|
|
399
|
+
ai doctor # diagnostic installation, comptes et clés
|
|
441
400
|
```
|
|
442
401
|
|
|
443
402
|
---
|
|
@@ -499,7 +458,7 @@ python3 -m unittest discover -s tests -v
|
|
|
499
458
|
bash -n scripts/install.sh
|
|
500
459
|
```
|
|
501
460
|
|
|
502
|
-
**
|
|
461
|
+
**106 tests** : persistance et reprise de session, limite de contexte, permissions des
|
|
503
462
|
fichiers, session ouverte dans deux terminaux, lecture des événements, annulation de
|
|
504
463
|
l'arbre de processus, parallélisme réel, sandbox en lecture seule, masquage des données
|
|
505
464
|
personnelles, fallback OpenRouter → Codex, validation des modèles, stockage des clés et
|
|
@@ -551,31 +510,20 @@ bloque les titres hors convention.
|
|
|
551
510
|
- Un commit par changement perceptible. Refactorisations et tests vont dans
|
|
552
511
|
`refactor:`/`test:`, qui n'apparaissent pas dans `/versions`.
|
|
553
512
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
B -->|échec| X[Merge bloqué]
|
|
559
|
-
C -->|invalide| X
|
|
560
|
-
B -->|ok| D[Squash merge dans main / next / beta]
|
|
561
|
-
C -->|ok| D
|
|
562
|
-
D --> E[Release : la CI tourne à nouveau]
|
|
563
|
-
E -->|échec| Y[Rien n'est publié]
|
|
564
|
-
E -->|ok| F{semantic-release :<br/>feat/fix/breaking<br/>depuis le dernier tag ?}
|
|
565
|
-
F -->|non| Z[Pas de release]
|
|
566
|
-
F -->|oui| G[Calcule la version et crée le tag vX.Y.Z]
|
|
567
|
-
G --> H[npm publish avec provenance<br/>sur le dist-tag de la branche]
|
|
568
|
-
G --> I[GitHub Release avec notes]
|
|
569
|
-
```
|
|
513
|
+

|
|
514
|
+
|
|
515
|
+
La publication utilise **OIDC / trusted publishing** : GitHub Actions s'authentifie auprès
|
|
516
|
+
de npm sans jeton et le paquet est publié avec *provenance*. Configuration unique :
|
|
570
517
|
|
|
571
|
-
|
|
518
|
+
1. **npm → page du paquet → Settings → Trusted Publisher** : ajouter *GitHub Actions* avec
|
|
519
|
+
`samuelrms/omni-ai-cli` et le workflow `release.yml`.
|
|
520
|
+
2. **GitHub → Settings → Branches** : protéger `main` (et `next`/`beta` si utilisées) en
|
|
521
|
+
exigeant une PR et les checks `Tests (...)`, `npm package` et `conventional`.
|
|
522
|
+
3. **GitHub → Settings → General → Pull Requests** : n'autoriser que le *squash merging* et
|
|
523
|
+
cocher *Default to pull request title*.
|
|
572
524
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
2. **Settings → Branches** : protéger `main` (et `next`/`beta` si utilisées) en exigeant
|
|
576
|
-
une PR et les checks `Tests (...)`, `npm package` et `conventional`.
|
|
577
|
-
3. **Settings → General → Pull Requests** : n'autoriser que le *squash merging* et cocher
|
|
578
|
-
*Default to pull request title*.
|
|
525
|
+
Le workflow demande déjà `id-token: write` et `NPM_CONFIG_PROVENANCE` ; il n'y a pas de
|
|
526
|
+
`NPM_TOKEN` à stocker ni à renouveler.
|
|
579
527
|
|
|
580
528
|
---
|
|
581
529
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
flowchart TD
|
|
2
|
+
A["You type and press Enter<br/>(the box never locks)"] --> B{"Starts with / ?"}
|
|
3
|
+
B -- yes --> C["Internal command<br/>/model · /account · /key · /lang · /versions"]
|
|
4
|
+
C --> C1["config.py writes to<br/>models.json, profiles.json or keys/"]
|
|
5
|
+
B -- no --> FQ["Goes into the queue<br/>with the tool selected at send time"]
|
|
6
|
+
|
|
7
|
+
subgraph CHAT ["chat.py — chat"]
|
|
8
|
+
FQ --> FW{"Waited in the queue and the previous<br/>answer already covers the request?"}
|
|
9
|
+
FW -- "yes (free classifier)" --> FS["Skipped, shown on screen"]
|
|
10
|
+
FW -- no --> D["Builds the context: latest messages<br/>of the session up to 48 thousand characters + language"]
|
|
11
|
+
D --> E["Thread worker launches the router<br/>in its own process group"]
|
|
12
|
+
E -. "JSON events line by line" .-> F["Shows provider, commands and answer"]
|
|
13
|
+
X["Ctrl-C"] -. "empties the queue; SIGTERM to the group,<br/>after 5 s, SIGKILL" .-> E
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
subgraph ROUTER ["cli.py — router"]
|
|
17
|
+
E --> G["Loads models:<br/>env > models.json > default"]
|
|
18
|
+
G --> H{"Tool pinned<br/>with /tool?"}
|
|
19
|
+
H -- yes --> H1["Only the chosen tool"]
|
|
20
|
+
H -- auto --> I{"Classifies: Groq → Cerebras →<br/>free OpenRouter → Grok;<br/>no answer, keywords"}
|
|
21
|
+
I -- simple --> J["OpenRouter: free, or paid<br/>if authorized · personal data masked"]
|
|
22
|
+
J -- "no key or failed" --> K["Codex: subscription,<br/>then API key"]
|
|
23
|
+
K -- failed --> L["Antigravity: subscription,<br/>then API key"]
|
|
24
|
+
L -- failed --> LP["Paid ones only if listed in fallback_order:<br/>Cursor, Grok, DeepSeek"]
|
|
25
|
+
LP -- failed --> M
|
|
26
|
+
I -- complex --> M["Claude: next active account<br/>by priority"]
|
|
27
|
+
M --> N{"Rate limit?"}
|
|
28
|
+
N -- yes --> O["Account on 60 min cooldown"]
|
|
29
|
+
O --> P{"Another account<br/>available?"}
|
|
30
|
+
P -- yes --> M
|
|
31
|
+
P -- no --> PK{"Anthropic<br/>API key?"}
|
|
32
|
+
PK -- yes --> PK1["Claude with ANTHROPIC_API_KEY"]
|
|
33
|
+
PK1 -- ok --> R
|
|
34
|
+
PK1 -- failed --> Q
|
|
35
|
+
PK -- no --> Q["Last resort:<br/>Codex, then Antigravity"]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
N -- no --> R["Answer"]
|
|
39
|
+
J -- ok --> R
|
|
40
|
+
K -- ok --> R
|
|
41
|
+
L -- ok --> R
|
|
42
|
+
LP -- ok --> R
|
|
43
|
+
Q --> R
|
|
44
|
+
H1 --> R
|
|
45
|
+
R --> F
|
|
46
|
+
F --> S[("Session saved to disk<br/>with atomic write")]
|
|
47
|
+
S --> FQ
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
flowchart TD
|
|
2
|
+
A["Escribes y presionas Enter<br/>(la caja nunca se bloquea)"] --> B{"¿Empieza con / ?"}
|
|
3
|
+
B -- sí --> C["Comando interno<br/>/model · /account · /key · /lang · /versions"]
|
|
4
|
+
C --> C1["config.py guarda en<br/>models.json, profiles.json o keys/"]
|
|
5
|
+
B -- no --> FQ["Entra en la cola<br/>con la herramienta del momento del envío"]
|
|
6
|
+
|
|
7
|
+
subgraph CHAT ["chat.py — chat"]
|
|
8
|
+
FQ --> FW{"¿Esperó en la cola y la respuesta<br/>anterior ya resuelve el pedido?"}
|
|
9
|
+
FW -- "sí (clasificador gratis)" --> FS["Omitido, avisa en pantalla"]
|
|
10
|
+
FW -- no --> D["Arma el contexto: últimos mensajes<br/>de la sesión hasta 48 mil caracteres + idioma"]
|
|
11
|
+
D --> E["Worker en hilo lanza el enrutador<br/>en un grupo de procesos propio"]
|
|
12
|
+
E -. "eventos JSON línea por línea" .-> F["Muestra proveedor, comandos y respuesta"]
|
|
13
|
+
X["Ctrl-C"] -. "vacía la cola; SIGTERM al grupo,<br/>tras 5 s, SIGKILL" .-> E
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
subgraph ROUTER ["cli.py — enrutador"]
|
|
17
|
+
E --> G["Carga modelos:<br/>env > models.json > predeterminado"]
|
|
18
|
+
G --> H{"¿Herramienta fijada<br/>con /tool?"}
|
|
19
|
+
H -- sí --> H1["Solo la herramienta elegida"]
|
|
20
|
+
H -- auto --> I{"Clasifica: Groq → Cerebras →<br/>OpenRouter gratis → Grok;<br/>sin respuesta, palabras clave"}
|
|
21
|
+
I -- simple --> J["OpenRouter: gratuitos, o pagos<br/>si se autorizan · datos personales enmascarados"]
|
|
22
|
+
J -- "sin clave o falló" --> K["Codex: suscripción,<br/>luego clave de API"]
|
|
23
|
+
K -- falló --> L["Antigravity: suscripción,<br/>luego clave de API"]
|
|
24
|
+
L -- falló --> LP["De pago solo si están en fallback_order:<br/>Cursor, Grok, DeepSeek"]
|
|
25
|
+
LP -- falló --> M
|
|
26
|
+
I -- compleja --> M["Claude: siguiente cuenta activa<br/>por prioridad"]
|
|
27
|
+
M --> N{"¿Rate limit?"}
|
|
28
|
+
N -- sí --> O["Cuenta en cooldown de 60 min"]
|
|
29
|
+
O --> P{"¿Otra cuenta<br/>disponible?"}
|
|
30
|
+
P -- sí --> M
|
|
31
|
+
P -- no --> PK{"¿Clave de API<br/>de Anthropic?"}
|
|
32
|
+
PK -- sí --> PK1["Claude con ANTHROPIC_API_KEY"]
|
|
33
|
+
PK1 -- ok --> R
|
|
34
|
+
PK1 -- falló --> Q
|
|
35
|
+
PK -- no --> Q["Último recurso:<br/>Codex, luego Antigravity"]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
N -- no --> R["Respuesta"]
|
|
39
|
+
J -- ok --> R
|
|
40
|
+
K -- ok --> R
|
|
41
|
+
L -- ok --> R
|
|
42
|
+
LP -- ok --> R
|
|
43
|
+
Q --> R
|
|
44
|
+
H1 --> R
|
|
45
|
+
R --> F
|
|
46
|
+
F --> S[("Sesión guardada en disco<br/>con escritura atómica")]
|
|
47
|
+
S --> FQ
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
flowchart TD
|
|
2
|
+
A["Vous saisissez et appuyez sur Entrée<br/>(la zone ne se bloque jamais)"] --> B{"Commence par / ?"}
|
|
3
|
+
B -- oui --> C["Commande interne<br/>/model · /account · /key · /lang · /versions"]
|
|
4
|
+
C --> C1["config.py écrit dans<br/>models.json, profiles.json ou keys/"]
|
|
5
|
+
B -- non --> FQ["Entre dans la file<br/>avec l'outil choisi au moment de l'envoi"]
|
|
6
|
+
|
|
7
|
+
subgraph CHAT ["chat.py — chat"]
|
|
8
|
+
FQ --> FW{"A attendu dans la file et la réponse<br/>précédente règle déjà la demande ?"}
|
|
9
|
+
FW -- "oui (classifieur gratuit)" --> FS["Ignorée, signalé à l'écran"]
|
|
10
|
+
FW -- non --> D["Construit le contexte : derniers messages<br/>de la session jusqu'à 48 mille caractères + langue"]
|
|
11
|
+
D --> E["Worker en thread lance le routeur<br/>dans son propre groupe de processus"]
|
|
12
|
+
E -. "événements JSON ligne par ligne" .-> F["Affiche le fournisseur, les commandes et la réponse"]
|
|
13
|
+
X["Ctrl-C"] -. "vide la file ; SIGTERM sur le groupe,<br/>après 5 s, SIGKILL" .-> E
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
subgraph ROUTER ["cli.py — routeur"]
|
|
17
|
+
E --> G["Charge les modèles :<br/>env > models.json > défaut"]
|
|
18
|
+
G --> H{"Outil fixé<br/>avec /tool ?"}
|
|
19
|
+
H -- oui --> H1["Seulement l'outil choisi"]
|
|
20
|
+
H -- auto --> I{"Classe : Groq → Cerebras →<br/>OpenRouter gratuit → Grok ;<br/>sans réponse, mots-clés"}
|
|
21
|
+
I -- simple --> J["OpenRouter : gratuits, ou payants<br/>si autorisés · données personnelles masquées"]
|
|
22
|
+
J -- "sans clé ou échec" --> K["Codex : abonnement,<br/>puis clé d'API"]
|
|
23
|
+
K -- échec --> L["Antigravity : abonnement,<br/>puis clé d'API"]
|
|
24
|
+
L -- échec --> LP["Payants seulement si listés dans fallback_order :<br/>Cursor, Grok, DeepSeek"]
|
|
25
|
+
LP -- échec --> M
|
|
26
|
+
I -- complexe --> M["Claude : prochain compte actif<br/>par priorité"]
|
|
27
|
+
M --> N{"Rate limit ?"}
|
|
28
|
+
N -- oui --> O["Compte en cooldown de 60 min"]
|
|
29
|
+
O --> P{"Un autre compte<br/>disponible ?"}
|
|
30
|
+
P -- oui --> M
|
|
31
|
+
P -- non --> PK{"Clé d'API<br/>Anthropic ?"}
|
|
32
|
+
PK -- oui --> PK1["Claude avec ANTHROPIC_API_KEY"]
|
|
33
|
+
PK1 -- ok --> R
|
|
34
|
+
PK1 -- échec --> Q
|
|
35
|
+
PK -- non --> Q["Dernier recours :<br/>Codex, puis Antigravity"]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
N -- non --> R["Réponse"]
|
|
39
|
+
J -- ok --> R
|
|
40
|
+
K -- ok --> R
|
|
41
|
+
L -- ok --> R
|
|
42
|
+
LP -- ok --> R
|
|
43
|
+
Q --> R
|
|
44
|
+
H1 --> R
|
|
45
|
+
R --> F
|
|
46
|
+
F --> S[("Session sauvegardée sur disque<br/>avec écriture atomique")]
|
|
47
|
+
S --> FQ
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
flowchart TD
|
|
2
|
+
A["Você digita e tecla Enter<br/>(a caixa nunca trava)"] --> B{"Começa com / ?"}
|
|
3
|
+
B -- sim --> C["Comando interno<br/>/model · /account · /key · /lang · /versions"]
|
|
4
|
+
C --> C1["config.py grava em<br/>models.json, profiles.json ou keys/"]
|
|
5
|
+
B -- não --> FQ["Entra na fila<br/>com a ferramenta do momento do envio"]
|
|
6
|
+
|
|
7
|
+
subgraph CHAT ["chat.py — chat"]
|
|
8
|
+
FQ --> FW{"Esperou na fila e a resposta<br/>anterior já resolve o pedido?"}
|
|
9
|
+
FW -- "sim (classificador grátis)" --> FS["Pulado, avisa na tela"]
|
|
10
|
+
FW -- não --> D["Monta o contexto: últimas mensagens<br/>da sessão até 48 mil caracteres + idioma"]
|
|
11
|
+
D --> E["Worker em thread dispara o roteador<br/>em grupo de processos próprio"]
|
|
12
|
+
E -. "eventos JSON linha a linha" .-> F["Mostra provedor, comandos e resposta"]
|
|
13
|
+
X["Ctrl-C"] -. "esvazia a fila; SIGTERM no grupo,<br/>após 5 s, SIGKILL" .-> E
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
subgraph ROUTER ["cli.py — roteador"]
|
|
17
|
+
E --> G["Carrega modelos:<br/>env > models.json > padrão"]
|
|
18
|
+
G --> H{"Ferramenta fixada<br/>com /tool?"}
|
|
19
|
+
H -- sim --> H1["Só a ferramenta escolhida"]
|
|
20
|
+
H -- auto --> I{"Classifica: Groq → Cerebras →<br/>OpenRouter grátis → Grok;<br/>sem resposta, palavras-chave"}
|
|
21
|
+
I -- simples --> J["OpenRouter: gratuitos, ou pagos<br/>se autorizados · dados pessoais mascarados"]
|
|
22
|
+
J -- "sem chave ou falhou" --> K["Codex: assinatura,<br/>depois chave de API"]
|
|
23
|
+
K -- falhou --> L["Antigravity: assinatura,<br/>depois chave de API"]
|
|
24
|
+
L -- falhou --> LP["Pagos só se listados em fallback_order:<br/>Cursor, Grok, DeepSeek"]
|
|
25
|
+
LP -- falhou --> M
|
|
26
|
+
I -- complexa --> M["Claude: próxima conta ativa<br/>por prioridade"]
|
|
27
|
+
M --> N{"Rate limit?"}
|
|
28
|
+
N -- sim --> O["Conta em cooldown de 60 min"]
|
|
29
|
+
O --> P{"Outra conta<br/>disponível?"}
|
|
30
|
+
P -- sim --> M
|
|
31
|
+
P -- não --> PK{"Chave de API<br/>da Anthropic?"}
|
|
32
|
+
PK -- sim --> PK1["Claude com ANTHROPIC_API_KEY"]
|
|
33
|
+
PK1 -- ok --> R
|
|
34
|
+
PK1 -- falhou --> Q
|
|
35
|
+
PK -- não --> Q["Último recurso:<br/>Codex, depois Antigravity"]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
N -- não --> R["Resposta"]
|
|
39
|
+
J -- ok --> R
|
|
40
|
+
K -- ok --> R
|
|
41
|
+
L -- ok --> R
|
|
42
|
+
LP -- ok --> R
|
|
43
|
+
Q --> R
|
|
44
|
+
H1 --> R
|
|
45
|
+
R --> F
|
|
46
|
+
F --> S[("Sessão salva em disco<br/>com escrita atômica")]
|
|
47
|
+
S --> FQ
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
flowchart TD
|
|
2
|
+
A[PR opened] --> B[CI: Python 3.11-3.13 tests on Linux and macOS<br/>+ npm pack + launcher]
|
|
3
|
+
A --> C[PR title: Conventional Commits?]
|
|
4
|
+
B -->|failed| X[Merge blocked]
|
|
5
|
+
C -->|invalid| X
|
|
6
|
+
B -->|ok| D[Squash merge into main / next / beta]
|
|
7
|
+
C -->|ok| D
|
|
8
|
+
D --> E[Release: CI runs again]
|
|
9
|
+
E -->|failed| Y[Nothing is published]
|
|
10
|
+
E -->|ok| F{semantic-release:<br/>any feat/fix/breaking<br/>since last tag?}
|
|
11
|
+
F -->|no| Z[No release]
|
|
12
|
+
F -->|yes| G[Compute version and tag vX.Y.Z]
|
|
13
|
+
G --> H[npm publish via OIDC / trusted publishing<br/>with provenance, on the branch dist-tag]
|
|
14
|
+
G --> I[GitHub Release with notes]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
flowchart TD
|
|
2
|
+
A[PR abierto] --> B[CI: tests Python 3.11-3.13 en Linux y macOS<br/>+ npm pack + launcher]
|
|
3
|
+
A --> C[PR title: ¿Conventional Commits?]
|
|
4
|
+
B -->|falló| X[Merge bloqueado]
|
|
5
|
+
C -->|inválido| X
|
|
6
|
+
B -->|ok| D[Squash merge en main / next / beta]
|
|
7
|
+
C -->|ok| D
|
|
8
|
+
D --> E[Release: CI corre de nuevo]
|
|
9
|
+
E -->|falló| Y[No se publica nada]
|
|
10
|
+
E -->|ok| F{semantic-release:<br/>¿hay feat/fix/breaking<br/>desde la última tag?}
|
|
11
|
+
F -->|no| Z[Sin release]
|
|
12
|
+
F -->|sí| G[Calcula versión y crea tag vX.Y.Z]
|
|
13
|
+
G --> H[npm publish vía OIDC / trusted publishing<br/>con provenance, en el dist-tag de la rama]
|
|
14
|
+
G --> I[GitHub Release con notas]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
flowchart TD
|
|
2
|
+
A[PR ouverte] --> B[CI : tests Python 3.11-3.13 sur Linux et macOS<br/>+ npm pack + launcher]
|
|
3
|
+
A --> C[PR title : Conventional Commits ?]
|
|
4
|
+
B -->|échec| X[Merge bloqué]
|
|
5
|
+
C -->|invalide| X
|
|
6
|
+
B -->|ok| D[Squash merge dans main / next / beta]
|
|
7
|
+
C -->|ok| D
|
|
8
|
+
D --> E[Release : la CI tourne à nouveau]
|
|
9
|
+
E -->|échec| Y[Rien n'est publié]
|
|
10
|
+
E -->|ok| F{semantic-release :<br/>feat/fix/breaking<br/>depuis le dernier tag ?}
|
|
11
|
+
F -->|non| Z[Pas de release]
|
|
12
|
+
F -->|oui| G[Calcule la version et crée le tag vX.Y.Z]
|
|
13
|
+
G --> H[npm publish via OIDC / trusted publishing<br/>avec provenance, sur le dist-tag de la branche]
|
|
14
|
+
G --> I[GitHub Release avec notes]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
flowchart TD
|
|
2
|
+
A[PR aberto] --> B[CI: testes Python 3.11-3.13 em Linux e macOS<br/>+ npm pack + launcher]
|
|
3
|
+
A --> C[PR title: Conventional Commits?]
|
|
4
|
+
B -->|falhou| X[Merge bloqueado]
|
|
5
|
+
C -->|fora do padrão| X
|
|
6
|
+
B -->|ok| D[Squash merge em main / next / beta]
|
|
7
|
+
C -->|ok| D
|
|
8
|
+
D --> E[Release: CI roda de novo]
|
|
9
|
+
E -->|falhou| Y[Nada é publicado]
|
|
10
|
+
E -->|ok| F{semantic-release:<br/>há feat/fix/breaking<br/>desde a última tag?}
|
|
11
|
+
F -->|não| Z[Sem release]
|
|
12
|
+
F -->|sim| G[Calcula versão e cria tag vX.Y.Z]
|
|
13
|
+
G --> H[npm publish via OIDC / trusted publishing<br/>com provenance, no dist-tag da branch]
|
|
14
|
+
G --> I[GitHub Release com notas]
|
package/omni_ai/cli.py
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
ai --mode plan|edits|auto|yolo "question"
|
|
8
8
|
ai --usage Uso e limites de todas as ferramentas e contas
|
|
9
9
|
ai --setup First-run guide: logins and API keys
|
|
10
|
-
ai --status | --reset | --log | --classify "prompt"
|
|
10
|
+
ai --status | --doctor | --reset | --log | --classify "prompt"
|
|
11
11
|
ai --model ... | --account ... | --key ... | --sync ...
|
|
12
12
|
"""
|
|
13
13
|
import json
|
|
@@ -683,6 +683,91 @@ def show_status():
|
|
|
683
683
|
say()
|
|
684
684
|
|
|
685
685
|
|
|
686
|
+
def ai_version():
|
|
687
|
+
try:
|
|
688
|
+
return json.loads((REPO / 'package.json').read_text()).get('version', '?')
|
|
689
|
+
except (OSError, ValueError):
|
|
690
|
+
return '?'
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
def doctor():
|
|
694
|
+
"""Health check: what is installed, logged in and configured, and what is missing. Returns 1 when
|
|
695
|
+
nothing can answer a request (no Claude login and no API key), 0 otherwise."""
|
|
696
|
+
import platform
|
|
697
|
+
ok, warn, bad = f'{GREEN}✓{NC}', f'{YELLOW}!{NC}', f'{RED}✗{NC}'
|
|
698
|
+
problems = []
|
|
699
|
+
|
|
700
|
+
say(f'{BOLD}{CYAN} 🩺 Omni AI CLI — diagnóstico{NC}\n')
|
|
701
|
+
|
|
702
|
+
say(f'{BOLD} Sistema{NC}')
|
|
703
|
+
say(f' {ok} Omni AI CLI {ai_version()}')
|
|
704
|
+
say(f' {ok} Python {platform.python_version()} · {platform.system()} {platform.machine()}')
|
|
705
|
+
local_bin = str(Path('~/.local/bin').expanduser())
|
|
706
|
+
if local_bin in os.environ.get('PATH', '').split(os.pathsep):
|
|
707
|
+
say(f' {ok} ~/.local/bin no PATH')
|
|
708
|
+
else:
|
|
709
|
+
say(f' {warn} ~/.local/bin fora do PATH (adicione ao shell para o comando `ai`)')
|
|
710
|
+
problems.append('~/.local/bin não está no PATH')
|
|
711
|
+
say()
|
|
712
|
+
|
|
713
|
+
say(f'{BOLD} Configuração{NC}')
|
|
714
|
+
cfg = config_tool.CONFIG_DIR
|
|
715
|
+
say(f' {ok if cfg.is_dir() else warn} {cfg}')
|
|
716
|
+
say()
|
|
717
|
+
|
|
718
|
+
say(f'{BOLD} Contas Claude{NC}')
|
|
719
|
+
logged = 0
|
|
720
|
+
profiles = config_tool.ordered(config_tool.profiles())
|
|
721
|
+
for name, conf in profiles:
|
|
722
|
+
if not conf.get('enabled', True):
|
|
723
|
+
say(f' {warn} {name}: desabilitado')
|
|
724
|
+
continue
|
|
725
|
+
has_login = bool(conf.get('config_dir')) or (
|
|
726
|
+
conf.get('credentials_file') and Path(conf['credentials_file']).expanduser().is_file())
|
|
727
|
+
left = cooldown_left(name)
|
|
728
|
+
if has_login and left:
|
|
729
|
+
say(f' {warn} {name}: em cooldown ({left // 60}m{left % 60}s)')
|
|
730
|
+
logged += 1
|
|
731
|
+
elif has_login:
|
|
732
|
+
say(f' {ok} {name}: logado')
|
|
733
|
+
logged += 1
|
|
734
|
+
else:
|
|
735
|
+
say(f' {bad} {name}: sem login (ai --account login {name})')
|
|
736
|
+
if not logged:
|
|
737
|
+
say(f' {warn} nenhuma conta Claude logada')
|
|
738
|
+
say()
|
|
739
|
+
|
|
740
|
+
say(f'{BOLD} Chaves de API{NC}')
|
|
741
|
+
keys = [p for p in config_tool.KEYS if config_tool.stored_key(p)[0]]
|
|
742
|
+
say(f" {ok if keys else warn} {', '.join(keys) if keys else 'nenhuma configurada (ai --key)'}")
|
|
743
|
+
say()
|
|
744
|
+
|
|
745
|
+
say(f'{BOLD} Ferramentas{NC}')
|
|
746
|
+
for name in CLIS:
|
|
747
|
+
label = TOOLS[name].label
|
|
748
|
+
say(f' {ok} {label}: {version(name)}' if installed(name) else f' {warn} {label}: não instalado')
|
|
749
|
+
say()
|
|
750
|
+
|
|
751
|
+
classifiers = [p for p in ('groq', 'cerebras') if config_tool.stored_key(p)[0]]
|
|
752
|
+
if classifiers:
|
|
753
|
+
say(f' {ok} Classificador: {", ".join(classifiers)} (rápido e grátis)')
|
|
754
|
+
else:
|
|
755
|
+
say(f' {warn} Sem chave de classificador (Groq/Cerebras); usa OpenRouter grátis ou palavras-chave')
|
|
756
|
+
say()
|
|
757
|
+
|
|
758
|
+
if not logged and not keys:
|
|
759
|
+
problems.append('nenhuma conta Claude logada e nenhuma chave de API: o roteador não tem como responder')
|
|
760
|
+
|
|
761
|
+
if problems:
|
|
762
|
+
say(f'{BOLD}{YELLOW} Pendências:{NC}')
|
|
763
|
+
for p in problems:
|
|
764
|
+
say(f' {warn} {p}')
|
|
765
|
+
say(f'\n{DIM} Rode `ai --setup` para um guia de primeiro acesso.{NC}\n')
|
|
766
|
+
else:
|
|
767
|
+
say(f'{GREEN} Tudo pronto.{NC}\n')
|
|
768
|
+
return 1 if not logged and not keys else 0
|
|
769
|
+
|
|
770
|
+
|
|
686
771
|
def ago(seconds):
|
|
687
772
|
seconds = int(seconds)
|
|
688
773
|
return f'{seconds // 3600}h{seconds % 3600 // 60:02d}m' if seconds >= 3600 else f'{seconds // 60}m'
|
|
@@ -867,7 +952,12 @@ def show_log():
|
|
|
867
952
|
def update():
|
|
868
953
|
"""Pull the latest version into the install folder; settings live in ~/.config and are kept."""
|
|
869
954
|
if (REPO / '.git').is_dir() and resolve_bin('git'):
|
|
870
|
-
|
|
955
|
+
# Pull the current branch from origin explicitly, so it works even when the branch has no
|
|
956
|
+
# upstream tracking set (a plain `git pull` fails there). A detached HEAD updates from main.
|
|
957
|
+
branch = subprocess.run(['git', '-C', str(REPO), 'rev-parse', '--abbrev-ref', 'HEAD'],
|
|
958
|
+
capture_output=True, text=True).stdout.strip()
|
|
959
|
+
ref = branch if branch and branch != 'HEAD' else 'main'
|
|
960
|
+
code = subprocess.call(['git', '-C', str(REPO), 'pull', '--ff-only', 'origin', ref])
|
|
871
961
|
say(f'{GREEN}✓ Omni AI atualizado.{NC}' if code == 0 else f'{RED}✗ Não consegui atualizar; veja a mensagem acima.{NC}')
|
|
872
962
|
return code
|
|
873
963
|
if os.environ.get('SMART_AI_INSTALLER') == 'npm':
|
|
@@ -895,7 +985,7 @@ USAGE = f'''{BOLD}Uso:{NC}
|
|
|
895
985
|
ai --usage Uso e limites de todas as ferramentas e contas
|
|
896
986
|
ai --setup Guia de primeiro acesso: logins e chaves
|
|
897
987
|
ai --install Instala as ferramentas que faltam (pergunta antes)
|
|
898
|
-
ai --status | --reset | --log | --classify "prompt"
|
|
988
|
+
ai --status | --doctor | --reset | --log | --classify "prompt"
|
|
899
989
|
ai --update Atualizar para a versão mais nova
|
|
900
990
|
'''
|
|
901
991
|
|
|
@@ -945,6 +1035,8 @@ def main(argv):
|
|
|
945
1035
|
elif arg in ('--status', '-s'):
|
|
946
1036
|
show_status()
|
|
947
1037
|
return 0
|
|
1038
|
+
elif arg in ('--doctor', 'doctor'):
|
|
1039
|
+
return doctor()
|
|
948
1040
|
elif arg in ('--setup', '--install'):
|
|
949
1041
|
from . import onboarding
|
|
950
1042
|
onboarding.main(install_missing=arg == '--install' or 'install' in argv[i + 1:])
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omni-ai-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "One chat for Claude Code, Codex, Antigravity and OpenRouter, with automatic routing, account fallback, API keys, permission modes and MCP/skill sync.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ai": "bin/ai.js",
|
|
@@ -12,9 +12,13 @@
|
|
|
12
12
|
"omni_ai/",
|
|
13
13
|
"!omni_ai/**/__pycache__",
|
|
14
14
|
"docs/",
|
|
15
|
+
"!docs/**/*.png",
|
|
15
16
|
"README.md",
|
|
16
17
|
"LICENSE"
|
|
17
18
|
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"diagrams": "bash scripts/diagrams.sh"
|
|
21
|
+
},
|
|
18
22
|
"optionalDependencies": {
|
|
19
23
|
"@anthropic-ai/claude-code": "*"
|
|
20
24
|
},
|