terminal-smart-cli 0.97.2 → 0.97.4

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
@@ -27,6 +27,7 @@ ts "como libero a porta 443 no ufw?"
27
27
  | `ts agente "tarefa"` | **Executa DE VERDADE nesta máquina** — comandos, arquivos, diagnóstico. Destrutivo pede aprovação; com `--yes` só roda se você aprovar remotamente no Telegram, senão é recusado |
28
28
  | `ts agente --continuar "..."` | Retoma o trabalho anterior desta pasta |
29
29
  | `ts agente --ler` / `--plano` | Modo só-leitura (Ask) / propõe um plano sem agir |
30
+ | `ts imagem "descrição"` | Escolhe Premium, Econômica ou Sem imagem antes de consumir créditos |
30
31
  | `ts agente --worktree` | Isola a run num git worktree (não toca a árvore principal) |
31
32
  | `ts agente --stream-json` | Modo headless/CI: só NDJSON tipado no stdout |
32
33
  | `ts diagnosticar "erro"` | **Investiga a causa raiz**: hipótese → sonda (só-leitura) → verificação adversarial → veredito. `--remoto "ssh user@host"` investiga uma VPS |
package/bin/ts.js CHANGED
@@ -172,6 +172,97 @@ async function ensureConv(token) {
172
172
  cfg = config.save({ convId: r.id });
173
173
  return r.id;
174
174
  }
