terminal-smart-cli 0.97.2 → 0.97.3
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 +1 -0
- package/bin/ts.js +95 -0
- package/lib/i18n.js +2 -0
- package/lib/intelligence-core.js +44 -7
- package/package.json +1 -1
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) => {
|
|
@@ -2757,6 +2848,7 @@ function recallCmd(args) {
|
|
|
2757
2848
|
case 'login': return login();
|
|
2758
2849
|
case 'logout': return logout();
|
|
2759
2850
|
case 'chat': case 'conversa': return chatRepl();
|
|
2851
|
+
case 'imagem': case 'image': case 'img': return imageCmd(POS.slice(1));
|
|
2760
2852
|
case 'quem': case 'whoami': return quem();
|
|
2761
2853
|
case 'nova': case 'new': return nova();
|
|
2762
2854
|
case 'run': return runCmd(POS.slice(1));
|
|
@@ -2806,6 +2898,9 @@ function recallCmd(args) {
|
|
|
2806
2898
|
// pipe/--json ficam determinísticos no chat (scripts não levam surpresa)
|
|
2807
2899
|
const text = POS.join(' ');
|
|
2808
2900
|
if (process.stdin.isTTY && !JSON_OUT && cfg.token) {
|
|
2901
|
+
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)) {
|
|
2902
|
+
return imageCmd([text]);
|
|
2903
|
+
}
|
|
2809
2904
|
const r = await router.route(text, cfg.token);
|
|
2810
2905
|
if (r.dest === 'agente') { console.log(' ' + C.cyan('⚙') + ' ' + C.dim(T.route_agent)); return agentCmd([text]); }
|
|
2811
2906
|
if (r.dest === 'run') { console.log(' ' + C.indigo('◆') + ' ' + C.dim(T.route_run)); return runCmd([text]); }
|
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'],
|
package/lib/intelligence-core.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
const crypto = require('crypto');
|
|
10
10
|
|
|
11
11
|
const INTELLIGENCE_CONTRACT = 1;
|
|
12
|
-
const PRICE_REVISION = '2026-07-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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 };
|