token-trace-manager 0.7.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/.claude-plugin/marketplace.json +19 -0
- package/.claude-plugin/plugin.json +18 -0
- package/.mcp.json +8 -0
- package/CHANGELOG.md +29 -0
- package/LICENSE +21 -0
- package/README.md +380 -0
- package/bin/ttm.mjs +343 -0
- package/commands/config.md +13 -0
- package/commands/dashboard.md +7 -0
- package/commands/link.md +12 -0
- package/commands/setup.md +19 -0
- package/commands/status.md +13 -0
- package/lib/adapters.mjs +428 -0
- package/lib/audit.mjs +238 -0
- package/lib/config.mjs +135 -0
- package/lib/daemon-client.mjs +139 -0
- package/lib/daemon.mjs +614 -0
- package/lib/db.mjs +444 -0
- package/lib/link.mjs +152 -0
- package/lib/mcp-server.mjs +275 -0
- package/lib/otel-export.mjs +122 -0
- package/lib/pricing.mjs +137 -0
- package/lib/report-data.mjs +171 -0
- package/lib/setup-snippets.mjs +201 -0
- package/lib/traces-data.mjs +155 -0
- package/lib/tui-config.mjs +429 -0
- package/package.json +56 -0
- package/templates/config.html +208 -0
- package/templates/dashboard.html +629 -0
- package/templates/traces.html +407 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "token-trace-manager",
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "Mpaape"
|
|
5
|
+
},
|
|
6
|
+
"metadata": {
|
|
7
|
+
"description": "Proxy local universal de rastreio de tokens/custo para TUIs de código, com labels por ticket e dashboard local",
|
|
8
|
+
"version": "0.7.0"
|
|
9
|
+
},
|
|
10
|
+
"plugins": [
|
|
11
|
+
{
|
|
12
|
+
"name": "ttm",
|
|
13
|
+
"source": "./",
|
|
14
|
+
"description": "Proxy local universal de rastreio de tokens/custo para TUIs de código (Claude Code, Codex, OpenCode, Gemini CLI, Kilo...), com labels por ticket e dashboard local.",
|
|
15
|
+
"version": "0.7.0",
|
|
16
|
+
"category": "productivity"
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ttm",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Proxy local universal de rastreio de tokens/custo para TUIs de código (Claude Code, Codex, OpenCode, Gemini CLI, Kilo...), com labels por ticket e dashboard local.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Mpaape"
|
|
7
|
+
},
|
|
8
|
+
"homepage": "https://github.com/cloud104/token-trace-manager",
|
|
9
|
+
"repository": "https://github.com/cloud104/token-trace-manager",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": ["tokens", "custo", "proxy", "dashboard", "jira", "mcp", "opencode", "codex"],
|
|
12
|
+
"mcpServers": {
|
|
13
|
+
"ttm": {
|
|
14
|
+
"command": "node",
|
|
15
|
+
"args": ["${CLAUDE_PLUGIN_ROOT}/lib/mcp-server.mjs"]
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
package/.mcp.json
ADDED
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Mudanças notáveis deste projeto. Formato: [Keep a Changelog](https://keepachangelog.com/), [semver](https://semver.org/).
|
|
4
|
+
|
|
5
|
+
## [0.7.0] - 2026-07-08
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **`ttm link <tui>` / `ttm unlink <tui>`** — auto-link que descobre a base URL que o TUI já usa (env var, `~/.claude/settings.json`, `~/.codex/config.toml`, `opencode.json`), vira upstream dela, e reescreve a config do TUI pra apontar pro ttm. **Preserva redirect prévio** p/ compat-anthropic (z.ai/GLM/...) — não re-routa pro vendor real. Suporta `claude-code`, `codex`, `opencode`, `gemini-cli`. Idempotente, `--force` p/ relinkar, `--repo`, `--label`.
|
|
9
|
+
- **`ttm config target <name> <url> [--protocol P] [--env VAR]`** — fallback manual ao auto-link; `--list`/`--remove`.
|
|
10
|
+
- **Validação de protocolo** (allow-list `anthropic`/`openai`/`openai-responses`/`gemini`) em `readConfig`; protocolo inválido → 400 no proxy, **sem fabricar evento fantasma de 0 tokens**.
|
|
11
|
+
- **Acurácia de tokens em compat parciais**: extratores registram `usage_fields` (quais campos o upstream reportou) e `usage_unknown`; `sawUsage` só é `true` quando um campo reconhecido está presente — subcontagem fica visível, não silenciosa.
|
|
12
|
+
- **`targetUrlEnv`** — target segue uma env var em runtime (env-following); troca de redirect sem reconfig.
|
|
13
|
+
- **Aviso de `targetUrl` com sufixo `/v1`** (colisão/dobramento de path no upstream).
|
|
14
|
+
- **Snippets redirect-aware** (`claude-code`/`codex`/`opencode`/`gemini-cli`) com `ttm link` como caminho primário.
|
|
15
|
+
- **Suporte Windows**: escrita atômica cross-platform (fallback `unlink+rename` no `fs.renameSync` que falha com EPERM), `pathToFileURL` no import dinâmico, `USERPROFILE` no teste, cleanup tolerante a arquivos abertos.
|
|
16
|
+
- **CI** em matriz (ubuntu/windows/macos × Node 22/24).
|
|
17
|
+
- `LICENSE` MIT, `files` explícito no `package.json`, `prepublishOnly` rodando os testes, `engines` honesto (`>=22.5`).
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- `package.json`: `private: false`, `repository`/`homepage`/`bugs` → `cloud104/token-trace-manager`, `keywords`, `author`.
|
|
21
|
+
- `.claude-plugin/plugin.json` + `marketplace.json`: v0.7.0, `homepage`/`repository` → cloud104.
|
|
22
|
+
- README: seção de Instalação reescrita (`npm i -g token-trace-manager` + `ttm link` + plugin marketplace cloud104 + nota sobre preservar redirect z.ai/GLM).
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- Bug do Codex review: `ttm link` (auto-link) agora preserva o redirect prévio em vez de re-rotar pro vendor real.
|
|
26
|
+
|
|
27
|
+
## [0.6.0] e anteriores
|
|
28
|
+
|
|
29
|
+
Ver histórico de commits: `git log --oneline`. Destaques: payloads inspecionáveis (0.6.0), traces estilo Langfuse + retenção com rollups (0.5.0), auditoria agnóstica ao harness + export OTLP (0.4.0), proxy universal multi-TUI + MCP (0.2.0).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mpaape
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
# token-trace-manager (`ttm`)
|
|
2
|
+
|
|
3
|
+
Proxy local **universal** de rastreio de tokens e custo para TUIs de código — Claude Code, Codex, OpenCode, Gemini CLI, Kilo Code, Goose, Crush, Aider, Cline, Roo Code, Droid, Copilot CLI e qualquer ferramenta que permita trocar a base URL do provedor.
|
|
4
|
+
|
|
5
|
+
Um único daemon local intercepta as chamadas de API de todas as suas ferramentas, extrai o uso de tokens (streaming ou não), atribui a um **label/ticket** (ex: `PROJ-123`) e mostra tudo num **dashboard local**. Um **servidor MCP** embutido deixa o próprio agente consultar e controlar o rastreio de dentro de qualquer TUI compatível com MCP.
|
|
6
|
+
|
|
7
|
+
## Regras de ouro
|
|
8
|
+
|
|
9
|
+
- **Escuta só em `127.0.0.1`** (porta padrão **25519**, configurável) — nunca exposto na rede.
|
|
10
|
+
- **Nunca toca nas chaves**: os headers de autenticação (`x-api-key`, `Authorization`) são repassados intactos ao provedor real e **jamais gravados** — em nenhum arquivo, nem nos payloads.
|
|
11
|
+
- **Conteúdo é seu, e fica local**: por padrão, request/response de cada chamada são guardados em JSONs locais (`payloads/`) para inspeção mensagem a mensagem na página Traces — seguindo a mesma retenção (24h) e com botão de desligar na `/config` para quem não quiser conteúdo em disco.
|
|
12
|
+
|
|
13
|
+
## Arquitetura
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
Claude Code ──┐ ┌──> api.anthropic.com
|
|
17
|
+
Codex ────────┤ proxyURL ├──> api.openai.com
|
|
18
|
+
OpenCode ─────┼─> http://127.0.0.1:25519 ┼──> generativelanguage.googleapis.com
|
|
19
|
+
Gemini CLI ───┤ /{target}/{TICKET?}/… ├──> openrouter.ai/api
|
|
20
|
+
Kilo, etc ────┘ │ └──> (targets customizados)
|
|
21
|
+
│ tee (só metadados de usage)
|
|
22
|
+
v
|
|
23
|
+
SQLite local ──> dashboard http://127.0.0.1:25519/dashboard
|
|
24
|
+
^
|
|
25
|
+
servidor MCP (ttm_status, ttm_set_label, ttm_usage_summary, ...)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- **targetUrl** — a URL real do provedor que o TUI chamaria sem proxy (já vem preenchida para `anthropic`, `openai`, `gemini` e `openrouter`; adicione outras no config).
|
|
29
|
+
- **proxyURL** — `http://127.0.0.1:25519`. O primeiro segmento do path escolhe o target; um segundo segmento opcional no formato `TICKET-123` vira o label daquela chamada.
|
|
30
|
+
|
|
31
|
+
## Instalação
|
|
32
|
+
|
|
33
|
+
Escolha o caminho pelo seu caso — cada um é completo e autossuficiente.
|
|
34
|
+
|
|
35
|
+
### Caminho A — uso o Claude Code (plugin, recomendado)
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
1. /plugin marketplace add cloud104/token-trace-manager
|
|
39
|
+
2. /plugin install ttm@token-trace-manager
|
|
40
|
+
3. Reinicie o Claude Code (plugins carregam na inicialização)
|
|
41
|
+
4. /ttm:setup (sobe o daemon e, com sua aprovação,
|
|
42
|
+
aponta o ANTHROPIC_BASE_URL pro proxy)
|
|
43
|
+
5. Reinicie o Claude Code de novo (a env var só vale pra sessão nova)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
São **dois reinícios** mesmo — um para o plugin carregar, outro para a env var valer. Só na primeira vez; depois é só usar. Valide com `/ttm:status`: se aparecer o daemon rodando e as chamadas contando, está pronto. Os comandos disponíveis: `/ttm:setup`, `/ttm:status`, `/ttm:link`, `/ttm:dashboard`, `/ttm:config`.
|
|
47
|
+
|
|
48
|
+
Quer rastrear outros TUIs além do Claude Code? O daemon já é o mesmo — só falta apontar cada ferramenta: `/ttm:setup codex` (ou `opencode`, `gemini-cli`...) mostra o snippet.
|
|
49
|
+
|
|
50
|
+
### Caminho B — qualquer outro TUI (standalone, sem Claude Code)
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm install -g token-trace-manager # instala o bin `ttm` (Node ≥ 22.5)
|
|
54
|
+
|
|
55
|
+
ttm start # 1. sobe o daemon (idempotente)
|
|
56
|
+
ttm link claude-code # 2. descobre a base URL do TUI e se insere no meio
|
|
57
|
+
# (use: claude-code, codex, opencode, gemini-cli)
|
|
58
|
+
ttm status # 3. confirma que está de pé
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
> **Já redireciona pra um compatível-anthropic (z.ai/GLM/...)?** O `ttm link` **preserva** o seu redirect — ele descobre pra onde você aponta, vira upstream disso, e reescreve a config do TUI pra passar pelo ttm. Continua batendo no mesmo lugar, só que com contagem de tokens.
|
|
62
|
+
|
|
63
|
+
Sem npm? Funciona pelo git também:
|
|
64
|
+
```bash
|
|
65
|
+
git clone https://github.com/cloud104/token-trace-manager
|
|
66
|
+
cd token-trace-manager
|
|
67
|
+
npm link # deixa `ttm` no PATH (sem isso, use `node bin/ttm.mjs`)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Requer Node ≥ 22.5 (SQLite nativo via `node:sqlite`). Sem dependências — é Node puro.
|
|
71
|
+
|
|
72
|
+
## No dia a dia
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
ttm summary --since 7d # resumo no terminal (ou /ttm:status no Claude Code)
|
|
76
|
+
ttm dashboard # abre o dashboard ao vivo (ou /ttm:dashboard)
|
|
77
|
+
ttm label PROJ-123 # muda o ticket padrão (ou /ttm:link PROJ-123)
|
|
78
|
+
ttm tokens PROJ-123 # SÓ a quantidade de tokens da tarefa — pronta pra colar no Jira
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Fechando a tarefa: preenchendo o campo "tokens gastos" do ticket
|
|
82
|
+
|
|
83
|
+
A métrica é **tokens lidos / escritos** (como no Langfuse): *lidos* = tudo que o modelo leu (entrada + cache lido + cache escrito); *escritos* = tudo que gerou (inclui reasoning). Sai de dois jeitos:
|
|
84
|
+
|
|
85
|
+
- **No dashboard**: a tabela "Tokens por tarefa" tem colunas Lidos/Escritos/Total e um botão **copiar** por linha — copia `4831207 lidos / 152030 escritos` pronto pra colar no campo do ticket. A busca no topo acha a tarefa na hora.
|
|
86
|
+
- **No terminal**: `ttm tokens TCW-142 --since all` imprime `lidos=... escritos=... total=...`; com `--raw`, só o total inteiro (para scripts).
|
|
87
|
+
|
|
88
|
+
O envio automático para o campo do Jira é a fase 2 (a base já guarda tudo por tarefa; com o MCP do Jira conectado é um comando a mais).
|
|
89
|
+
|
|
90
|
+
## Auditabilidade — como confiar nos números
|
|
91
|
+
|
|
92
|
+
Os **tokens não são estimados**: o proxy lê o objeto `usage` que o próprio provedor devolve em cada resposta (o mesmo dado que fatura sua conta). O **custo** é a única coisa calculada localmente: tokens × tabela de preços. Para isso ser verificável (estilo Langfuse, só que 100% local), cada chamada grava:
|
|
93
|
+
|
|
94
|
+
- **`provider_id`** — o id da resposta no provedor (`msg_...`, `chatcmpl-...`, `resp_...`): o elo para reconciliar com qualquer outra fonte.
|
|
95
|
+
- **`rates`** — o "recibo" das taxas aplicadas ($/1M por tipo de token, com a data da tabela): o custo é re-multiplicável para sempre, mesmo que a tabela mude depois.
|
|
96
|
+
- O split de cache-write **1h vs 5m** da Anthropic (TTL 1h custa 2×, não 1.25× — sem capturar isso o custo sai ~35% menor em sessões do Claude Code, que usam cache de 1h).
|
|
97
|
+
|
|
98
|
+
Duas ferramentas de verificação:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
ttm audit --since 7d
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
1. **Matemática interna** (universal, qualquer TUI/provedor): re-multiplica tokens × taxas gravadas de cada evento e compara com o custo armazenado — divergência zero ou o comando falha.
|
|
105
|
+
2. **Reconciliação com fontes independentes** — agnóstica ao harness, via adaptadores plugáveis. Cada TUI que mantém contabilidade local própria vira uma fonte; hoje:
|
|
106
|
+
- **Claude Code**: transcripts em `~/.claude/projects/` — casa **id a id** (`msg_...` = `provider_id`), token a token.
|
|
107
|
+
- **OpenCode**: `opencode.db` local — o OpenCode não guarda o id da resposta, então casa por **impressão digital** (tupla exata de tokens + mesma janela de tempo, 1:1).
|
|
108
|
+
- Eventos sem fonte local que os cubra (TUIs que não logam usage) aparecem listados como "sem fonte" — nunca somem. Novos harnesses = um adaptador novo em `lib/audit.mjs`, nada mais muda.
|
|
109
|
+
|
|
110
|
+
No dashboard, o card **"Chamadas individuais (auditoria)"** mostra cada request com a conta aberta no hover: `7.213 × $10/M = $0.072130 (entrada)`, linha a linha. A verificação externa final é a página de usage do próprio provedor (Console da Anthropic / dashboard da OpenAI), que deve bater com os agregados do período.
|
|
111
|
+
|
|
112
|
+
### Exportar para Langfuse (ou qualquer backend OTel) — opcional
|
|
113
|
+
|
|
114
|
+
Rodar um Langfuse self-hosted é pesado (Postgres + ClickHouse + Redis); acoplá-lo quebraria a premissa de plugin pequeno. Em vez disso, o daemon tem um **exportador OTLP nativo (~100 linhas, sem dependências, desligado por padrão)**: um span GenAI por chamada, com `usage_details`/`cost_details` no formato que o Langfuse ingere. Funciona com o endpoint OTel do Langfuse (cloud ou self-hosted) e com qualquer collector OTel (Grafana, Jaeger...). Para ligar, no `config.json` do diretório de dados:
|
|
115
|
+
|
|
116
|
+
```jsonc
|
|
117
|
+
{
|
|
118
|
+
"otel": {
|
|
119
|
+
"endpoint": "https://cloud.langfuse.com/api/public/otel",
|
|
120
|
+
"headers": { "Authorization": "Basic <base64(pk-lf-...:sk-lf-...)>" }
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Fire-and-forget: exportar nunca atrasa nem derruba o proxy, e sem o bloco `otel` nenhum byte sai da máquina.
|
|
126
|
+
|
|
127
|
+
> Caso real que motivou isso: a primeira chamada de teste registrou $0.52; a auditoria contra o log do Claude Code confirmou os 42.880 tokens **exatos**, mas revelou que o cache-write era todo TTL 1h — custo verdadeiro $0.79. O split passou a ser capturado desde a v0.3.0. Eventos antigos (sem recibo/provider_id) aparecem marcados como tal no audit, nunca silenciosamente.
|
|
128
|
+
|
|
129
|
+
## Testando a instalação (30 segundos, com tráfego real)
|
|
130
|
+
|
|
131
|
+
Sem mexer em nenhuma configuração: uma única chamada real do Claude Code, roteada pelo proxy só naquela invocação (a env var vale só para aquele comando), com um label de teste:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
ttm start
|
|
135
|
+
ANTHROPIC_BASE_URL=http://127.0.0.1:25519/anthropic/TESTE-1 claude -p "responda somente: ok"
|
|
136
|
+
ttm summary --since 24h
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Saída esperada — a resposta `ok` do Claude e, no summary, o label `TESTE-1` com tokens e custo reais:
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
período: 24h — 1 chamadas, 42.9k tokens, $0.52
|
|
143
|
+
TESTE-1 42.9k tok $0.52 (1 chamadas)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Se apareceu, está tudo validado de uma vez: daemon de pé, passthrough com sua autenticação intacta, extração de usage, atribuição por label e custo calculado. Abra `http://127.0.0.1:25519/dashboard` para ver no navegador. (Para testar os internos sem rede: `npm test` — 40 asserções contra um upstream fake.)
|
|
147
|
+
|
|
148
|
+
## Configurando cada TUI
|
|
149
|
+
|
|
150
|
+
O padrão é sempre o mesmo: trocar a base URL da ferramenta de `targetUrl` (a URL padrão do provedor) para `proxyURL/{target}`. Com um ticket no path (`proxyURL/{target}/PROJ-123`), todo o tráfego daquela configuração é atribuído ao ticket. `ttm setup <tui>` imprime o snippet da sua porta atual.
|
|
151
|
+
|
|
152
|
+
### Claude Code
|
|
153
|
+
`targetUrl` padrão: `https://api.anthropic.com` — override por env var `ANTHROPIC_BASE_URL` (precisa existir **antes** do processo iniciar):
|
|
154
|
+
|
|
155
|
+
```jsonc
|
|
156
|
+
// ~/.claude/settings.json (todas as sessões) ou .claude/settings.json do repo:
|
|
157
|
+
{ "env": { "ANTHROPIC_BASE_URL": "http://127.0.0.1:25519/anthropic" } }
|
|
158
|
+
|
|
159
|
+
// por repositório, com ticket embutido:
|
|
160
|
+
{ "env": { "ANTHROPIC_BASE_URL": "http://127.0.0.1:25519/anthropic/PROJ-123" } }
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
> Atenção: desde a v2.1.196, apontar `ANTHROPIC_BASE_URL` para fora de `api.anthropic.com` desativa o Remote Control do Claude Code. A autenticação (inclusive OAuth de assinatura) continua funcionando — o proxy repassa tudo intacto. E o `/ttm:setup` faz essa edição para você, com sua aprovação.
|
|
164
|
+
|
|
165
|
+
### Codex (OpenAI)
|
|
166
|
+
`targetUrl` padrão: `https://api.openai.com/v1`, protocolo Responses API — override no `~/.codex/config.toml` (o config de projeto ignora esta chave):
|
|
167
|
+
|
|
168
|
+
```toml
|
|
169
|
+
model_provider = "ttm"
|
|
170
|
+
|
|
171
|
+
[model_providers.ttm]
|
|
172
|
+
name = "OpenAI via ttm"
|
|
173
|
+
base_url = "http://127.0.0.1:25519/openai/v1" # com ticket: /openai/PROJ-123/v1
|
|
174
|
+
wire_api = "responses"
|
|
175
|
+
env_key = "OPENAI_API_KEY"
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
> Prefira `model_providers` a `openai_base_url`/`OPENAI_BASE_URL`: há releases do Codex com bug que ignora essas formas (issue #16719).
|
|
179
|
+
|
|
180
|
+
### OpenCode (sst)
|
|
181
|
+
Multi-provider — override no `opencode.json` (projeto ou global):
|
|
182
|
+
|
|
183
|
+
```jsonc
|
|
184
|
+
{
|
|
185
|
+
"$schema": "https://opencode.ai/config.json",
|
|
186
|
+
"provider": {
|
|
187
|
+
"anthropic": { "options": { "baseURL": "http://127.0.0.1:25519/anthropic" } },
|
|
188
|
+
"openai": { "options": { "baseURL": "http://127.0.0.1:25519/openai/v1" } }
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
O OpenCode também suporta MCP — aponte para `node <repo>/lib/mcp-server.mjs` no bloco `mcp` do `opencode.json` e ganhe as mesmas tools `ttm_*` lá dentro.
|
|
194
|
+
|
|
195
|
+
### Gemini CLI (Google)
|
|
196
|
+
`targetUrl` padrão: `https://generativelanguage.googleapis.com` — override por env var (http permitido para 127.0.0.1):
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
export GEMINI_API_KEY=sua-chave
|
|
200
|
+
export GOOGLE_GEMINI_BASE_URL="http://127.0.0.1:25519/gemini"
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
> Limitação: só o modo **API key** é rastreável. No login OAuth pessoal, o Gemini CLI usa o backend interno Code Assist (`cloudcode-pa.googleapis.com`), que essa variável não afeta.
|
|
204
|
+
|
|
205
|
+
### Kilo Code
|
|
206
|
+
`targetUrl` padrão: gateway próprio (`api.kilo.ai`) — para rastrear, use um provider OpenAI-compatible no `kilo.jsonc` ou na UI (Settings → Providers → Custom Provider → Base URL):
|
|
207
|
+
|
|
208
|
+
```jsonc
|
|
209
|
+
{
|
|
210
|
+
"provider": {
|
|
211
|
+
"openai-compatible": {
|
|
212
|
+
"options": { "apiKey": "{env:OPENAI_API_KEY}", "baseURL": "http://127.0.0.1:25519/openai/v1" }
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Goose (Block)
|
|
219
|
+
```bash
|
|
220
|
+
export OPENAI_HOST="http://127.0.0.1:25519/openai" # provider OpenAI-compatible
|
|
221
|
+
export ANTHROPIC_HOST="http://127.0.0.1:25519/anthropic" # provider Anthropic
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Crush (Charm)
|
|
225
|
+
`crush.json` (ou `~/.config/crush/crush.json`):
|
|
226
|
+
|
|
227
|
+
```jsonc
|
|
228
|
+
{
|
|
229
|
+
"providers": {
|
|
230
|
+
"ttm-anthropic": { "type": "anthropic", "base_url": "http://127.0.0.1:25519/anthropic/v1", "api_key": "$ANTHROPIC_API_KEY" },
|
|
231
|
+
"ttm-openai": { "type": "openai-compat", "base_url": "http://127.0.0.1:25519/openai/v1", "api_key": "$OPENAI_API_KEY" }
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Aider
|
|
237
|
+
```bash
|
|
238
|
+
export OPENAI_API_BASE="http://127.0.0.1:25519/openai/v1"
|
|
239
|
+
# ou: aider --openai-api-base http://127.0.0.1:25519/openai/v1
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Cline / Roo Code
|
|
243
|
+
Só via interface: Settings → API Provider = "OpenAI Compatible" → Base URL = `http://127.0.0.1:25519/openai/v1`.
|
|
244
|
+
|
|
245
|
+
### Droid (Factory AI)
|
|
246
|
+
`~/.factory/settings.json`:
|
|
247
|
+
|
|
248
|
+
```jsonc
|
|
249
|
+
{ "customModels": [{ "model": "claude-opus-4-8", "provider": "anthropic", "baseUrl": "http://127.0.0.1:25519/anthropic", "apiKey": "${ANTHROPIC_API_KEY}" }] }
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### GitHub Copilot CLI (modo BYOK)
|
|
253
|
+
```bash
|
|
254
|
+
export COPILOT_PROVIDER_BASE_URL="http://127.0.0.1:25519/openai/v1"
|
|
255
|
+
export COPILOT_PROVIDER_TYPE=openai
|
|
256
|
+
export COPILOT_MODEL=gpt-5.5
|
|
257
|
+
export COPILOT_PROVIDER_API_KEY=sua-chave
|
|
258
|
+
```
|
|
259
|
+
O modo padrão (backend proprietário do Copilot) não é rastreável.
|
|
260
|
+
|
|
261
|
+
### Não suportados (sem override documentado de base URL)
|
|
262
|
+
- **Amp (Sourcegraph)** — roteia modelos pelo backend proprietário; `HTTP_PROXY` é só transporte.
|
|
263
|
+
- **Cursor CLI** — o `cursor-agent` fala com o backend da Cursor; o BYOK da IDE não vale para o CLI.
|
|
264
|
+
- **Kiro CLI (AWS)** — nenhum mecanismo documentado.
|
|
265
|
+
|
|
266
|
+
## Labels (atribuição por ticket)
|
|
267
|
+
|
|
268
|
+
Duas formas, combináveis:
|
|
269
|
+
|
|
270
|
+
1. **Na URL** (recomendado, por repo/branch): `http://127.0.0.1:25519/anthropic/PROJ-123` — tudo que passar por essa configuração cai no `PROJ-123`. Ideal via `.claude/settings.json` por repositório, ou um `opencode.json` por projeto.
|
|
271
|
+
2. **Label padrão global**: `ttm label PROJ-123` (ou tool MCP `ttm_set_label`, ou `/ttm:link PROJ-123` no Claude Code) — vale para todo tráfego **sem** label na URL, de qualquer TUI, até trocar. `ttm label none` limpa.
|
|
272
|
+
|
|
273
|
+
Chamadas sem nenhum label aparecem no dashboard como "Sem label" — nada se perde.
|
|
274
|
+
|
|
275
|
+
> A ideia é fechar custo por tarefa localmente. Enviar os agregados para o campo do ticket no Jira é uma fase futura (a base já registra tudo por label).
|
|
276
|
+
|
|
277
|
+
## As páginas do proxy (ao vivo, só em 127.0.0.1)
|
|
278
|
+
|
|
279
|
+
| Página | O que tem |
|
|
280
|
+
|---|---|
|
|
281
|
+
| `/dashboard` | Visão geral: KPIs lidos/escritos, tabela "Tokens por tarefa" (share, sparkline, botão copiar no hover), quebras por provedor/modelo/cliente/dia, chamadas recentes. |
|
|
282
|
+
| `/traces` | **Inspetor estilo Langfuse**: sessões (agrupadas por cliente + tarefa + janela de inatividade de 30min) → expande para as mensagens uma a uma — entrada, cache escrito (5m/1h), cache lido, saída, custo com o recibo aberto no hover, duração, id no provedor (clique copia) e o botão **conteúdo**, que abre o payload da chamada: sistema, mensagens (papel a papel) e a resposta do modelo. Filtros por período, provedor, modelo, cliente e tarefa. |
|
|
283
|
+
| `/config` | Retenção dos traces (padrão **24h**, editável; `0` = nunca apagar), botões de limpeza/deleção e resumo da configuração. |
|
|
284
|
+
|
|
285
|
+
APIs JSON para automações: `GET /api/summary` (com `tasks`), `/api/traces`, `/api/events`, `/api/payload?id=N` (request/response da chamada), `/api/label`, `/api/config`, `/healthz`; `POST /api/retention`, `/api/purge`, `/api/settings`. `ttm dashboard --out relatorio.html` exporta a visão geral como HTML estático.
|
|
286
|
+
|
|
287
|
+
## Retenção e histórico
|
|
288
|
+
|
|
289
|
+
Tudo fica num **SQLite interno** (`~/.claude/plugins/data/token-trace-manager/ttm.sqlite`), que sobrevive a upgrades do plugin. Duas camadas com vidas diferentes:
|
|
290
|
+
|
|
291
|
+
- **Traces detalhados** (mensagem a mensagem): retidos por **24h por padrão** — ajuste na página `/config` (até 1 ano, ou `0` para nunca apagar). Um job de hora em hora expira os antigos.
|
|
292
|
+
- **Histórico por tarefa**: ao expirar, cada trace é **somado num rollup diário por tarefa/modelo/provedor antes de ser apagado** — o total que vai pro campo do Jira nunca se perde, só o detalhe individual. O dashboard mistura as duas camadas automaticamente, sem dupla contagem (um rollup só existe para traces já apagados).
|
|
293
|
+
- **Conteúdo das chamadas** (`payloads/<id>.json`): request completo (sistema + mensagens) e o texto da resposta, para o drill-down da página Traces. Vive e morre com o trace correspondente (retenção e deleções removem os JSONs juntos). Toggle na `/config`.
|
|
294
|
+
- **Deleção sob demanda** na `/config`: apagar todos os traces (mantendo o histórico), ou apagar tudo — inclusive por tarefa específica via `POST /api/purge {"mode": "...", "label": "PROJ-123"}`.
|
|
295
|
+
|
|
296
|
+
## Servidor MCP
|
|
297
|
+
|
|
298
|
+
O mesmo servidor funciona em qualquer cliente MCP e **sobe o daemon sozinho** quando necessário:
|
|
299
|
+
|
|
300
|
+
| Tool | O que faz |
|
|
301
|
+
|---|---|
|
|
302
|
+
| `ttm_status` | daemon rodando?, porta, label padrão, targets, URL do dashboard |
|
|
303
|
+
| `ttm_set_label` | define/limpa o label padrão |
|
|
304
|
+
| `ttm_usage_summary` | resumo por label/provedor/modelo/dia (`since`, `label`) |
|
|
305
|
+
| `ttm_dashboard_url` | garante o daemon de pé e retorna a URL do dashboard |
|
|
306
|
+
|
|
307
|
+
No Claude Code vem junto com o plugin. No OpenCode:
|
|
308
|
+
|
|
309
|
+
```jsonc
|
|
310
|
+
// opencode.json
|
|
311
|
+
{ "mcp": { "ttm": { "type": "local", "command": ["node", "/caminho/do/repo/lib/mcp-server.mjs"] } } }
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## CLI
|
|
315
|
+
|
|
316
|
+
```
|
|
317
|
+
ttm start | stop | status gerencia o daemon
|
|
318
|
+
ttm label <TICKET-123|none> label padrão
|
|
319
|
+
ttm summary [--since 7d] resumo no terminal
|
|
320
|
+
ttm dashboard [--since 7d] [--out arquivo.html]
|
|
321
|
+
ttm tokens <TICKET> [--since] [--raw] lidos/escritos da tarefa (--raw: só o total)
|
|
322
|
+
ttm audit [--since 7d] auditoria: matemática interna + fontes independentes
|
|
323
|
+
ttm targets lista os upstreams registrados
|
|
324
|
+
ttm config [porta <n>] mostra config / troca a porta
|
|
325
|
+
ttm setup <tui> snippet de configuração por ferramenta
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Configuração avançada
|
|
329
|
+
|
|
330
|
+
Arquivo: `~/.claude/plugins/data/token-trace-manager/config.json` (criado sob demanda):
|
|
331
|
+
|
|
332
|
+
```jsonc
|
|
333
|
+
{
|
|
334
|
+
"port": 25519,
|
|
335
|
+
"injectUsage": true, // injeta include_usage em streams OpenAI sem usage (e filtra o chunk extra)
|
|
336
|
+
"targets": {
|
|
337
|
+
"meugateway": { "targetUrl": "https://llm.minhaempresa.com/api", "protocol": "openai" }
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
- **Protocolos suportados**: `anthropic`, `openai` (chat completions **e** Responses API, detectado pelo path), `openai-responses`, `gemini`.
|
|
343
|
+
- **`injectUsage`**: streams de chat completions sem `stream_options.include_usage` não trazem usage. O proxy injeta a opção na request e **remove o chunk extra da resposta** antes de repassar (SDKs oficiais toleram o chunk, mas wrappers comuns — LangChain, Haystack — quebram; por isso o filtro). Se o cliente já pediu `include_usage`, nada é alterado.
|
|
344
|
+
- **Preços**: tabela em `lib/pricing.mjs` (Anthropic/OpenAI/Gemini, coletados das páginas oficiais em 2026-07-06). Modelos fora da tabela têm custo `null` — os tokens são registrados mesmo assim e o dashboard sinaliza custo parcial. Override sem tocar em código: `pricing.override.json` no diretório de dados, ex: `{ "meu-modelo": { "input": 2.0, "output": 8.0 } }` (USD/1M tokens).
|
|
345
|
+
|
|
346
|
+
## Comandos do plugin (Claude Code)
|
|
347
|
+
|
|
348
|
+
| Comando | O que faz |
|
|
349
|
+
|---|---|
|
|
350
|
+
| `/ttm:setup [tui]` | configura o Claude Code para passar pelo proxy (edita settings com sua aprovação) ou mostra o snippet de outro TUI |
|
|
351
|
+
| `/ttm:status` | estado do daemon + resumo de uso/custo |
|
|
352
|
+
| `/ttm:link PROJ-123` | define o label padrão |
|
|
353
|
+
| `/ttm:dashboard` | abre o dashboard local |
|
|
354
|
+
| `/ttm:config` | mostra porta, targets e label; explica como ajustar |
|
|
355
|
+
|
|
356
|
+
## Testes
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
node test/e2e.mjs
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Sobe um upstream fake + o daemon real e valida: passthrough de streaming e não-streaming nos 4 protocolos, labels por URL e por padrão, normalização de tokens (cache/reasoning), cálculo de custo, injeção+filtragem de `include_usage`, endpoints locais, dashboard, privacidade (nenhuma chave ou conteúdo persistido) e as regressões da revisão adversarial (XSS via User-Agent, corrupção por `$'` no template, rotas herdadas de protótipo, erros de protocolo MCP). 81+ asserções, cobrindo também retenção/rollups, contexto repo/branch, relabel retroativo, sessionização de traces e o exportador OTel.
|
|
363
|
+
|
|
364
|
+
## Upgrade
|
|
365
|
+
|
|
366
|
+
Versionamento semver em `.claude-plugin/plugin.json`. Como plugin:
|
|
367
|
+
|
|
368
|
+
```
|
|
369
|
+
/plugin update ttm (reinicie o Claude Code para aplicar)
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
Standalone: `git pull` e `ttm stop && ttm start`.
|
|
373
|
+
|
|
374
|
+
## Limitações conhecidas
|
|
375
|
+
|
|
376
|
+
- O TUI precisa suportar override de base URL (ver lista de não suportados acima).
|
|
377
|
+
- Se o daemon estiver parado, o TUI configurado fica sem resposta — `ttm start` resolve; o servidor MCP e o CLI sobem o daemon automaticamente.
|
|
378
|
+
- Preços de modelos mudam; a tabela é estática com data marcada (`ttm config` mostra) e override via `pricing.override.json`.
|
|
379
|
+
- Gemini CLI: só modo API key (OAuth usa backend interno não roteável).
|
|
380
|
+
- O custo do Sonnet 5 usa o preço introdutório ($2/$10) válido até 2026-08-31 — atualizar para $3/$15 depois disso.
|