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 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
- ![npm](https://img.shields.io/badge/npm-em%20breve-1A1714)
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)
4
10
  [![License: MIT](https://img.shields.io/badge/license-MIT-C4552F.svg)](LICENSE)
5
11
  ![Plataformas](https://img.shields.io/badge/macOS%20·%20Linux%20·%20Windows-1A1714)
6
12
  ![Idiomas](https://img.shields.io/badge/pt--BR%20·%20en%20·%20es%20·%20fr-C4552F)
@@ -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
- ```mermaid
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
+ ![Fluxo do pedido até a resposta](https://raw.githubusercontent.com/samuelrms/omni-ai-cli/main/docs/diagrams/flow1.pt.png)
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
- **Em breve no npm, yarn, pnpm e bun.** O pacote `omni-ai-cli` está pronto e sai assim que a publicação for liberada:
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
- Por enquanto, use o comando de macOS/Linux ou Windows acima.
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
- **38 testes**: persistência e retomada de sessão, limite de contexto, permissão dos
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
- ```mermaid
545
- flowchart TD
546
- A[PR aberto] --> B[CI: testes Python 3.11-3.13 em Linux e macOS<br/>+ npm pack + launcher]
547
- A --> C[PR title: Conventional Commits?]
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
+ ![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:
560
507
 
561
- Configuração única no GitHub:
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
- 1. **Settings → Secrets and variables → Actions**: criar `NPM_TOKEN` (token *Automation*
564
- ou *Granular* com permissão de publish no npmjs.com).
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
- ![npm](https://img.shields.io/badge/npm-coming%20soon-1A1714)
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)
4
10
  [![License: MIT](https://img.shields.io/badge/license-MIT-C4552F.svg)](../LICENSE)
5
11
  ![Platforms](https://img.shields.io/badge/macOS%20·%20Linux%20·%20Windows-1A1714)
6
12
  ![Languages](https://img.shields.io/badge/pt--BR%20·%20en%20·%20es%20·%20fr-C4552F)
@@ -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
- ```mermaid
53
- flowchart TD
54
- A["You type and press Enter<br/>(the box never locks)"] --> B{"Starts with / ?"}
55
- B -- yes --> C["Internal command<br/>/model · /account · /key · /lang · /versions"]
56
- C --> C1["config.py writes to<br/>models.json, profiles.json or keys/"]
57
- B -- no --> FQ["Goes into the queue<br/>with the tool selected at send time"]
58
-
59
- subgraph CHAT ["chat.py — chat"]
60
- FQ --> FW{"Waited in the queue and the previous<br/>answer already covers the request?"}
61
- FW -- "yes (free classifier)" --> FS["Skipped, shown on screen"]
62
- FW -- no --> D["Builds the context: latest messages<br/>of the session up to 48 thousand characters + language"]
63
- D --> E["Thread worker launches the router<br/>in its own process group"]
64
- E -. "JSON events line by line" .-> F["Shows provider, commands and answer"]
65
- X["Ctrl-C"] -. "empties the queue; SIGTERM to the group,<br/>after 5 s, SIGKILL" .-> E
66
- end
67
-
68
- subgraph ROUTER ["cli.py — router"]
69
- E --> G["Loads models:<br/>env > models.json > default"]
70
- G --> H{"Tool pinned<br/>with /tool?"}
71
- H -- yes --> H1["Only the chosen tool"]
72
- H -- auto --> I{"Classifies: Groq → Cerebras →<br/>free OpenRouter → Grok;<br/>no answer, keywords"}
73
- I -- simple --> J["OpenRouter: free, or paid<br/>if authorized · personal data masked"]
74
- J -- "no key or failed" --> K["Codex: subscription,<br/>then API key"]
75
- K -- failed --> L["Antigravity: subscription,<br/>then API key"]
76
- L -- failed --> LP["Paid ones only if listed in fallback_order:<br/>Cursor, Grok, DeepSeek"]
77
- LP -- failed --> M
78
- I -- complex --> M["Claude: next active account<br/>by priority"]
79
- M --> N{"Rate limit?"}
80
- N -- yes --> O["Account on 60 min cooldown"]
81
- O --> P{"Another account<br/>available?"}
82
- P -- yes --> M
83
- P -- no --> PK{"Anthropic<br/>API key?"}
84
- PK -- yes --> PK1["Claude with ANTHROPIC_API_KEY"]
85
- PK1 -- ok --> R
86
- PK1 -- failed --> Q
87
- PK -- no --> Q["Last resort:<br/>Codex, then Antigravity"]
88
- end
89
-
90
- N -- no --> R["Answer"]
91
- J -- ok --> R
92
- K -- ok --> R
93
- L -- ok --> R
94
- LP -- ok --> R
95
- Q --> R
96
- H1 --> R
97
- R --> F
98
- F --> S[("Session saved to disk<br/>with atomic write")]
99
- S --> FQ
100
- ```
58
+ ![Request-to-answer flow](https://raw.githubusercontent.com/samuelrms/omni-ai-cli/main/docs/diagrams/flow1.en.png)
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
- **Coming soon to npm, yarn, pnpm and bun.** The `omni-ai-cli` package is ready and ships as soon as publishing is unlocked:
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
- For now, use the macOS/Linux or Windows command above.
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
- **38 tests**: session persistence and resume, context limit, file permissions, session
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
- ```mermaid
544
- flowchart TD
545
- A[PR opened] --> B[CI: Python 3.11-3.13 tests on Linux and macOS<br/>+ npm pack + launcher]
546
- A --> C[PR title: Conventional Commits?]
547
- B -->|failed| X[Merge blocked]
548
- C -->|invalid| X
549
- B -->|ok| D[Squash merge into main / next / beta]
550
- C -->|ok| D
551
- D --> E[Release: CI runs again]
552
- E -->|failed| Y[Nothing is published]
553
- E -->|ok| F{semantic-release:<br/>any feat/fix/breaking<br/>since last tag?}
554
- F -->|no| Z[No release]
555
- F -->|yes| G[Compute version and tag vX.Y.Z]
556
- G --> H[npm publish with provenance<br/>on the branch dist-tag]
557
- G --> I[GitHub Release with notes]
558
- ```
502
+ ![Release and publish flow](https://raw.githubusercontent.com/samuelrms/omni-ai-cli/main/docs/diagrams/flow2.en.png)
503
+
504
+ Publishing uses **OIDC / trusted publishing**: GitHub Actions authenticates to npm with no
505
+ token and the package ships with *provenance*. One-time setup:
559
506
 
560
- One-time GitHub setup:
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
- 1. **Settings → Secrets and variables → Actions**: add `NPM_TOKEN` (an *Automation* or
563
- *Granular* npmjs.com token with publish permission).
564
- 2. **Settings → Branches**: protect `main` (and `next`/`beta` if used), requiring a PR and
565
- the `Tests (...)`, `npm package` and `conventional` checks.
566
- 3. **Settings → General → Pull Requests**: allow only *squash merging* and tick *Default
567
- to pull request title*.
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
- ![npm](https://img.shields.io/badge/npm-pr%C3%B3ximamente-1A1714)
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)
4
10
  [![License: MIT](https://img.shields.io/badge/license-MIT-C4552F.svg)](../LICENSE)
5
11
  ![Plataformas](https://img.shields.io/badge/macOS%20·%20Linux%20·%20Windows-1A1714)
6
12
  ![Idiomas](https://img.shields.io/badge/pt--BR%20·%20en%20·%20es%20·%20fr-C4552F)
@@ -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
- ```mermaid
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
+ ![Flujo del pedido a la respuesta](https://raw.githubusercontent.com/samuelrms/omni-ai-cli/main/docs/diagrams/flow1.es.png)
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
- **Pronto en npm, yarn, pnpm y bun.** El paquete `omni-ai-cli` está listo y sale en cuanto se libere la publicación:
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
- Por ahora, usa el comando de macOS/Linux o Windows de arriba.
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
- **38 pruebas**: persistencia y reanudación de sesión, límite de contexto, permisos de
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
- ```mermaid
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
+ ![Flujo de release y publicación](https://raw.githubusercontent.com/samuelrms/omni-ai-cli/main/docs/diagrams/flow2.es.png)
561
505
 
562
- Configuración única en GitHub:
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. **Settings → Secrets and variables → Actions**: crear `NPM_TOKEN` (token *Automation*
565
- o *Granular* de npmjs.com con permiso de publish).
566
- 2. **Settings → Branches**: proteger `main` (y `next`/`beta` si se usan) exigiendo PR y
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
- ![npm](https://img.shields.io/badge/npm-bient%C3%B4t-1A1714)
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)
4
10
  [![License: MIT](https://img.shields.io/badge/license-MIT-C4552F.svg)](../LICENSE)
5
11
  ![Plateformes](https://img.shields.io/badge/macOS%20·%20Linux%20·%20Windows-1A1714)
6
12
  ![Langues](https://img.shields.io/badge/pt--BR%20·%20en%20·%20es%20·%20fr-C4552F)
@@ -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
- ```mermaid
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
+ ![Flux de la demande à la réponse](https://raw.githubusercontent.com/samuelrms/omni-ai-cli/main/docs/diagrams/flow1.fr.png)
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
- **Bientôt sur npm, yarn, pnpm et bun.** Le paquet `omni-ai-cli` est prêt et sera publié dès que possible :
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
- En attendant, utilisez la commande macOS/Linux ou Windows ci-dessus.
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
- **38 tests** : persistance et reprise de session, limite de contexte, permissions des
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
- ```mermaid
555
- flowchart TD
556
- A[PR ouverte] --> B[CI : tests Python 3.11-3.13 sur Linux et macOS<br/>+ npm pack + launcher]
557
- A --> C[PR title : Conventional Commits ?]
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
+ ![Flux de release et publication](https://raw.githubusercontent.com/samuelrms/omni-ai-cli/main/docs/diagrams/flow2.fr.png)
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
- Configuration unique sur GitHub :
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
- 1. **Settings → Secrets and variables → Actions** : créer `NPM_TOKEN` (jeton *Automation*
574
- ou *Granular* npmjs.com avec droit de publish).
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
- code = subprocess.call(['git', '-C', str(REPO), 'pull', '--ff-only'])
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.0.0",
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
  },