@leg3ndy/otto-bridge 1.0.2 → 1.0.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 CHANGED
@@ -15,7 +15,7 @@ Para o estado atual da arquitetura, capacidades entregues, limitacoes e roadmap
15
15
 
16
16
  Para o corte de arquitetura do `0.9.0`, veja [`leg3ndy-ai-backend/docs/OTTO_BRIDGE_0_9_0_RELEASE.md`](../leg3ndy-ai-backend/docs/OTTO_BRIDGE_0_9_0_RELEASE.md).
17
17
 
18
- Para a release atual `1.0.2`, com runtime agentico formal, hub terminal refinado e console alinhado ao Otto da web, veja [`leg3ndy-ai-backend/docs/OTTO_BRIDGE_1_0_2_PATCH.md`](../leg3ndy-ai-backend/docs/OTTO_BRIDGE_1_0_2_PATCH.md).
18
+ Para a release atual `1.0.3`, com console terminal azul da marca, seletor `Fast/Thinking` e rendering estruturado de resultados locais, veja [`leg3ndy-ai-backend/docs/OTTO_BRIDGE_1_0_3_PATCH.md`](../leg3ndy-ai-backend/docs/OTTO_BRIDGE_1_0_3_PATCH.md).
19
19
 
20
20
  ## Distribuicao
21
21
 
@@ -38,14 +38,14 @@ Enquanto o pacote nao estiver publicado, voce pode gerar um tarball local:
38
38
 
39
39
  ```bash
40
40
  npm pack
41
- npm install -g ./leg3ndy-otto-bridge-1.0.2.tgz
41
+ npm install -g ./leg3ndy-otto-bridge-1.0.3.tgz
42
42
  ```
43
43
 
44
- Na linha `1.0.2`, `playwright` segue como dependencia obrigatoria no `otto-bridge`. O primeiro `npm install -g @leg3ndy/otto-bridge` pode demorar mais porque instala o browser persistente usado pelo WhatsApp Web e pelos fluxos web em background do bridge.
44
+ Na linha `1.0.3`, `playwright` segue como dependencia obrigatoria no `otto-bridge`. O primeiro `npm install -g @leg3ndy/otto-bridge` pode demorar mais porque instala o browser persistente usado pelo WhatsApp Web e pelos fluxos web em background do bridge.
45
45
 
46
- No macOS, a linha `1.0.2` usa o provider `macos-helper`, um helper `WKWebView` sem Dock para o WhatsApp Web. O helper sobe com user-agent de Chrome moderno para evitar o bloqueio do WhatsApp ao detectar Safari/WebKit. O runtime antigo com Chromium/Playwright fica disponivel apenas como override explicito via `OTTO_BRIDGE_WHATSAPP_RUNTIME_PROVIDER=embedded-playwright`.
46
+ No macOS, a linha `1.0.3` usa o provider `macos-helper`, um helper `WKWebView` sem Dock para o WhatsApp Web. O helper sobe com user-agent de Chrome moderno para evitar o bloqueio do WhatsApp ao detectar Safari/WebKit. O runtime antigo com Chromium/Playwright fica disponivel apenas como override explicito via `OTTO_BRIDGE_WHATSAPP_RUNTIME_PROVIDER=embedded-playwright`.
47
47
 
48
- No nivel arquitetural, o `0.9.0` marcou a mudanca de papel do bridge: ele publica tools e resultados estruturados para o Otto, em vez de injetar resposta pronta como caminho principal do chat. O `1.0.0` oficializou isso como runtime agentico; o `1.0.2` consolida o hub terminal como fluxo principal, esconde aliases legados da superfície pública e alinha o console ao comportamento normal do Otto antes de fazer handoff local.
48
+ No nivel arquitetural, o `0.9.0` marcou a mudanca de papel do bridge: ele publica tools e resultados estruturados para o Otto, em vez de injetar resposta pronta como caminho principal do chat. O `1.0.0` oficializou isso como runtime agentico; o `1.0.3` consolida o hub terminal como fluxo principal, esconde aliases legados da superfície pública, alinha o console ao comportamento normal do Otto antes de fazer handoff local e melhora a leitura visual/estruturada do resultado no proprio terminal.
49
49
 
50
50
  ## Publicacao
51
51
 
@@ -137,9 +137,17 @@ otto-bridge console
137
137
 