175
+
176
+ // ── ts imagem "descrição" ───────────────────────────────────────────────────
177
+ // A escolha é sempre explícita. --yes sozinho não autoriza uma geração paga:
178
+ // scripts precisam declarar --premium ou --economica.
179
+ async function imageCmd(args) {
180
+ const token = needToken();
181
+ const en = cfg.lang === 'en';
182
+ const prompt = args.join(' ').trim() || await readStdin();
183
+ if (!prompt || prompt.trim().length < 8) {
184
+ console.error(ui.errLine(en ? 'Describe the image (at least 8 characters).' : 'Descreva a imagem (mínimo de 8 caracteres).'));
185
+ process.exit(2);
186
+ }
187
+ const catalog = await api('/api/ia/image/options', { token });
188
+ const options = Array.isArray(catalog.options) ? catalog.options : [];
189
+ let choice = FLAGS.has('--premium') ? 'premium'
190
+ : (FLAGS.has('--economica') || FLAGS.has('--economy')) ? 'economy'
191
+ : (FLAGS.has('--sem-imagem') || FLAGS.has('--none')) ? 'none' : '';
192
+
193
+ if (!choice && (!process.stdin.isTTY || JSON_OUT)) {
194
+ throw new ApiError(en
195
+ ? 'Choose --premium, --economy or --none. Image generation is never silently approved.'
196
+ : 'Escolha --premium, --economica ou --sem-imagem. Geração de imagem nunca é aprovada silenciosamente.', { code: 'image_confirmation_required' });
197
+ }
198
+ if (!choice) {
199
+ const byId = id => options.find(o => o.id === id) || {};
200
+ const premium = byId('premium'), economy = byId('economy');
201
+ console.log('\n' + ui.box([
202
+ C.bold(en ? 'This action can generate an image' : 'Esta ação pode gerar uma imagem'),
203
+ C.dim(en ? 'Choose before spending credits. There is no silent paid fallback.' : 'Escolha antes de gastar créditos. Não existe fallback pago silencioso.'),
204
+ '',
205
+ ` ${C.cyan('1')} ${C.bold(premium.label || 'Premium · GPT Image 2')} ${C.dim('~' + (premium.estimatedCredits || '?') + ' créditos')}${premium.allowed === false ? C.warn(en ? ' · requires Pro/Ultra' : ' · requer Pro/Ultra') : ''}`,
206
+ ` ${C.cyan('2')} ${C.bold(economy.label || 'Econômica · Gemini')} ${C.dim('~' + (economy.estimatedCredits || '?') + ' créditos')}`,
207
+ ` ${C.cyan('3')} ${C.bold(en ? 'Continue without images' : 'Continuar sem imagens')} ${C.dim(en ? 'no charge' : 'sem cobrança')}`,
208
+ '',
209
+ C.dim((en ? 'Plan ' : 'Plano ') + (catalog.plan || '?') + ' · ' + (en ? 'balance ' : 'saldo ') + (catalog.remaining ?? 0)),
210
+ ], { title: 'ts imagem' }));
211
+ const answer = String(await ui.ask(C.dim(en ? ' Choose 1, 2 or 3 › ' : ' Escolha 1, 2 ou 3 › '))).trim();
212
+ choice = answer === '1' ? 'premium' : answer === '2' ? 'economy' : 'none';
213
+ }
214
+ const selected = options.find(o => o.id === choice);
215
+ if (!selected || selected.allowed === false) {
216
+ throw new ApiError((selected && selected.upgradeRequired)
217
+ ? (en ? 'GPT Image 2 requires Pro or Ultra.' : 'GPT Image 2 requer plano Pro ou Ultra.')
218
+ : (en ? 'Invalid image option.' : 'Opção de imagem inválida.'), { code: 'plan_limit' });
219
+ }
220
+ if (choice === 'none') {
221
+ const result = { ok: true, skipped: true, charged: 0, message: en ? 'No image generated and no credits charged.' : 'Nenhuma imagem foi gerada e nenhum crédito foi cobrado.' };
222
+ if (JSON_OUT) console.log(JSON.stringify(result)); else console.log('\n' + ui.infoLine(result.message));
223
+ return;
224
+ }
225
+ const convId = await ensureConv(token);
226
+ const sp = JSON_OUT ? { stop() {} } : ui.spinner(en ? 'generating image…' : 'gerando imagem…').start();
227
+ let result;
228
+ try {
229
+ result = await api('/api/ia/image', {
230
+ method: 'POST',
231
+ token,
232
+ timeoutMs: 200000,
233
+ body: {
234
+ conversationId: convId,
235
+ prompt,
236
+ choice,
237
+ quality: selected.quality,
238
+ size: selected.size,
239
+ confirmed: true,
240
+ },
241
+ });
242
+ } finally { sp.stop(); }
243
+ if (!result || !result.url) throw new Error(en ? 'Image provider returned no image.' : 'O provedor não retornou uma imagem.');
244
+ const fs = require('fs'), os = require('os'), path = require('path');
245
+ let bytes, ext = '.png';
246
+ const dataMatch = String(result.url).match(/^data:image\/(png|jpeg|webp);base64,([\s\S]+)$/);
247
+ if (dataMatch) {
248
+ ext = dataMatch[1] === 'jpeg' ? '.jpg' : '.' + dataMatch[1];
249
+ bytes = Buffer.from(dataMatch[2], 'base64');
250
+ } else {
251
+ const downloaded = await fetch(result.url, { signal: AbortSignal.timeout(60000) });
252
+ if (!downloaded.ok) throw new Error('Download da imagem falhou: HTTP ' + downloaded.status);
253
+ bytes = Buffer.from(await downloaded.arrayBuffer());
254
+ const type = downloaded.headers.get('content-type') || '';
255
+ ext = /jpeg/.test(type) ? '.jpg' : /webp/.test(type) ? '.webp' : '.png';
256
+ }
257
+ if (!bytes || bytes.length < 256 || bytes.length > 25 * 1024 * 1024) throw new Error(en ? 'Invalid image payload.' : 'Conteúdo de imagem inválido.');
258
+ const target = path.join(os.homedir(), 'Downloads', `terminal-smart-imagem-${Date.now()}${ext}`);
259
+ const partial = target + '.partial-' + process.pid;
260
+ fs.writeFileSync(partial, bytes, { flag: 'wx' });
261
+ fs.renameSync(partial, target);
262
+ const output = { ok: true, path: target, model: result.model, charged: result.charged, remaining: result.remaining, choice };
263
+ if (JSON_OUT) console.log(JSON.stringify(output));
264
+ else console.log('\n' + ui.okLine((en ? 'image saved: ' : 'imagem salva: ') + C.cyan(target)) + '\n ' + C.dim(`${result.model} · ${result.charged} créditos · saldo ${result.remaining}`));
265
+ }
175
266
  // Envia uma mensagem na conversa ativa e devolve o texto (recria a conversa 1x se apagada na web).