138
138
  O console usa a mesma sessão local já ligada pelo `otto-bridge`, envia prompts naturais ao backend usando `device_token`, respeita quota/plano do usuário e só vira handoff local quando o pedido realmente tiver cara de ação no computador. Quando houver `device_job`, ele acompanha polling e resolve `confirm_required` no terminal.
139
139
 
140
+ Dentro do console, use:
141
+
142
+ - `/model fast` para `OttoAI Fast`
143
+ - `/model thinking` para `OttoAI Thinking`
144
+ - `/status` para ver detalhes técnicos do bridge e do runtime
145
+
146
+ Quando o handoff local devolver resultado estruturado, o CLI agora mostra inline a listagem de arquivos e o conteúdo de `read_file`, em vez de só resumir que executou a tarefa.
147
+
140
148
  ### WhatsApp Web em background
141
149
 
142
- Fluxo recomendado na linha `1.0.2`:
150
+ Fluxo recomendado na linha `1.0.3`:
143
151
 
144
152
  ```bash
145
153
  otto-bridge extensions --install whatsappweb
@@ -149,13 +157,13 @@ otto-bridge extensions --status whatsappweb
149
157
 
150
158
  O setup agora abre o login do WhatsApp Web no helper/background browser do proprio bridge. Depois do QR code, o Otto usa a sessao local em background, sem depender de aba visivel no Safari.
151
159
 
152
- Contrato da linha `1.0.2`:
160
+ Contrato da linha `1.0.3`:
153
161
 
154
162
  - `otto-bridge extensions --setup whatsappweb`: autentica a sessao uma vez
155
163
  - `otto-bridge`: mantem o browser persistente do WhatsApp vivo em background enquanto o runtime do hub estiver ativo, sem depender de uma aba aberta no Safari
156
164
  - ao fechar o `otto-bridge`: o browser em background e desligado, mas a sessao local fica lembrada para o proximo boot
157
165
 
158
- ## Handoff rapido da linha 1.0.2
166
+ ## Handoff rapido da linha 1.0.3
159
167
 
160
168
  Ja fechado no codigo:
161
169
 
@@ -12,9 +12,12 @@ const ANSI = {
12
12
  reset: "\u001b[0m",
13
13
  dim: "\u001b[2m",
14
14
  bold: "\u001b[1m",
15
- coral: "\u001b[38;5;216m",
15
+ italic: "\u001b[3m",
16
+ brandBlue: "\u001b[38;2;0;119;208m",
16
17
  blue: "\u001b[38;5;111m",
17
18
  teal: "\u001b[38;5;80m",
19
+ slate: "\u001b[38;5;245m",
20
+ slateItalic: "\u001b[38;5;245m\u001b[3m",
18
21
  amber: "\u001b[38;5;221m",
19
22
  red: "\u001b[38;5;203m",
20
23
  green: "\u001b[38;5;114m",
@@ -29,6 +32,22 @@ const OTTOAI_BANNER = [
29
32
  " ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝",
30
33
  ];
31
34
  const CLI_EXIT_SENTINEL = "__OTTO_BRIDGE_CLI_EXIT__";
35
+ const CLI_MODEL_REGISTRY = {
36
+ fast: {
37
+ label: "OttoAI Fast",
38
+ requestModel: "deepseek-chat",
39
+ aliases: ["fast", "chat", "default", "ottoai fast", "otto fast"],
40
+ },
41
+ thinking: {
42
+ label: "OttoAI Thinking",
43
+ requestModel: "deepseek-reasoner",
44
+ aliases: ["thinking", "reasoner", "think", "ottoai thinking", "otto thinking"],
45
+ },
46
+ };
47
+ const MAX_RENDERED_LIST_ENTRIES = 28;
48
+ const MAX_RENDERED_LIST_ENTRIES_COMPACT = 10;
49
+ const MAX_RENDERED_FILE_CHARS = 6_000;
50
+ const MAX_RENDERED_FILE_CHARS_COMPACT = 1_400;
32
51
  class CliRuntimeSession {
33
52
  config;
34
53
  runtime = null;
@@ -157,14 +176,14 @@ function supportsAnsi() {
157
176
  }
158
177
  function renderBanner() {
159
178
  const enabled = supportsAnsi();
160
- const lines = OTTOAI_BANNER.map((line) => style(line, ANSI.coral, enabled));
179
+ const lines = OTTOAI_BANNER.map((line) => style(line, ANSI.brandBlue, enabled));
161
180
  const title = `${BRIDGE_PACKAGE_NAME} v${BRIDGE_VERSION}`;
162
181
  const subtitle = "Paired local runtime and terminal console";
163
182
  return [
164
183
  lines.join("\n"),
165
184
  "",
166
- `${style("OTTO BRIDGE", ANSI.blue, enabled)} ${style(title, ANSI.white, enabled)}`,
167
- `${style(subtitle, ANSI.dim, enabled)}`,
185
+ `${style("OTTO BRIDGE", ANSI.brandBlue, enabled)} ${style(title, ANSI.white, enabled)}`,
186
+ `${style(subtitle, ANSI.slate, enabled)}`,
168
187
  ].join("\n");
169
188
  }
170
189
  function createCliExitError() {
@@ -182,11 +201,14 @@ function isReadlineClosedError(error) {
182
201
  }
183
202
  function printSection(title) {
184
203
  const enabled = supportsAnsi();
185
- console.log(`\n${style(title, ANSI.blue, enabled)}`);
204
+ console.log(`\n${style(title, ANSI.brandBlue, enabled)}`);
186
205
  }
187
206
  function printMuted(message) {
188
207
  console.log(style(message, ANSI.dim, supportsAnsi()));
189
208
  }
209
+ function printSoft(message) {
210
+ console.log(style(message, ANSI.slateItalic, supportsAnsi()));
211
+ }
190
212
  function printSuccess(message) {
191
213
  console.log(style(message, ANSI.green, supportsAnsi()));
192
214
  }
@@ -206,6 +228,40 @@ function truncate(text, max = 180) {
206
228
  }
207
229
  return `${value.slice(0, Math.max(0, max - 1)).trimEnd()}…`;
208
230
  }
231
+ function humanFileSize(value) {
232
+ const size = typeof value === "number" ? value : Number(value);
233
+ if (!Number.isFinite(size) || size < 0) {
234
+ return "";
235
+ }
236
+ if (size < 1024) {
237
+ return `${size} B`;
238
+ }
239
+ if (size < 1024 * 1024) {
240
+ return `${(size / 1024).toFixed(1)} KB`;
241
+ }
242
+ if (size < 1024 * 1024 * 1024) {
243
+ return `${(size / (1024 * 1024)).toFixed(1)} MB`;
244
+ }
245
+ return `${(size / (1024 * 1024 * 1024)).toFixed(1)} GB`;
246
+ }
247
+ function getCliModelLabel(mode) {
248
+ return CLI_MODEL_REGISTRY[mode].label;
249
+ }
250
+ function getCliModelRequestModel(mode) {
251
+ return CLI_MODEL_REGISTRY[mode].requestModel;
252
+ }
253
+ export function resolveCliModelMode(value) {
254
+ const normalized = normalizeText(value).toLowerCase();
255
+ if (!normalized) {
256
+ return null;
257
+ }
258
+ for (const [mode, definition] of Object.entries(CLI_MODEL_REGISTRY)) {
259
+ if (definition.aliases.includes(normalized)) {
260
+ return mode;
261
+ }
262
+ }
263
+ return null;
264
+ }
209
265
  function delay(ms) {
210
266
  return new Promise((resolve) => setTimeout(resolve, ms));
211
267
  }
@@ -234,7 +290,7 @@ async function question(rl, prompt) {
234
290
  async function ask(rl, label, options) {
235
291
  const defaultValue = normalizeText(options?.defaultValue);
236
292
  const suffix = defaultValue ? ` ${style(`[${defaultValue}]`, ANSI.dim, supportsAnsi())}` : "";
237
- const answer = normalizeText(await question(rl, `${style("›", ANSI.coral, supportsAnsi())} ${label}${suffix}: `));
293
+ const answer = normalizeText(await question(rl, `${style("›", ANSI.brandBlue, supportsAnsi())} ${label}${suffix}: `));
238
294
  if (answer) {
239
295
  return answer;
240
296
  }
@@ -247,7 +303,7 @@ async function ask(rl, label, options) {
247
303
  return await ask(rl, label, options);
248
304
  }
249
305
  async function askYesNo(rl, promptText, defaultValue = true) {
250
- const answer = normalizeText(await question(rl, `${style("?", ANSI.blue, supportsAnsi())} ${promptText} ${style(defaultValue ? "[Y/n]" : "[y/N]", ANSI.dim, supportsAnsi())}: `)).toLowerCase();
306
+ const answer = normalizeText(await question(rl, `${style("?", ANSI.brandBlue, supportsAnsi())} ${promptText} ${style(defaultValue ? "[Y/n]" : "[y/N]", ANSI.dim, supportsAnsi())}: `)).toLowerCase();
251
307
  if (!answer) {
252
308
  return defaultValue;
253
309
  }
@@ -259,8 +315,8 @@ async function pauseForEnter(rl, message = "Pressione Enter para continuar") {
259
315
  async function chooseExecutor(rl, current) {
260
316
  const defaultType = current?.type || resolveExecutorConfig().type;
261
317
  console.log([
262
- `${style("1.", ANSI.coral, supportsAnsi())} native-macos ${style("(Mac real, runtime local)", ANSI.dim, supportsAnsi())}`,
263
- `${style("2.", ANSI.coral, supportsAnsi())} mock ${style("(ambiente de teste)", ANSI.dim, supportsAnsi())}`,
318
+ `${style("1.", ANSI.brandBlue, supportsAnsi())} native-macos ${style("(Mac real, runtime local)", ANSI.dim, supportsAnsi())}`,
319
+ `${style("2.", ANSI.brandBlue, supportsAnsi())} mock ${style("(ambiente de teste)", ANSI.dim, supportsAnsi())}`,
264
320
  ].join("\n"));
265
321
  const selection = await ask(rl, "Executor", {
266
322
  defaultValue: defaultType === "mock" ? "2" : "1",
@@ -322,16 +378,16 @@ function extractConfirmationPrompt(job) {
322
378
  }
323
379
  function renderStatusOverview(config, runtimeSession) {
324
380
  return [
325
- `${style("Device", ANSI.blue, supportsAnsi())}: ${config.deviceName}`,
326
- `${style("Device ID", ANSI.blue, supportsAnsi())}: ${config.deviceId}`,
327
- `${style("API", ANSI.blue, supportsAnsi())}: ${config.apiBaseUrl}`,
328
- `${style("Executor", ANSI.blue, supportsAnsi())}: ${config.executor.type}`,
329
- `${style("Approval", ANSI.blue, supportsAnsi())}: ${config.approvalMode}`,
330
- `${style("Runtime", ANSI.blue, supportsAnsi())}: ${runtimeSession?.getStatusLabel() || "offline"}`,
381
+ `${style("Device", ANSI.brandBlue, supportsAnsi())}: ${config.deviceName}`,
382
+ `${style("Device ID", ANSI.brandBlue, supportsAnsi())}: ${config.deviceId}`,
383
+ `${style("API", ANSI.brandBlue, supportsAnsi())}: ${config.apiBaseUrl}`,
384
+ `${style("Executor", ANSI.brandBlue, supportsAnsi())}: ${config.executor.type}`,
385
+ `${style("Approval", ANSI.brandBlue, supportsAnsi())}: ${config.approvalMode}`,
386
+ `${style("Runtime", ANSI.brandBlue, supportsAnsi())}: ${runtimeSession?.getStatusLabel() || "offline"}`,
331
387
  ...(runtimeSession?.getStatusDetail()
332
- ? [`${style("Runtime note", ANSI.blue, supportsAnsi())}: ${runtimeSession.getStatusDetail()}`]
388
+ ? [`${style("Runtime note", ANSI.brandBlue, supportsAnsi())}: ${runtimeSession.getStatusDetail()}`]
333
389
  : []),
334
- `${style("Config", ANSI.blue, supportsAnsi())}: ${getBridgeConfigPath()}`,
390
+ `${style("Config", ANSI.brandBlue, supportsAnsi())}: ${getBridgeConfigPath()}`,
335
391
  ];
336
392
  }
337
393
  function renderRuntimeHeadline(runtimeSession) {
@@ -356,49 +412,177 @@ function renderRuntimeHeadline(runtimeSession) {
356
412
  function padRight(text, width) {
357
413
  return text.length >= width ? text : `${text}${" ".repeat(width - text.length)}`;
358
414
  }
415
+ function styleCardLine(line, tone, enabled) {
416
+ if (!enabled) {
417
+ return line;
418
+ }
419
+ if (tone === "title") {
420
+ return `${ANSI.bold}${ANSI.brandBlue}${line}${ANSI.reset}`;
421
+ }
422
+ if (tone === "muted") {
423
+ return `${ANSI.slateItalic}${line}${ANSI.reset}`;
424
+ }
425
+ return `${ANSI.white}${line}${ANSI.reset}`;
426
+ }
359
427
  function renderInfoCard(lines) {
360
- const normalized = lines.map((line) => truncate(line, 78));
361
- const width = Math.max(38, ...normalized.map((line) => line.length));
428
+ const enabled = supportsAnsi();
429
+ const normalized = lines.map((line) => ({
430
+ text: truncate(line.text, 82),
431
+ tone: line.tone,
432
+ }));
433
+ const width = Math.max(44, ...normalized.map((line) => line.text.length));
434
+ const top = style(`┌${"─".repeat(width + 2)}┐`, ANSI.brandBlue, enabled);
435
+ const bottom = style(`└${"─".repeat(width + 2)}┘`, ANSI.brandBlue, enabled);
362
436
  return [
363
- `+${"-".repeat(width + 2)}+`,
364
- ...normalized.map((line) => `| ${padRight(line, width)} |`),
365
- `+${"-".repeat(width + 2)}+`,
437
+ top,
438
+ ...normalized.map((line) => {
439
+ const border = style("", ANSI.brandBlue, enabled);
440
+ const content = styleCardLine(padRight(line.text, width), line.tone, enabled);
441
+ return `${border} ${content} ${border}`;
442
+ }),
443
+ bottom,
366
444
  ].join("\n");
367
445
  }
368
- function printHubScreen(runtimeSession) {
446
+ function buildWelcomeCard(runtimeSession, modelMode) {
447
+ return [
448
+ { text: "Welcome to OttoAI", tone: "title" },
449
+ { text: "", tone: "muted" },
450
+ { text: "/help inside the console, /status for bridge details", tone: "muted" },
451
+ { text: "", tone: "muted" },
452
+ { text: `model: ${getCliModelLabel(modelMode)}`, tone: "muted" },
453
+ { text: `cwd: ${process.cwd()}`, tone: "muted" },
454
+ { text: renderRuntimeHeadline(runtimeSession), tone: "muted" },
455
+ ];
456
+ }
457
+ function printHubScreen(runtimeSession, modelMode) {
369
458
  console.clear();
370
459
  console.log(renderBanner());
371
460
  console.log("");
372
- console.log(renderInfoCard([
373
- "Welcome to OttoAI",
374
- "/status for bridge details, /help inside the console",
375
- "model: OttoAI local console",
376
- `cwd: ${process.cwd()}`,
377
- renderRuntimeHeadline(runtimeSession),
378
- ]));
379
- }
380
- function printConsoleScreen(runtimeSession) {
461
+ console.log(renderInfoCard(buildWelcomeCard(runtimeSession, modelMode)));
462
+ }
463
+ function printConsoleScreen(runtimeSession, modelMode) {
381
464
  console.clear();
382
465
  console.log(renderBanner());
383
466
  console.log("");
384
- console.log(renderInfoCard([
385
- "Welcome to OttoAI",
386
- "/help for commands, /status for bridge details",
387
- "model: OttoAI local console",
388
- `cwd: ${process.cwd()}`,
389
- renderRuntimeHeadline(runtimeSession),
390
- ]));
467
+ console.log(renderInfoCard(buildWelcomeCard(runtimeSession, modelMode)));
391
468
  console.log("");
469
+ console.log(style("Peça algo ao Otto", `${ANSI.bold}${ANSI.white}`, supportsAnsi()));
470
+ printSoft("Comandos: /help, /model [fast|thinking], /status, /clear, /exit");
471
+ console.log("");
472
+ }
473
+ function renderPromptFrameLine(width, edgeLeft, edgeRight) {
474
+ return style(`${edgeLeft}${"─".repeat(width)}${edgeRight}`, ANSI.brandBlue, supportsAnsi());
392
475
  }
393
476
  async function askConsoleInput(rl) {
394
- return normalizeText(await question(rl, `${style(">", ANSI.white, supportsAnsi())} `));
477
+ const availableWidth = Number(output.columns || 90);
478
+ const frameWidth = Math.max(36, Math.min(availableWidth - 2, 88));
479
+ console.log(renderPromptFrameLine(frameWidth, "┌", "┐"));
480
+ const answer = normalizeText(await question(rl, `${style("│", ANSI.brandBlue, supportsAnsi())} ${style(">", `${ANSI.bold}${ANSI.white}`, supportsAnsi())} `));
481
+ console.log(renderPromptFrameLine(frameWidth, "└", "┘"));
482
+ return answer;
395
483
  }
396
484
  function printAssistantMessage(message) {
397
485
  const text = normalizeText(message);
398
486
  if (!text) {
399
487
  return;
400
488
  }
401
- console.log(`${style("•", ANSI.coral, supportsAnsi())} ${text}`);
489
+ console.log(`${style("•", ANSI.brandBlue, supportsAnsi())} ${text}`);
490
+ }
491
+ function extractJobOutcome(job) {
492
+ const result = job.result && typeof job.result === "object"
493
+ ? job.result
494
+ : {};
495
+ return result.outcome && typeof result.outcome === "object"
496
+ ? result.outcome
497
+ : {};
498
+ }
499
+ function outcomePathLabel(outcome) {
500
+ return normalizeText(outcome.resolved_path || outcome.path);
501
+ }
502
+ function collectOutcomeFileContent(outcome) {
503
+ const directContent = outcome.content;
504
+ if (typeof directContent === "string" && directContent.trim()) {
505
+ return directContent;
506
+ }
507
+ const chunks = Array.isArray(outcome.content_chunks) ? outcome.content_chunks : [];
508
+ return chunks
509
+ .filter((item) => item && typeof item === "object")
510
+ .map((item) => String(item.text || ""))
511
+ .filter((item) => item.length > 0)
512
+ .join("");
513
+ }
514
+ function formatDirectoryEntry(entry) {
515
+ const name = normalizeText(entry.name) || "(sem nome)";
516
+ const kind = normalizeText(entry.kind).toLowerCase();
517
+ const prefix = kind === "directory" ? "[dir]" : "[file]";
518
+ const size = kind === "file" ? humanFileSize(entry.size_bytes) : "";
519
+ return `${prefix} ${name}${size ? ` (${size})` : ""}`;
520
+ }
521
+ export function renderStructuredOutcome(job, options) {
522
+ const compact = Boolean(options?.compact);
523
+ const outcome = extractJobOutcome(job);
524
+ const actionType = normalizeText(outcome.action_type).toLowerCase();
525
+ const lines = [];
526
+ if (actionType === "list_files") {
527
+ const entries = Array.isArray(outcome.entries)
528
+ ? outcome.entries.filter((item) => item && typeof item === "object")
529
+ : [];
530
+ const target = outcomePathLabel(outcome);
531
+ const itemCount = outcome.listed_item_count ?? entries.length;
532
+ if (target) {
533
+ lines.push(target);
534
+ }
535
+ if (itemCount) {
536
+ lines.push(`${itemCount} item(ns) encontrados`);
537
+ }
538
+ if (lines.length) {
539
+ lines.push("");
540
+ }
541
+ const maxEntries = compact ? MAX_RENDERED_LIST_ENTRIES_COMPACT : MAX_RENDERED_LIST_ENTRIES;
542
+ entries.slice(0, maxEntries).forEach((entry) => {
543
+ lines.push(formatDirectoryEntry(entry));
544
+ });
545
+ if (entries.length > maxEntries) {
546
+ lines.push(`... +${entries.length - maxEntries} item(ns)`);
547
+ }
548
+ }
549
+ if (actionType === "read_file") {
550
+ const target = outcomePathLabel(outcome);
551
+ const binaryNotice = normalizeText(outcome.binary_notice);
552
+ const content = collectOutcomeFileContent(outcome);
553
+ const maxChars = compact ? MAX_RENDERED_FILE_CHARS_COMPACT : MAX_RENDERED_FILE_CHARS;
554
+ if (target) {
555
+ lines.push(target);
556
+ lines.push("");
557
+ }
558
+ if (binaryNotice) {
559
+ lines.push(binaryNotice);
560
+ }
561
+ else if (content) {
562
+ const truncatedContent = content.length > maxChars
563
+ ? `${content.slice(0, maxChars).trimEnd()}\n\n[... conteúdo truncado ...]`
564
+ : content;
565
+ lines.push(truncatedContent);
566
+ }
567
+ }
568
+ return lines.join("\n").trim();
569
+ }
570
+ function printStructuredOutcome(job) {
571
+ const rendered = renderStructuredOutcome(job);
572
+ if (!rendered) {
573
+ return;
574
+ }
575
+ console.log(rendered);
576
+ }
577
+ function buildConversationSummary(summary, job) {
578
+ const rendered = renderStructuredOutcome(job, { compact: true });
579
+ if (!rendered) {
580
+ return summary;
581
+ }
582
+ if (!summary) {
583
+ return rendered;
584
+ }
585
+ return `${summary}\n${rendered}`.slice(0, 4_000).trim();
402
586
  }
403
587
  async function printExtensionsOverview(config) {
404
588
  printSection("Extensions");
@@ -497,18 +681,19 @@ async function followConsoleJob(rl, config, jobId) {
497
681
  ? "Execução local falhou."
498
682
  : "Execução local cancelada.");
499
683
  printAssistantMessage(summary);
500
- return summary;
684
+ printStructuredOutcome(job);
685
+ return buildConversationSummary(summary, job);
501
686
  }
502
687
  await delay(1400);
503
688
  }
504
689
  }
505
690
  async function runOttoConsole(rl, config, runtimeSession, options) {
506
- printConsoleScreen(runtimeSession);
507
- printMuted("Digite sua mensagem normalmente. O handoff local só acontece quando fizer sentido.");
691
+ let activeModel = "fast";
692
+ printConsoleScreen(runtimeSession, activeModel);
508
693
  const sessionId = randomUUID();
509
694
  const conversation = [];
510
695
  const printConsoleHelp = () => {
511
- printMuted("Comandos: /help, /clear, /status, /exit");
696
+ printSoft("Comandos: /help, /model [fast|thinking], /status, /clear, /exit");
512
697
  };
513
698
  const handlePrompt = async (promptText) => {
514
699
  const normalizedPrompt = normalizeText(promptText);
@@ -521,11 +706,32 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
521
706
  }
522
707
  if (normalizedPrompt === "/clear") {
523
708
  conversation.splice(0, conversation.length);
524
- printConsoleScreen(runtimeSession);
709
+ printConsoleScreen(runtimeSession, activeModel);
525
710
  printMuted("Contexto local do console limpo.");
526
711
  return;
527
712
  }
713
+ if (normalizedPrompt === "/model") {
714
+ printMuted(`Modelo ativo: ${getCliModelLabel(activeModel)}.`);
715
+ printSoft("Use /model fast ou /model thinking para trocar.");
716
+ return;
717
+ }
718
+ if (normalizedPrompt.startsWith("/model ")) {
719
+ const nextMode = resolveCliModelMode(normalizedPrompt.slice("/model ".length));
720
+ if (!nextMode) {
721
+ printWarning("Modelo inválido. Use /model fast ou /model thinking.");
722
+ return;
723
+ }
724
+ if (nextMode === activeModel) {
725
+ printMuted(`Modelo já está em ${getCliModelLabel(activeModel)}.`);
726
+ return;
727
+ }
728
+ activeModel = nextMode;
729
+ printConsoleScreen(runtimeSession, activeModel);
730
+ printSuccess(`Modelo ativo: ${getCliModelLabel(activeModel)}.`);
731
+ return;
732
+ }
528
733
  if (normalizedPrompt === "/status") {
734
+ console.log(`${style("Model", ANSI.brandBlue, supportsAnsi())}: ${getCliModelLabel(activeModel)}`);
529
735
  renderStatusOverview(config, runtimeSession).forEach((line) => console.log(line));
530
736
  const runtimeFailure = runtimeSession.getLastError();
531
737
  if (runtimeFailure) {
@@ -545,6 +751,7 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
545
751
  let handoffPayload = null;
546
752
  await streamDeviceCliChat(config, {
547
753
  messages: conversation,
754
+ model: getCliModelRequestModel(activeModel),
548
755
  session_id: sessionId,
549
756
  }, async (event) => {
550
757
  const chunkType = normalizeText(event.chunk_type).toLowerCase();
@@ -569,7 +776,7 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
569
776
  return;
570
777
  }
571
778
  if (!assistantPrefixPrinted) {
572
- output.write(`${style("•", ANSI.coral, supportsAnsi())} `);
779
+ output.write(`${style("•", ANSI.brandBlue, supportsAnsi())} `);
573
780
  assistantPrefixPrinted = true;
574
781
  }
575
782
  output.write(contentChunk);
@@ -630,15 +837,15 @@ async function pickHomeChoice(rl, paired) {
630
837
  printSection("Home");
631
838
  const options = paired
632
839
  ? [
633
- `${style("1.", ANSI.coral, supportsAnsi())} Otto Console`,
634
- `${style("2.", ANSI.coral, supportsAnsi())} Setup / parear novamente`,
635
- `${style("3.", ANSI.coral, supportsAnsi())} Status detalhado`,
636
- `${style("4.", ANSI.coral, supportsAnsi())} Extensões instaladas`,
637
- `${style("5.", ANSI.coral, supportsAnsi())} Sair`,
840
+ `${style("1.", ANSI.brandBlue, supportsAnsi())} Otto Console`,
841
+ `${style("2.", ANSI.brandBlue, supportsAnsi())} Setup / parear novamente`,
842
+ `${style("3.", ANSI.brandBlue, supportsAnsi())} Status detalhado`,
843
+ `${style("4.", ANSI.brandBlue, supportsAnsi())} Extensões instaladas`,
844
+ `${style("5.", ANSI.brandBlue, supportsAnsi())} Sair`,
638
845
  ]
639
846
  : [
640
- `${style("1.", ANSI.coral, supportsAnsi())} Pairing setup`,
641
- `${style("2.", ANSI.coral, supportsAnsi())} Sair`,
847
+ `${style("1.", ANSI.brandBlue, supportsAnsi())} Pairing setup`,
848
+ `${style("2.", ANSI.brandBlue, supportsAnsi())} Sair`,
642
849
  ];
643
850
  console.log(options.join("\n"));
644
851
  const answer = await ask(rl, "Escolha");
@@ -661,7 +868,7 @@ export async function launchInteractiveCli(options) {
661
868
  try {
662
869
  let config = await loadBridgeConfig();
663
870
  if (!config) {
664
- printHubScreen(null);
871
+ printHubScreen(null, "fast");
665
872
  const setup = await runSetupWizard(rl, options);
666
873
  config = setup.config;
667
874
  if (config && setup.openConsole) {
@@ -676,7 +883,7 @@ export async function launchInteractiveCli(options) {
676
883
  runtimeSession = runtimeSession || new CliRuntimeSession(config);
677
884
  await runtimeSession.ensureStarted();
678
885
  for (;;) {
679
- printHubScreen(runtimeSession);
886
+ printHubScreen(runtimeSession, "fast");
680
887
  const choice = await pickHomeChoice(rl, true);
681
888
  if (choice === "exit") {
682
889
  break;
@@ -726,7 +933,7 @@ export async function runSetupCommand(options) {
726
933
  const rl = await createPromptInterface();
727
934
  let runtimeSession = null;
728
935
  try {
729
- printHubScreen(null);
936
+ printHubScreen(null, "fast");
730
937
  const setup = await runSetupWizard(rl, options);
731
938
  if (setup.config && setup.openConsole) {
732
939
  runtimeSession = new CliRuntimeSession(setup.config);
@@ -748,7 +955,7 @@ export async function runConsoleCommand(initialPrompt) {
748
955
  const rl = await createPromptInterface();
749
956
  let runtimeSession = null;
750
957
  try {
751
- printHubScreen(null);
958
+ printHubScreen(null, "fast");
752
959
  let config = await loadBridgeConfig();
753
960
  if (!config) {
754
961
  const setup = await runSetupWizard(rl);
package/dist/types.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export const BRIDGE_CONFIG_VERSION = 1;
2
- export const BRIDGE_VERSION = "1.0.2";
2
+ export const BRIDGE_VERSION = "1.0.3";
3
3
  export const BRIDGE_PACKAGE_NAME = "@leg3ndy/otto-bridge";
4
4
  export const DEFAULT_API_BASE_URL = "http://localhost:8000";
5
5
  export const DEFAULT_POLL_INTERVAL_MS = 3000;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leg3ndy/otto-bridge",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Local companion for Otto Bridge device pairing and WebSocket runtime.",
@@ -24,7 +24,7 @@ if (!existsSync(mainPath)) {
24
24
  process.exit(0);
25
25
  }
26
26
 
27
- console.log("\n[otto-bridge] Welcome to OTTOAI 1.0.2");
27
+ console.log("\n[otto-bridge] Welcome to OTTOAI 1.0.3");
28
28
  console.log("[otto-bridge] Vamos iniciar o setup interativo do bridge.\n");
29
29
 
30
30
  const result = spawnSync(process.execPath, [mainPath, "setup", "--postinstall"], {