176
267
  async function sendMessage(token, content) {
177
268
  const doStream = async (convId) => {
@@ -2567,12 +2658,38 @@ async function cloudCmd(args) {
2567
2658
  const sub = String(args[0] || 'status').toLowerCase();
2568
2659
  const rest = args.slice(1).filter(a => !a.startsWith('-'));
2569
2660
  const call = (path, opts = {}) => api('/api/cloud/' + path, { token, ...opts });
2661
+ const cloudSlug = require('../lib/cloud-slug');
2662
+ const chooseSlug = async (provided) => {
2663
+ let value = String(provided || '').trim();
2664
+ if (!value && process.stdin.isTTY && !JSON_OUT && !YES) {
2665
+ value = String(await ui.ask(en
2666
+ ? ' Choose the public name (<name>.tsbox1.com): '
2667
+ : ' Escolha o endereço público (<nome>.tsbox1.com): ')).trim();
2668
+ }
2669
+ const checked = cloudSlug.validate(value);
2670
+ if (!checked.ok) {
2671
+ if (JSON_OUT) console.log(JSON.stringify(checked));
2672
+ else console.log(ui.errLine(checked.error));
2673
+ return null;
2674
+ }
2675
+ return checked.slug;
2676
+ };
2570
2677
 
2571
2678
  if (sub === 'up' || sub === 'start' || sub === 'ligar') {
2679
+ let requestedSlug = rest[0] || '';
2680
+ const current = await call('status');
2681
+ if (requestedSlug) {
2682
+ requestedSlug = await chooseSlug(requestedSlug);
2683
+ if (!requestedSlug) return;
2684
+ } else if (!current.exists || current.needsSlug) {
2685
+ requestedSlug = await chooseSlug('');
2686
+ if (!requestedSlug) return;
2687
+ }
2572
2688
  console.log(C.dim(en ? 'provisioning your cloud box…' : 'provisionando sua caixa na nuvem…'));
2573
2689
  try {
2574
- const r = await call('up', { method: 'POST', body: {}, timeoutMs: 120000 });
2690
+ const r = await call('up', { method: 'POST', body: requestedSlug ? { slug: requestedSlug } : {}, timeoutMs: 120000 });
2575
2691
  if (!r.ok) { console.log(ui.errLine(r.error || 'falha')); if (r.code === 'plan_required') console.log(C.dim(en ? 'Get Ultra at terminalsmart.com.br/planos' : 'Assine o Ultra em terminalsmart.com.br/planos')); return; }
2692
+ if (JSON_OUT) { console.log(JSON.stringify(r)); return; }
2576
2693
  console.log('\n' + ui.box([
2577
2694
  C.ok(en ? 'Cloud box is up!' : 'Caixa na nuvem no ar!'), '',
2578
2695
  C.dim('URL: ') + C.bold(r.url),
@@ -2586,6 +2703,15 @@ async function cloudCmd(args) {
2586
2703
  if (sub === 'status' || sub === 'url') {
2587
2704
  const r = await call('status');
2588
2705
  if (!r.exists) { console.log(ui.infoLine(en ? 'No cloud box yet. Run: ts cloud up' : 'Nenhuma caixa ainda. Rode: ts cloud up')); return; }
2706
+ if (JSON_OUT) { console.log(JSON.stringify(r)); return; }
2707
+ if (r.needsSlug || !r.url) {
2708
+ console.log('\n' + ui.box([
2709
+ C.warn(en ? 'Public name not chosen yet.' : 'Endereço público ainda não escolhido.'),
2710
+ C.dim(en ? 'Choose one: ' : 'Escolha um nome: ') + C.cyan('ts cloud nome <nome>'),
2711
+ C.dim(en ? 'Example: ' : 'Exemplo: ') + C.cyan('ts cloud nome minhaempresa'),
2712
+ ], { title: 'ts cloud' }) + '\n');
2713
+ return;
2714
+ }
2589
2715
  if (sub === 'url') { console.log(r.url); return; }
2590
2716
  const lines = [
2591
2717
  C.dim('status: ') + (r.running ? C.ok(en ? 'running' : 'no ar') : C.dim(en ? 'suspended (idle) — wakes on next use' : 'suspensa (ociosa) — acorda no próximo uso')),
@@ -2597,6 +2723,19 @@ async function cloudCmd(args) {
2597
2723
  return;
2598
2724
  }
2599
2725
 
2726
+ if (sub === 'nome' || sub === 'name' || sub === 'slug' || sub === 'renomear' || sub === 'rename') {
2727
+ const slug = await chooseSlug(rest[0]);
2728
+ if (!slug) return;
2729
+ const r = await call('slug', { method: 'POST', body: { slug } });
2730
+ if (JSON_OUT) { console.log(JSON.stringify(r)); return; }
2731
+ if (!r.ok) { console.log(ui.errLine(r.error || 'falha')); return; }
2732
+ console.log('\n' + ui.box([
2733
+ C.ok(en ? 'Public address saved!' : 'Endereço público salvo!'),
2734
+ C.dim('URL: ') + C.bold(r.url),
2735
+ ], { title: 'ts cloud' }) + '\n');
2736
+ return;
2737
+ }
2738
+
2600
2739
  if (sub === 'exec' || sub === 'sh' || sub === 'run') {
2601
2740
  const cmd = rest.join(' ').trim();
2602
2741
  if (!cmd) { console.log(ui.errLine(en ? 'usage: ts cloud exec "<command>"' : 'uso: ts cloud exec "<comando>"')); return; }
@@ -2617,7 +2756,13 @@ async function cloudCmd(args) {
2617
2756
  const goal = rest.join(' ').trim();
2618
2757
  if (!goal) { console.log(ui.errLine(en ? 'usage: ts cloud meta "<goal>"' : 'uso: ts cloud meta "<objetivo>"')); return; }
2619
2758
  let st = await call('status');
2620
- if (!st.exists) { console.log(C.dim(en ? 'creating box…' : 'criando caixa…')); const up = await call('up', { method: 'POST', body: {}, timeoutMs: 120000 }); if (!up.ok) { console.log(ui.errLine(up.error || 'falha')); if (up.code === 'plan_required') console.log(C.dim('terminalsmart.com.br/planos')); return; } }
2759
+ if (!st.exists || st.needsSlug) {
2760
+ const slug = await chooseSlug('');
2761
+ if (!slug) return;
2762
+ console.log(C.dim(en ? 'creating box…' : 'criando caixa…'));
2763
+ const up = await call('up', { method: 'POST', body: { slug }, timeoutMs: 120000 });
2764
+ if (!up.ok) { console.log(ui.errLine(up.error || 'falha')); if (up.code === 'plan_required') console.log(C.dim('terminalsmart.com.br/planos')); return; }
2765
+ }
2621
2766
  console.log(C.dim(en ? 'running ts meta inside the cloud box (may take a while)…' : 'rodando ts meta dentro da caixa (pode demorar)…'));
2622
2767
  const safe = goal.replace(/(["\\$`])/g, '\\$1');
2623
2768
  const cmdMeta = 'cd /workspace && ts meta "' + safe + '" --yes 2>&1';
@@ -2651,7 +2796,8 @@ async function cloudCmd(args) {
2651
2796
 
2652
2797
  console.log('\n' + ui.box([
2653
2798
  C.bold('ts cloud') + C.dim(en ? ' — your dev box in the cloud' : ' — sua caixa de dev na nuvem'), '',
2654
- C.dim('up') + ' ' + (en ? 'create/start your box + public URL' : 'cria/liga sua caixa + URL pública'),
2799
+ C.dim('up [nome]') + ' ' + (en ? 'create/start your box with a chosen URL' : 'cria/liga sua caixa com endereço escolhido'),
2800
+ C.dim('nome <nome>') + ' ' + (en ? 'choose or change <name>.tsbox1.com' : 'escolhe ou altera <nome>.tsbox1.com'),
2655
2801
  C.dim('meta "…"') + ' ' + (en ? 'the agent builds your app in the cloud' : 'o agente constrói seu app na nuvem'),
2656
2802
  C.dim('exec "…"') + ' ' + (en ? 'run a shell command in the box' : 'roda um comando na caixa'),
2657
2803
  C.dim('url') + ' ' + (en ? 'print the public URL' : 'mostra a URL pública'),
@@ -2757,6 +2903,7 @@ function recallCmd(args) {
2757
2903
  case 'login': return login();
2758
2904
  case 'logout': return logout();
2759
2905
  case 'chat': case 'conversa': return chatRepl();
2906
+ case 'imagem': case 'image': case 'img': return imageCmd(POS.slice(1));
2760
2907
  case 'quem': case 'whoami': return quem();
2761
2908
  case 'nova': case 'new': return nova();
2762
2909
  case 'run': return runCmd(POS.slice(1));
@@ -2806,6 +2953,9 @@ function recallCmd(args) {
2806
2953
  // pipe/--json ficam determinísticos no chat (scripts não levam surpresa)
2807
2954
  const text = POS.join(' ');
2808
2955
  if (process.stdin.isTTY && !JSON_OUT && cfg.token) {
2956
+ if (/^\s*(?:(?:quero|preciso)\s+(?:que\s+)?(?:voc[eê]\s+)?|(?:por favor[,\s]+)?)?(?:gere|gerar|crie|criar|fa[çc]a|produza|desenhe|ilustre|edite)\s+(?:uma|a|minha)?\s*(?:imagem|foto|ilustra[çc][aã]o|arte|banner|capa|mockup)\b/i.test(text)) {
2957
+ return imageCmd([text]);
2958
+ }
2809
2959
  const r = await router.route(text, cfg.token);
2810
2960
  if (r.dest === 'agente') { console.log(' ' + C.cyan('⚙') + ' ' + C.dim(T.route_agent)); return agentCmd([text]); }
2811
2961
  if (r.dest === 'run') { console.log(' ' + C.indigo('◆') + ' ' + C.dim(T.route_run)); return runCmd([text]); }
@@ -0,0 +1,30 @@
1
+ 'use strict';
2
+
3
+ const RESERVED = new Set([
4
+ 'www', 'api', 'app', 'admin', 'mail', 'email', 'ftp', 'smtp', 'imap',
5
+ 'pop', 'ns1', 'ns2', 'cdn', 'assets', 'static', 'status', 'suporte',
6
+ 'support', 'help', 'billing', 'checkout', 'teste', 'test', 'terminalsmart',
7
+ 'terminal-smart', 'cloudflare', 'cloud', 'box', 'site', 'ts',
8
+ ]);
9
+
10
+ function normalize(value) {
11
+ return String(value || '')
12
+ .normalize('NFD')
13
+ .replace(/[\u0300-\u036f]/g, '')
14
+ .toLowerCase()
15
+ .trim()
16
+ .replace(/[^a-z0-9]+/g, '-')
17
+ .replace(/^-+|-+$/g, '')
18
+ .replace(/-+/g, '-');
19
+ }
20
+
21
+ function validate(value) {
22
+ const slug = normalize(value);
23
+ if (!slug || slug.length < 3 || slug.length > 40) return { ok: false, code: 'invalid_slug', error: 'O nome deve ter entre 3 e 40 caracteres.' };
24
+ if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$/.test(slug)) return { ok: false, code: 'invalid_slug', error: 'Use letras, números e hífens.' };
25
+ if (RESERVED.has(slug)) return { ok: false, code: 'reserved_slug', error: 'Esse nome é reservado pelo Terminal Smart.' };
26
+ return { ok: true, slug };
27
+ }
28
+
29
+ module.exports = { RESERVED, normalize, validate };
30
+
package/lib/i18n.js CHANGED
@@ -8,6 +8,7 @@ const STR = {
8
8
  ['ts', 'abre o modo conversa — digite naturalmente'],
9
9
  ['ts "pergunta"', 'resposta única e volta pro shell'],
10
10
  ['cat log | ts "analise"', 'analisa o que vier do pipe'],
11
+ ['ts imagem "descrição"', 'gera imagem com escolha Premium/Econômica/Sem imagem antes da cobrança'],
11
12
  ['ts video <url> "..."', 'lê a transcrição de um vídeo e responde (yt-dlp)'],
12
13
  ['ts arquivar <url>', 'salva a página como 1 HTML offline (monolith)'],
13
14
  ['ts qr', 'QR pra continuar a conversa no celular'],
@@ -189,6 +190,7 @@ const STR = {
189
190
  ['ts', 'opens chat mode — just type naturally'],
190
191
  ['ts "question"', 'one-shot answer, back to the shell'],
191
192
  ['cat log | ts "analyze"', 'analyze piped input'],
193
+ ['ts image "description"', 'choose Premium/Economy/No image before any charge'],
192
194
  ['ts video <url> "..."', 'reads a video transcript and answers (yt-dlp)'],
193
195
  ['ts arquivar <url>', 'saves the page as 1 offline HTML (monolith)'],
194
196
  ['ts qr', 'QR to continue the conversation on your phone'],
@@ -9,7 +9,7 @@
9
9
  const crypto = require('crypto');
10
10
 
11
11
  const INTELLIGENCE_CONTRACT = 1;
12
- const PRICE_REVISION = '2026-07-26';
12
+ const PRICE_REVISION = '2026-07-28';
13
13
 
14
14
  const MODEL_CATALOG = Object.freeze({
15
15
  smart: {
@@ -18,7 +18,7 @@ const MODEL_CATALOG = Object.freeze({
18
18
  },
19
19
  'deepseek-v4-flash': {
20
20
  upstreamId: 'deepseek/deepseek-v4-flash',
21
- provider: 'deepseek', contextWindow: 1048576, price: { input: 0.14, output: 0.28, cachedInput: 0.028 },
21
+ provider: 'deepseek', contextWindow: 1048576, price: { input: 0.14, output: 0.28, cachedInput: 0.0028 },
22
22
  capabilities: ['tools', 'code', 'planning', 'execution', 'long-context'], toolReliability: 0.94, quality: 0.86,
23
23
  },
24
24
  'deepseek-chat': {
@@ -51,11 +51,21 @@ const MODEL_CATALOG = Object.freeze({
51
51
  provider: 'moonshot', contextWindow: 262144, price: { input: 0.75, output: 3.50, cachedInput: 0.15 },
52
52
  capabilities: ['tools', 'code', 'vision', 'long-context'], toolReliability: 0.86, quality: 0.84,
53
53
  },
54
+ 'kimi-k3': {
55
+ upstreamId: 'moonshotai/kimi-k3',
56
+ provider: 'moonshot', contextWindow: 262144, price: { input: 3.00, output: 15.00, cachedInput: 0.30 },
57
+ capabilities: ['tools', 'code', 'planning', 'long-context'], toolReliability: 0.88, quality: 0.91,
58
+ },
54
59
  'glm-4.6': {
55
60
  upstreamId: 'z-ai/glm-4.6',
56
61
  provider: 'zai', contextWindow: 204800, price: { input: 0.50, output: 2.00, cachedInput: 0.10 },
57
62
  capabilities: ['tools', 'planning', 'code'], toolReliability: 0.84, quality: 0.82,
58
63
  },
64
+ 'glm-5.2': {
65
+ upstreamId: 'z-ai/glm-5.2',
66
+ provider: 'zai', contextWindow: 200000, price: { input: 0.7644, output: 2.4024, cachedInput: 0.15288 },
67
+ capabilities: ['tools', 'planning', 'code', 'long-context'], toolReliability: 0.88, quality: 0.89,
68
+ },
59
69
  'mimo-v2.5': {
60
70
  upstreamId: 'xiaomi/mimo-v2.5',
61
71
  provider: 'xiaomi', contextWindow: 1050000, price: { input: 0.14, output: 0.28, cachedInput: 0.0028 },
@@ -66,6 +76,26 @@ const MODEL_CATALOG = Object.freeze({
66
76
  provider: 'openai', contextWindow: 128000, price: { input: 0.15, output: 0.60, cachedInput: 0.075 },
67
77
  capabilities: ['tools', 'classification', 'summarization', 'review'], toolReliability: 0.88, quality: 0.76,
68
78
  },
79
+ 'gpt-5.6-sol': {
80
+ upstreamId: 'openai/gpt-5.6-sol',
81
+ provider: 'openai', contextWindow: 1000000, price: { input: 5.00, output: 30.00, cachedInput: 0.50 },
82
+ capabilities: ['tools', 'code', 'planning', 'review', 'long-context'], toolReliability: 0.97, quality: 0.98,
83
+ },
84
+ 'gpt-5.6-terra': {
85
+ upstreamId: 'openai/gpt-5.6-terra',
86
+ provider: 'openai', contextWindow: 1000000, price: { input: 2.50, output: 15.00, cachedInput: 0.25 },
87
+ capabilities: ['tools', 'code', 'planning', 'review', 'long-context'], toolReliability: 0.96, quality: 0.95,
88
+ },
89
+ 'gpt-5.6-luna': {
90
+ upstreamId: 'openai/gpt-5.6-luna',
91
+ provider: 'openai', contextWindow: 1000000, price: { input: 1.00, output: 6.00, cachedInput: 0.10 },
92
+ capabilities: ['tools', 'code', 'summarization', 'review', 'long-context'], toolReliability: 0.93, quality: 0.89,
93
+ },
94
+ 'claude-sonnet-5': {
95
+ upstreamId: 'anthropic/claude-sonnet-5',
96
+ provider: 'anthropic', contextWindow: 1000000, price: { input: 2.00, output: 10.00, cachedInput: 0.20 },
97
+ capabilities: ['tools', 'code', 'planning', 'review', 'long-context'], toolReliability: 0.97, quality: 0.96,
98
+ },
69
99
  'claude-sonnet-4-6': {
70
100
  upstreamId: 'anthropic/claude-sonnet-4.6',
71
101
  provider: 'anthropic', contextWindow: 1000000, price: { input: 3.00, output: 15.00, cachedInput: 0.30 },
@@ -123,17 +153,24 @@ function normalizeModelId(model) {
123
153
  return upstream ? upstream[0] : raw;
124
154
  }
125
155
 
126
- function modelInfo(model) {
156
+ function modelInfo(model, now) {
127
157
  const id = normalizeModelId(model);
128
- return Object.assign({ id, known: !!MODEL_CATALOG[id] }, MODEL_CATALOG[id] || {
158
+ const info = MODEL_CATALOG[id] || {
129
159
  provider: 'unknown', contextWindow: 100000, price: DEFAULT_PRICE,
130
160
  capabilities: [], toolReliability: 0.50, quality: 0.50,
131
- });
161
+ };
162
+ const result = Object.assign({ id, known: !!MODEL_CATALOG[id] }, info);
163
+ const at = now ? new Date(now) : new Date();
164
+ if (id === 'claude-sonnet-5' && at.getTime() >= Date.parse('2026-09-01T00:00:00Z')) {
165
+ result.price = { input: 3.00, output: 15.00, cachedInput: 0.30 };
166
+ }
167
+ return result;
132
168
  }
133
169
 
134
- function legacyPriceMap() {
170
+ function legacyPriceMap(now) {
135
171
  const out = {};
136
- for (const [id, m] of Object.entries(MODEL_CATALOG)) {
172
+ for (const id of Object.keys(MODEL_CATALOG)) {
173
+ const m = modelInfo(id, now);
137
174
  out[id] = { i: m.price.input, o: m.price.output, c: m.price.cachedInput };
138
175
  }
139
176
  out._default = { i: DEFAULT_PRICE.input, o: DEFAULT_PRICE.output, c: DEFAULT_PRICE.cachedInput };
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "terminal-smart-cli",
3
- "version": "0.97.2",
3
+ "version": "0.97.4",
4
4
  "description": "Terminal Smart no seu terminal — pergunte, analise logs por pipe e orquestre agentes de IA. Comando: ts",
5
5
  "bin": {
6
6
  "ts": "bin/ts.js"
7
7
  },
8
8
  "scripts": {
9
- "test": "node test/core.test.js && node test/intelligence-core.test.js && node test/eval-model.test.js && node test/project-cache.test.js && node test/memory-bus.test.js && node test/capabilities.test.js && node test/mcp-e2e.test.js && node test/erros.test.js && node test/capability-pack.test.js && node test/byok.test.js && node test/conhecimento.test.js && node test/policy.test.js && node test/temas.test.js && node test/skill-index.test.js"
9
+ "test": "node test/core.test.js && node test/intelligence-core.test.js && node test/cloud-slug.test.js && node test/eval-model.test.js && node test/project-cache.test.js && node test/memory-bus.test.js && node test/capabilities.test.js && node test/mcp-e2e.test.js && node test/erros.test.js && node test/capability-pack.test.js && node test/byok.test.js && node test/conhecimento.test.js && node test/policy.test.js && node test/temas.test.js && node test/skill-index.test.js"
10
10
  },
11
11
  "files": [
12
12
  "bin",