@leg3ndy/otto-bridge 1.1.1 → 1.1.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/releases/OTTO_BRIDGE_0_9_0_RELEASE.md`](../leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_0_9_0_RELEASE.md).
17
17
 
18
- Para o patch atual `1.1.1`, veja [`leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_1_1_PATCH.md`](../leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_1_1_PATCH.md). Para o corte funcional da linha `1.1.0`, veja [`leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_1_0_RELEASE.md`](../leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_1_0_RELEASE.md).
18
+ Para o patch atual `1.1.3`, veja [`leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_1_3_PATCH.md`](../leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_1_3_PATCH.md). Para o corte funcional da linha `1.1.0`, veja [`leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_1_0_RELEASE.md`](../leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_1_0_RELEASE.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.1.1.tgz
41
+ npm install -g ./leg3ndy-otto-bridge-1.1.3.tgz
42
42
  ```
43
43
 
44
- Na linha `1.1.1`, `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.1.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.1.1` 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.1.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.1.1` adiciona a camada workspace-first com rail de coding, trust/policy por workspace, source control first-class, working set persistido e grounding remoto por repositório.
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.1.3` mantem a camada workspace-first com rail de coding, trust/policy por workspace, source control first-class, working set persistido e grounding remoto por repositório, enquanto troca o `Otto Console` para scrollback real, menu com setas e palette de comandos ao digitar `/`.
49
49
 
50
50
  ## Publicacao
51
51
 
@@ -152,8 +152,9 @@ Dentro do console, use:
152
152
  - `/workspace attach <path>` para anexar uma pasta/repo novo pelo helper autenticado
153
153
  - `/workspace use <id|n>` para fixar um workspace anexado na sessão atual
154
154
  - `/workspace clear` para limpar o binding atual do chat/sessão
155
+ - `/new` para iniciar uma nova sessão e limpar o contexto local do console
155
156
 
156
- No TTY, o composer agora fica ancorado no rodapé com placeholder `Peça algo ao Otto`, quebra por largura real do terminal e deixa o transcript do Otto sempre visível acima. Logo abaixo do input, o console mostra o modelo ativo, a barra de uso de contexto/tokens e o modo de aprovação atual; `Shift+Tab` alterna esse modo sem sair do console.
157
+ No TTY, o console agora reaproveita a própria tela do bridge quando você entra no `Otto Console`, em vez de abrir uma segunda viewport logo abaixo do hub. O header é impresso uma vez no topo da sessão, o histórico completo fica salvo no scrollback real do terminal e o conteúdo vai empurrando os blocos para cima naturalmente. O composer continua com placeholder `Peça algo ao Otto`, o footer mantém modelo/tokens/aprovação, e ao digitar `/` o bridge abre uma palette navegável por setas, com `Enter` preenchendo comandos como `/new` para iniciar uma nova sessão local.
157
158
 
158
159
  No modo `OttoAI Thinking`, o terminal agora marca explicitamente o trecho de raciocínio com `Pensando (OttoAI Thinking)` e separa esse bloco da resposta final do Otto.
159
160
 
@@ -169,7 +170,7 @@ Esse comando abre um shell local interativo para instalar extensoes, rodar coman
169
170
 
170
171
  ### WhatsApp Web em background
171
172
 
172
- Fluxo recomendado na linha `1.1.1`:
173
+ Fluxo recomendado na linha `1.1.3`:
173
174
 
174
175
  ```bash
175
176
  otto-bridge extensions --install whatsappweb
@@ -179,13 +180,13 @@ otto-bridge extensions --status whatsappweb
179
180
 
180
181
  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.
181
182
 
182
- Contrato da linha `1.1.1`:
183
+ Contrato da linha `1.1.3`:
183
184
 
184
185
  - `otto-bridge extensions --setup whatsappweb`: autentica a sessao uma vez
185
186
  - `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
186
187
  - ao fechar o `otto-bridge`: o browser em background e desligado, mas a sessao local fica lembrada para o proximo boot
187
188
 
188
- ## Handoff rapido da linha 1.1.1
189
+ ## Handoff rapido da linha 1.1.3
189
190
 
190
191
  Ja fechado no codigo:
191
192
 
@@ -56,11 +56,74 @@ const MAX_RENDERED_LIST_ENTRIES_COMPACT = 10;
56
56
  const MAX_RENDERED_FILE_CHARS = 6_000;
57
57
  const MAX_RENDERED_FILE_CHARS_COMPACT = 1_400;
58
58
  const CONSOLE_PLACEHOLDER = "Peça algo ao Otto";
59
- const CONSOLE_COMMAND_HINT = "/help, /model [fast|thinking], /approval [preview|confirm|trusted], /workspace, /status, /clear, /exit";
59
+ const CONSOLE_COMMAND_HINT = "/help, /model [fast|thinking], /approval [preview|confirm|trusted], /workspace, /status, /clear, /new, /exit";
60
60
  const CONSOLE_COMPOSER_PROMPT_WIDTH = 2;
61
61
  const CONSOLE_COMPOSER_CURSOR_COLUMN = 2;
62
62
  const CONSOLE_EST_CHARS_PER_TOKEN = 4;
63
63
  const CONSOLE_EST_MESSAGE_OVERHEAD_TOKENS = 8;
64
+ const CONSOLE_MENU_HINT = "↑ ↓ navegar • Enter selecionar";
65
+ const CONSOLE_SLASH_SUGGESTIONS = [
66
+ {
67
+ command: "/new",
68
+ insertText: "/new",
69
+ description: "inicia uma nova sessão e limpa o contexto local",
70
+ },
71
+ {
72
+ command: "/help",
73
+ insertText: "/help",
74
+ description: "mostra os comandos disponíveis no console",
75
+ },
76
+ {
77
+ command: "/model fast",
78
+ insertText: "/model fast",
79
+ description: "troca para OttoAI Fast",
80
+ },
81
+ {
82
+ command: "/model thinking",
83
+ insertText: "/model thinking",
84
+ description: "troca para OttoAI Thinking",
85
+ },
86
+ {
87
+ command: "/approval preview",
88
+ insertText: "/approval preview",
89
+ description: "usa permissões padrão no device",
90
+ },
91
+ {
92
+ command: "/approval confirm",
93
+ insertText: "/approval confirm",
94
+ description: "pede confirmação manual por passo",
95
+ },
96
+ {
97
+ command: "/approval trusted",
98
+ insertText: "/approval trusted",
99
+ description: "ativa bypass de permissões no device",
100
+ },
101
+ {
102
+ command: "/workspace",
103
+ insertText: "/workspace",
104
+ description: "mostra o workspace ativo e os anexados",
105
+ },
106
+ {
107
+ command: "/workspace list",
108
+ insertText: "/workspace list",
109
+ description: "lista os workspaces anexados ao device",
110
+ },
111
+ {
112
+ command: "/status",
113
+ insertText: "/status",
114
+ description: "mostra status técnico do bridge e do runtime",
115
+ },
116
+ {
117
+ command: "/clear",
118
+ insertText: "/clear",
119
+ description: "limpa a tela e o contexto local desta sessão",
120
+ },
121
+ {
122
+ command: "/exit",
123
+ insertText: "/exit",
124
+ description: "sai do Otto Console",
125
+ },
126
+ ];
64
127
  class CliRuntimeSession {
65
128
  config;
66
129
  runtime = null;
@@ -238,6 +301,9 @@ function renderBanner() {
238
301
  `${style(subtitle, ANSI.slate, enabled)}`,
239
302
  ].join("\n");
240
303
  }
304
+ function getCardMaxContentWidth() {
305
+ return Math.max(24, Number(output.columns || 96) - 4);
306
+ }
241
307
  function createCliExitError() {
242
308
  return new Error(CLI_EXIT_SENTINEL);
243
309
  }
@@ -384,6 +450,16 @@ function getNextApprovalMode(mode) {
384
450
  }
385
451
  return "preview";
386
452
  }
453
+ export function resolveConsoleSlashSuggestions(value) {
454
+ const normalized = normalizeText(value).toLowerCase();
455
+ if (!normalized.startsWith("/") || normalized.includes("\n")) {
456
+ return [];
457
+ }
458
+ if (normalized === "/") {
459
+ return [...CONSOLE_SLASH_SUGGESTIONS];
460
+ }
461
+ return CONSOLE_SLASH_SUGGESTIONS.filter((item) => item.command.startsWith(normalized));
462
+ }
387
463
  function styleTranscriptLine(text, tone, enabled) {
388
464
  if (!enabled) {
389
465
  return text;
@@ -425,16 +501,30 @@ function buildConsoleFooterStatusLine(width, modelMode, usageTokens, enabled) {
425
501
  const usageRatio = contextLimit > 0 ? usageTokens / contextLimit : 0;
426
502
  const percent = Math.round(Math.max(0, Math.min(usageRatio, 1)) * 100);
427
503
  const barWidth = Math.max(12, Math.min(28, Math.floor(width * 0.18)));
428
- return `${getCliModelLabel(modelMode)} | ${buildConsoleUsageBar(barWidth, usageRatio, enabled)} ${percent}% | ${formatCompactTokenCount(usageTokens)} tokens`;
504
+ const modelLabel = style(getCliModelLabel(modelMode), ANSI.slate, enabled);
505
+ return `${modelLabel} | ${buildConsoleUsageBar(barWidth, usageRatio, enabled)} ${percent}% | ${formatCompactTokenCount(usageTokens)} tokens`;
429
506
  }
430
507
  function buildConsoleFooterApprovalLine(mode, enabled, statusSuffix) {
431
508
  const state = getConsoleApprovalFooterState(mode);
432
509
  const hint = statusSuffix || "Shift+Tab para alternar";
433
510
  return style(`${state.label} (${hint})`, state.tone === "warning" ? ANSI.red : state.tone === "primary" ? ANSI.amber : ANSI.slate, enabled);
434
511
  }
512
+ function styleTranscriptPrefix(text, tone, enabled) {
513
+ if (!enabled || !text) {
514
+ return text;
515
+ }
516
+ if (tone === "assistant") {
517
+ return `${ANSI.bold}${ANSI.brandBlue}${text}${ANSI.reset}`;
518
+ }
519
+ if (tone === "user") {
520
+ return `${ANSI.bold}${ANSI.white}${text}${ANSI.reset}`;
521
+ }
522
+ return text;
523
+ }
435
524
  class ConsoleScreenRenderer {
436
525
  modelMode;
437
526
  approvalMode;
527
+ headerFactory;
438
528
  transcript = [];
439
529
  onResize = () => {
440
530
  this.render();
@@ -444,15 +534,19 @@ class ConsoleScreenRenderer {
444
534
  draftValue = "";
445
535
  conversationMessages = [];
446
536
  approvalStatusSuffix = null;
447
- constructor(modelMode, approvalMode) {
537
+ usingAlternateBuffer = false;
538
+ constructor(modelMode, approvalMode, headerFactory = () => []) {
448
539
  this.modelMode = modelMode;
449
540
  this.approvalMode = approvalMode;
541
+ this.headerFactory = headerFactory;
450
542
  }
451
543
  activate() {
452
544
  if (this.active || !supportsAnsi() || !input.isTTY || !output.isTTY) {
453
545
  return;
454
546
  }
455
547
  this.active = true;
548
+ output.write("\u001b[?1049h");
549
+ this.usingAlternateBuffer = true;
456
550
  output.on("resize", this.onResize);
457
551
  this.render();
458
552
  }
@@ -463,6 +557,10 @@ class ConsoleScreenRenderer {
463
557
  output.off("resize", this.onResize);
464
558
  this.active = false;
465
559
  output.write("\u001b[?25h");
560
+ if (this.usingAlternateBuffer) {
561
+ output.write("\u001b[?1049l");
562
+ this.usingAlternateBuffer = false;
563
+ }
466
564
  }
467
565
  isActive() {
468
566
  return this.active;
@@ -523,7 +621,8 @@ class ConsoleScreenRenderer {
523
621
  const wrapped = sliceByWidth(logicalLine, Math.max(1, width - currentPrefix.length));
524
622
  wrapped.forEach((segment, index) => {
525
623
  const linePrefix = isFirstVisualLine && index === 0 ? prefix : continuationPrefix;
526
- rendered.push(styleTranscriptLine(`${linePrefix}${segment}`, entry.tone, supportsAnsi()));
624
+ const prefixTone = isFirstVisualLine && index === 0 ? entry.prefixTone : undefined;
625
+ rendered.push(`${styleTranscriptPrefix(linePrefix, prefixTone, supportsAnsi())}${styleTranscriptLine(segment, entry.tone, supportsAnsi())}`);
527
626
  });
528
627
  if (logicalLine.length === 0 && wrapped.length === 0) {
529
628
  rendered.push("");
@@ -539,6 +638,7 @@ class ConsoleScreenRenderer {
539
638
  const enabled = supportsAnsi();
540
639
  const width = Math.max(48, Number(output.columns || 96));
541
640
  const height = Math.max(12, Number(output.rows || 24));
641
+ const headerLines = this.headerFactory();
542
642
  const separator = style("─".repeat(width), ANSI.brandBlue, enabled);
543
643
  const composer = renderConsoleComposerLines(this.draftValue, width, enabled);
544
644
  const usageTokens = Math.min(estimateConsoleContextTokens(this.conversationMessages, this.draftValue), getCliModelContextWindowTokens(this.modelMode));
@@ -549,7 +649,8 @@ class ConsoleScreenRenderer {
549
649
  buildConsoleFooterStatusLine(width, this.modelMode, usageTokens, enabled),
550
650
  buildConsoleFooterApprovalLine(this.approvalMode, enabled, this.approvalStatusSuffix),
551
651
  ];
552
- const transcriptHeight = Math.max(0, height - footerLines.length);
652
+ const visibleHeader = headerLines.slice(0, Math.max(0, height - footerLines.length));
653
+ const transcriptHeight = Math.max(0, height - visibleHeader.length - footerLines.length);
553
654
  const transcriptLines = this.transcript.flatMap((entry) => this.buildEntryLines(entry, width));
554
655
  const visibleTranscript = transcriptLines.slice(-transcriptHeight);
555
656
  const paddedTranscript = [
@@ -558,11 +659,14 @@ class ConsoleScreenRenderer {
558
659
  ];
559
660
  output.write("\u001b[?25l");
560
661
  output.write("\u001b[H\u001b[2J");
561
- output.write([...paddedTranscript, ...footerLines].join("\n"));
562
- cursorTo(output, Math.min(width - 1, CONSOLE_COMPOSER_CURSOR_COLUMN + composer.cursorColumn), transcriptHeight + 1 + composer.cursorLineIndex);
662
+ output.write([...visibleHeader, ...paddedTranscript, ...footerLines].join("\n"));
663
+ cursorTo(output, Math.min(width - 1, CONSOLE_COMPOSER_CURSOR_COLUMN + composer.cursorColumn), visibleHeader.length + transcriptHeight + 1 + composer.cursorLineIndex);
563
664
  output.write("\u001b[?25h");
564
665
  }
565
666
  }
667
+ function createConsoleScreenRenderer(_modelMode, _approvalMode, _runtimeSession) {
668
+ return null;
669
+ }
566
670
  export function resolveCliModelMode(value) {
567
671
  const normalized = normalizeText(value).toLowerCase();
568
672
  if (!normalized) {
@@ -797,13 +901,14 @@ function styleCardLine(line, tone, enabled) {
797
901
  }
798
902
  return `${ANSI.white}${line}${ANSI.reset}`;
799
903
  }
800
- function renderInfoCard(lines) {
904
+ export function renderInfoCard(lines, maxContentWidth = 82) {
801
905
  const enabled = supportsAnsi();
906
+ const safeMaxContentWidth = Math.max(20, maxContentWidth);
802
907
  const normalized = lines.map((line) => ({
803
- text: truncate(line.text, 82),
908
+ text: truncate(line.text, safeMaxContentWidth),
804
909
  tone: line.tone,
805
910
  }));
806
- const width = Math.max(44, ...normalized.map((line) => line.text.length));
911
+ const width = Math.max(Math.min(44, safeMaxContentWidth), ...normalized.map((line) => line.text.length));
807
912
  const top = style(`┌${"─".repeat(width + 2)}┐`, ANSI.brandBlue, enabled);
808
913
  const bottom = style(`└${"─".repeat(width + 2)}┘`, ANSI.brandBlue, enabled);
809
914
  return [
@@ -816,17 +921,31 @@ function renderInfoCard(lines) {
816
921
  bottom,
817
922
  ].join("\n");
818
923
  }
819
- function buildWelcomeCard(runtimeSession, modelMode) {
924
+ export function buildWelcomeCard(runtimeSession) {
820
925
  return [
821
926
  { text: "Welcome to OttoAI", tone: "title" },
822
927
  { text: "", tone: "muted" },
823
928
  { text: "/help inside the console, /status for bridge details", tone: "muted" },
824
929
  { text: "", tone: "muted" },
825
- { text: `model: ${getCliModelLabel(modelMode)}`, tone: "muted" },
826
930
  { text: `cwd: ${process.cwd()}`, tone: "muted" },
827
931
  { text: renderRuntimeHeadline(runtimeSession), tone: "muted" },
828
932
  ];
829
933
  }
934
+ function buildConsoleHeaderLines(runtimeSession) {
935
+ const lines = [
936
+ ...renderBanner().split("\n"),
937
+ "",
938
+ ...renderInfoCard(buildWelcomeCard(runtimeSession), getCardMaxContentWidth()).split("\n"),
939
+ ];
940
+ const releaseNotice = runtimeSession.getReleaseNotice();
941
+ if (releaseNotice) {
942
+ lines.push("");
943
+ lines.push(...renderInfoCard(buildBridgeReleaseCard(releaseNotice), getCardMaxContentWidth()).split("\n"));
944
+ }
945
+ lines.push("");
946
+ lines.push(style(`Comandos: ${CONSOLE_COMMAND_HINT}`, ANSI.slateItalic, supportsAnsi()));
947
+ return lines;
948
+ }
830
949
  function buildBridgeReleaseCard(notice) {
831
950
  const isRequired = notice.kind === "required";
832
951
  return [
@@ -916,29 +1035,20 @@ async function buildExtensionsCard(config) {
916
1035
  }
917
1036
  return lines;
918
1037
  }
919
- function printHubScreen(runtimeSession, modelMode) {
1038
+ function printHubScreen(runtimeSession) {
920
1039
  clearScreen();
921
1040
  console.log(renderBanner());
922
1041
  console.log("");
923
- console.log(renderInfoCard(buildWelcomeCard(runtimeSession, modelMode)));
1042
+ console.log(renderInfoCard(buildWelcomeCard(runtimeSession), getCardMaxContentWidth()));
924
1043
  const releaseNotice = runtimeSession?.getReleaseNotice();
925
1044
  if (releaseNotice) {
926
1045
  console.log("");
927
- console.log(renderInfoCard(buildBridgeReleaseCard(releaseNotice)));
1046
+ console.log(renderInfoCard(buildBridgeReleaseCard(releaseNotice), getCardMaxContentWidth()));
928
1047
  }
929
1048
  }
930
- function printConsoleScreen(runtimeSession, modelMode) {
1049
+ function printConsoleScreen(runtimeSession) {
931
1050
  clearScreen();
932
- console.log(renderBanner());
933
- console.log("");
934
- console.log(renderInfoCard(buildWelcomeCard(runtimeSession, modelMode)));
935
- const releaseNotice = runtimeSession.getReleaseNotice();
936
- if (releaseNotice) {
937
- console.log("");
938
- console.log(renderInfoCard(buildBridgeReleaseCard(releaseNotice)));
939
- }
940
- console.log("");
941
- printSoft(`Comandos: ${CONSOLE_COMMAND_HINT}`);
1051
+ console.log(buildConsoleHeaderLines(runtimeSession).join("\n"));
942
1052
  console.log("");
943
1053
  }
944
1054
  function formatConsoleWorkspaceLine(workspace, options) {
@@ -1202,6 +1312,30 @@ export function tryConsumeControlSequence(buffer) {
1202
1312
  };
1203
1313
  }
1204
1314
  }
1315
+ const moveUpSequences = [
1316
+ "\u001b[A",
1317
+ "\u001bOA",
1318
+ ];
1319
+ for (const sequence of moveUpSequences) {
1320
+ if (buffer.startsWith(sequence)) {
1321
+ return {
1322
+ consumed: sequence.length,
1323
+ action: "move_up",
1324
+ };
1325
+ }
1326
+ }
1327
+ const moveDownSequences = [
1328
+ "\u001b[B",
1329
+ "\u001bOB",
1330
+ ];
1331
+ for (const sequence of moveDownSequences) {
1332
+ if (buffer.startsWith(sequence)) {
1333
+ return {
1334
+ consumed: sequence.length,
1335
+ action: "move_down",
1336
+ };
1337
+ }
1338
+ }
1205
1339
  if (buffer === "\u001b" || /^\u001b\[[0-9;?]*$/.test(buffer)) {
1206
1340
  return {
1207
1341
  consumed: 0,
@@ -1240,55 +1374,100 @@ async function askConsoleInput(rl, options) {
1240
1374
  return await new Promise((resolve, reject) => {
1241
1375
  const enabled = supportsAnsi();
1242
1376
  const ui = options?.ui && options.ui.isActive() ? options.ui : null;
1243
- const availableWidth = Number(output.columns || 96);
1244
- const innerWidth = Math.max(42, Math.min(availableWidth - 8, 116));
1245
- const sectionTopOffsetFromInputLine = 1;
1246
1377
  let value = "";
1247
1378
  let renderedOnce = false;
1379
+ let renderedCursorLineIndex = 0;
1248
1380
  let controlBuffer = "";
1381
+ let selectedSuggestionIndex = 0;
1382
+ const getPromptWidth = () => Math.max(48, Number(output.columns || 96));
1383
+ const getModelMode = () => options?.getModelMode?.() || "fast";
1384
+ const getApprovalMode = () => options?.getApprovalMode?.() || "preview";
1385
+ const getConversationMessages = () => options?.getConversationMessages?.() || [];
1386
+ const getVisibleSuggestions = () => {
1387
+ const suggestions = resolveConsoleSlashSuggestions(value).slice(0, 6);
1388
+ if (selectedSuggestionIndex >= suggestions.length) {
1389
+ selectedSuggestionIndex = Math.max(0, suggestions.length - 1);
1390
+ }
1391
+ return suggestions;
1392
+ };
1249
1393
  const cleanup = () => {
1250
1394
  input.removeListener("data", onData);
1395
+ output.off("resize", renderPromptBlock);
1396
+ if (!ui && renderedOnce) {
1397
+ cursorTo(output, 0);
1398
+ moveCursor(output, 0, -renderedCursorLineIndex);
1399
+ clearScreenDown(output);
1400
+ }
1251
1401
  input.setRawMode(false);
1252
1402
  input.pause();
1253
1403
  rl.resume();
1254
1404
  ui?.setDraftValue("");
1255
1405
  };
1256
- const renderInputContent = () => {
1257
- const promptPlain = "> ";
1258
- const promptStyled = style(">", `${ANSI.bold}${ANSI.white}`, enabled);
1259
- const maxValueLength = Math.max(0, innerWidth - promptPlain.length);
1260
- if (!value) {
1261
- const placeholder = truncate(CONSOLE_PLACEHOLDER, maxValueLength);
1262
- const padded = `${style(placeholder, ANSI.slate, enabled)}${" ".repeat(Math.max(0, maxValueLength - placeholder.length))}`;
1263
- return `${promptStyled} ${padded}`;
1406
+ const renderSuggestionLines = (width) => {
1407
+ const suggestions = getVisibleSuggestions();
1408
+ if (!suggestions.length) {
1409
+ return [];
1264
1410
  }
1265
- const visibleValue = value.length > maxValueLength
1266
- ? value.slice(value.length - maxValueLength)
1267
- : value;
1268
- return `${promptStyled} ${visibleValue}${" ".repeat(Math.max(0, maxValueLength - visibleValue.length))}`;
1411
+ const suggestionWidth = Math.max(24, width - 4);
1412
+ return suggestions.map((item, index) => {
1413
+ const selected = index === selectedSuggestionIndex;
1414
+ const prefix = selected
1415
+ ? style("›", `${ANSI.bold}${ANSI.brandBlue}`, enabled)
1416
+ : style(" ", ANSI.slate, enabled);
1417
+ const command = style(item.command, selected ? `${ANSI.bold}${ANSI.white}` : ANSI.brandBlue, enabled);
1418
+ const description = style(truncate(item.description, Math.max(12, suggestionWidth - item.command.length - 4)), selected ? ANSI.white : ANSI.slate, enabled);
1419
+ return `${prefix} ${command} ${description}`;
1420
+ });
1269
1421
  };
1270
- const renderLegacyInput = () => {
1422
+ const renderPromptBlock = () => {
1271
1423
  if (ui) {
1272
1424
  ui.setDraftValue(value);
1273
1425
  return;
1274
1426
  }
1427
+ const width = getPromptWidth();
1428
+ const composer = renderConsoleComposerLines(value, width, enabled);
1429
+ const modelMode = getModelMode();
1430
+ const approvalMode = getApprovalMode();
1431
+ const usageTokens = Math.min(estimateConsoleContextTokens(getConversationMessages(), value), getCliModelContextWindowTokens(modelMode));
1432
+ const suggestionLines = renderSuggestionLines(width);
1433
+ const blockLines = [
1434
+ style("─".repeat(width), ANSI.brandBlue, enabled),
1435
+ ...composer.renderedLines,
1436
+ ...suggestionLines,
1437
+ style("─".repeat(width), ANSI.brandBlue, enabled),
1438
+ buildConsoleFooterStatusLine(width, modelMode, usageTokens, enabled),
1439
+ buildConsoleFooterApprovalLine(approvalMode, enabled),
1440
+ ];
1275
1441
  if (renderedOnce) {
1276
1442
  cursorTo(output, 0);
1277
- moveCursor(output, 0, -sectionTopOffsetFromInputLine);
1443
+ moveCursor(output, 0, -renderedCursorLineIndex);
1444
+ clearScreenDown(output);
1278
1445
  }
1279
1446
  else {
1280
1447
  renderedOnce = true;
1281
1448
  }
1282
- const top = renderPromptFrameLine(innerWidth + 2, "", "┐");
1283
- const border = style("│", ANSI.brandBlue, enabled);
1284
- const middle = `${border} ${renderInputContent()} ${border}`;
1285
- const bottom = renderPromptFrameLine(innerWidth + 2, "└", "┘");
1286
- clearScreenDown(output);
1287
- output.write(`${top}\n${middle}\n${bottom}\n`);
1449
+ output.write(blockLines.join("\n"));
1450
+ renderedCursorLineIndex = 1 + composer.cursorLineIndex;
1451
+ const linesBelowCursor = blockLines.length - 1 - renderedCursorLineIndex;
1288
1452
  cursorTo(output, 0);
1289
- moveCursor(output, 0, -2);
1290
- const visibleValueLength = Math.min(value.length, Math.max(0, innerWidth - 2));
1291
- cursorTo(output, 4 + visibleValueLength);
1453
+ if (linesBelowCursor > 0) {
1454
+ moveCursor(output, 0, -linesBelowCursor);
1455
+ }
1456
+ cursorTo(output, Math.min(width - 1, CONSOLE_COMPOSER_CURSOR_COLUMN + composer.cursorColumn));
1457
+ };
1458
+ const applySelectedSuggestion = () => {
1459
+ const suggestions = getVisibleSuggestions();
1460
+ const selected = suggestions[selectedSuggestionIndex];
1461
+ if (!selected) {
1462
+ return false;
1463
+ }
1464
+ if (normalizeText(value) === selected.insertText) {
1465
+ return false;
1466
+ }
1467
+ value = selected.insertText;
1468
+ selectedSuggestionIndex = 0;
1469
+ renderPromptBlock();
1470
+ return true;
1292
1471
  };
1293
1472
  const consumeControlBuffer = () => {
1294
1473
  while (controlBuffer.length > 0) {
@@ -1305,12 +1484,31 @@ async function askConsoleInput(rl, options) {
1305
1484
  continue;
1306
1485
  }
1307
1486
  if (parsed.action === "cycle_approval") {
1308
- void options?.onCycleApprovalMode?.();
1487
+ void Promise.resolve(options?.onCycleApprovalMode?.()).finally(() => {
1488
+ renderPromptBlock();
1489
+ });
1490
+ continue;
1491
+ }
1492
+ if (parsed.action === "move_up") {
1493
+ const suggestions = getVisibleSuggestions();
1494
+ if (suggestions.length > 0) {
1495
+ selectedSuggestionIndex = selectedSuggestionIndex > 0 ? selectedSuggestionIndex - 1 : suggestions.length - 1;
1496
+ renderPromptBlock();
1497
+ }
1498
+ continue;
1499
+ }
1500
+ if (parsed.action === "move_down") {
1501
+ const suggestions = getVisibleSuggestions();
1502
+ if (suggestions.length > 0) {
1503
+ selectedSuggestionIndex = selectedSuggestionIndex < suggestions.length - 1 ? selectedSuggestionIndex + 1 : 0;
1504
+ renderPromptBlock();
1505
+ }
1309
1506
  continue;
1310
1507
  }
1311
1508
  if (parsed.action === "newline") {
1312
1509
  value += "\n";
1313
- renderLegacyInput();
1510
+ selectedSuggestionIndex = 0;
1511
+ renderPromptBlock();
1314
1512
  }
1315
1513
  }
1316
1514
  };
@@ -1328,26 +1526,30 @@ async function askConsoleInput(rl, options) {
1328
1526
  return;
1329
1527
  }
1330
1528
  if (char === "\r" || char === "\n") {
1331
- cleanup();
1332
- if (!ui) {
1333
- output.write("\n");
1529
+ if (applySelectedSuggestion()) {
1530
+ continue;
1334
1531
  }
1532
+ cleanup();
1533
+ output.write("\n");
1335
1534
  resolve(normalizeText(value));
1336
1535
  return;
1337
1536
  }
1338
1537
  if (char === "\u007f" || char === "\b") {
1339
1538
  value = value.slice(0, -1);
1340
- renderLegacyInput();
1539
+ selectedSuggestionIndex = 0;
1540
+ renderPromptBlock();
1341
1541
  continue;
1342
1542
  }
1343
1543
  if (char === "\t") {
1544
+ applySelectedSuggestion();
1344
1545
  continue;
1345
1546
  }
1346
1547
  value += char;
1347
- renderLegacyInput();
1548
+ renderPromptBlock();
1348
1549
  }
1349
1550
  };
1350
- renderLegacyInput();
1551
+ output.on("resize", renderPromptBlock);
1552
+ renderPromptBlock();
1351
1553
  input.on("data", onData);
1352
1554
  });
1353
1555
  }
@@ -1649,14 +1851,11 @@ async function followConsoleJob(rl, config, jobId) {
1649
1851
  }
1650
1852
  async function runOttoConsole(rl, config, runtimeSession, options) {
1651
1853
  let activeModel = "fast";
1652
- const sessionId = randomUUID();
1854
+ let sessionId = randomUUID();
1653
1855
  const conversation = [];
1654
- const ui = supportsAnsi() && typeof input.setRawMode === "function" && input.isTTY && output.isTTY
1655
- ? new ConsoleScreenRenderer(activeModel, config.approvalMode)
1656
- : null;
1657
- ui?.activate();
1856
+ const ui = createConsoleScreenRenderer(activeModel, config.approvalMode, runtimeSession);
1658
1857
  if (!ui) {
1659
- printConsoleScreen(runtimeSession, activeModel);
1858
+ printConsoleScreen(runtimeSession);
1660
1859
  }
1661
1860
  const renderConversationState = () => {
1662
1861
  ui?.setConversationMessages(conversation);
@@ -1669,12 +1868,13 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
1669
1868
  text,
1670
1869
  tone,
1671
1870
  prefix: options?.prefix,
1871
+ prefixTone: options?.prefixTone,
1672
1872
  continuationPrefix: options?.continuationPrefix,
1673
1873
  });
1674
1874
  return;
1675
1875
  }
1676
1876
  if (options?.prefix) {
1677
- console.log(`${options.prefix}${text}`);
1877
+ console.log(`${styleTranscriptPrefix(options.prefix, options.prefixTone, supportsAnsi())}${styleTranscriptLine(text, tone, supportsAnsi())}`);
1678
1878
  return;
1679
1879
  }
1680
1880
  if (tone === "warning") {
@@ -1698,6 +1898,14 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
1698
1898
  const emitUserPrompt = (text) => {
1699
1899
  emitConsoleEntry(text, "user", {
1700
1900
  prefix: "> ",
1901
+ prefixTone: "user",
1902
+ continuationPrefix: " ",
1903
+ });
1904
+ };
1905
+ const emitAssistantPrompt = (text) => {
1906
+ emitConsoleEntry(text, "assistant", {
1907
+ prefix: "• ",
1908
+ prefixTone: "assistant",
1701
1909
  continuationPrefix: " ",
1702
1910
  });
1703
1911
  };
@@ -1723,6 +1931,9 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
1723
1931
  return await askConsoleInput(rl, {
1724
1932
  ui,
1725
1933
  onCycleApprovalMode: cycleApprovalMode,
1934
+ getConversationMessages: () => conversation,
1935
+ getModelMode: () => activeModel,
1936
+ getApprovalMode: () => config.approvalMode,
1726
1937
  });
1727
1938
  };
1728
1939
  const askConsoleDecision = async (promptText, defaultValue = true) => {
@@ -1736,7 +1947,7 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
1736
1947
  };
1737
1948
  const printConsoleHelp = () => {
1738
1949
  emitConsoleEntry("Console", "headline");
1739
- emitConsoleEntry("Comandos: /help, /model [fast|thinking], /approval [preview|confirm|trusted], /workspace, /status, /clear, /exit", "muted");
1950
+ emitConsoleEntry("Comandos: /help, /model [fast|thinking], /approval [preview|confirm|trusted], /workspace, /status, /clear, /new, /exit", "muted");
1740
1951
  emitConsoleEntry("Composer: Enter envia, Shift+Enter adiciona linha e Shift+Tab alterna o modo de aprovação.", "muted");
1741
1952
  emitConsoleEntry("Workspace: /workspace list, /workspace attach <path>, /workspace use <id|n>, /workspace clear", "muted");
1742
1953
  };
@@ -1886,7 +2097,12 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
1886
2097
  : status === "failed"
1887
2098
  ? "Execução local falhou."
1888
2099
  : "Execução local cancelada.");
1889
- emitConsoleEntry(summary, status === "failed" ? "error" : status === "cancelled" ? "warning" : "assistant");
2100
+ if (status === "completed") {
2101
+ emitAssistantPrompt(summary);
2102
+ }
2103
+ else {
2104
+ emitConsoleEntry(summary, status === "failed" ? "error" : "warning");
2105
+ }
1890
2106
  const rendered = renderStructuredOutcome(job);
1891
2107
  if (rendered) {
1892
2108
  emitConsoleEntry(rendered, "muted");
@@ -1910,11 +2126,22 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
1910
2126
  ui?.clearTranscript();
1911
2127
  renderConversationState();
1912
2128
  if (!ui) {
1913
- printConsoleScreen(runtimeSession, activeModel);
2129
+ printConsoleScreen(runtimeSession);
1914
2130
  }
1915
2131
  emitConsoleEntry("Contexto local do console limpo.", "muted");
1916
2132
  return;
1917
2133
  }
2134
+ if (normalizedPrompt === "/new") {
2135
+ sessionId = randomUUID();
2136
+ conversation.splice(0, conversation.length);
2137
+ ui?.clearTranscript();
2138
+ renderConversationState();
2139
+ if (!ui) {
2140
+ printConsoleScreen(runtimeSession);
2141
+ }
2142
+ emitConsoleEntry("Nova sessão iniciada. Contexto local reiniciado.", "muted");
2143
+ return;
2144
+ }
1918
2145
  if (normalizedPrompt === "/model") {
1919
2146
  emitConsoleEntry(`Modelo ativo: ${getCliModelLabel(activeModel)}.`, "muted");
1920
2147
  emitConsoleEntry("Use /model fast ou /model thinking para trocar.", "muted");
@@ -1978,10 +2205,8 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
1978
2205
  throw createCliExitError();
1979
2206
  }
1980
2207
  emitUserPrompt(normalizedPrompt);
2208
+ emitConsoleEntry("", "muted");
1981
2209
  conversation.push({ role: "user", content: normalizedPrompt });
1982
- while (conversation.length > 18) {
1983
- conversation.shift();
1984
- }
1985
2210
  renderConversationState();
1986
2211
  let streamedAssistant = "";
1987
2212
  let assistantEntryId = null;
@@ -2041,8 +2266,17 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
2041
2266
  }
2042
2267
  if (!assistantPrefixPrinted) {
2043
2268
  assistantEntryId = ui
2044
- ? ui.pushEntry({ text: "", tone: "assistant" })
2269
+ ? ui.pushEntry({
2270
+ text: "",
2271
+ tone: "assistant",
2272
+ prefix: "• ",
2273
+ prefixTone: "assistant",
2274
+ continuationPrefix: " ",
2275
+ })
2045
2276
  : null;
2277
+ if (!ui) {
2278
+ output.write(styleTranscriptPrefix("• ", "assistant", supportsAnsi()));
2279
+ }
2046
2280
  assistantPrefixPrinted = true;
2047
2281
  }
2048
2282
  if (ui && assistantEntryId) {
@@ -2065,7 +2299,7 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
2065
2299
  : null;
2066
2300
  const jobId = normalizeText(job?.id);
2067
2301
  if (bridgeSummary && !jobId) {
2068
- emitConsoleEntry(bridgeSummary, "assistant");
2302
+ emitAssistantPrompt(bridgeSummary);
2069
2303
  }
2070
2304
  if (jobId) {
2071
2305
  finalAssistantSummary = await followConsoleJobUi(jobId);
@@ -2076,9 +2310,6 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
2076
2310
  }
2077
2311
  if (finalAssistantSummary) {
2078
2312
  conversation.push({ role: "assistant", content: finalAssistantSummary });
2079
- while (conversation.length > 18) {
2080
- conversation.shift();
2081
- }
2082
2313
  renderConversationState();
2083
2314
  }
2084
2315
  };
@@ -2134,52 +2365,112 @@ async function printHelpView(rl) {
2134
2365
  { text: "otto-bridge version | otto-bridge unpair", tone: "primary" },
2135
2366
  { text: "Mostra a versao instalada ou remove o pairing local.", tone: "muted" },
2136
2367
  { text: "", tone: "muted" },
2137
- { text: "Dentro do console: /help, /model fast|thinking, /approval preview|confirm|trusted, /status, /clear, /exit", tone: "muted" },
2368
+ { text: "Dentro do console: /help, /model fast|thinking, /approval preview|confirm|trusted, /status, /clear, /new, /exit", tone: "muted" },
2138
2369
  ]));
2139
2370
  await pauseForEnter(rl);
2140
2371
  }
2141
- async function pickHomeChoice(rl, paired) {
2142
- printSection("Home");
2372
+ function renderHomeOptionLine(label, selected) {
2373
+ if (!selected) {
2374
+ return ` ${label}`;
2375
+ }
2376
+ return `${style("▸", ANSI.brandBlue, supportsAnsi())} ${style(label, `${ANSI.bold}${ANSI.white}`, supportsAnsi())}`;
2377
+ }
2378
+ async function pickHomeChoice(rl, paired, renderBaseScreen) {
2143
2379
  const options = paired
2144
2380
  ? [
2145
- `${style("1.", ANSI.brandBlue, supportsAnsi())} Otto Console`,
2146
- `${style("2.", ANSI.brandBlue, supportsAnsi())} Terminal`,
2147
- `${style("3.", ANSI.brandBlue, supportsAnsi())} Setup / parear novamente`,
2148
- `${style("4.", ANSI.brandBlue, supportsAnsi())} Status detalhado`,
2149
- `${style("5.", ANSI.brandBlue, supportsAnsi())} Extensões`,
2150
- `${style("6.", ANSI.brandBlue, supportsAnsi())} Ajuda`,
2151
- `${style("7.", ANSI.brandBlue, supportsAnsi())} Sair`,
2381
+ { value: "console", label: "Otto Console" },
2382
+ { value: "terminal", label: "Terminal" },
2383
+ { value: "setup", label: "Setup / parear novamente" },
2384
+ { value: "status", label: "Status detalhado" },
2385
+ { value: "extensions", label: "Extensões" },
2386
+ { value: "help", label: "Ajuda" },
2387
+ { value: "exit", label: "Sair" },
2152
2388
  ]
2153
2389
  : [
2154
- `${style("1.", ANSI.brandBlue, supportsAnsi())} Pairing setup`,
2155
- `${style("2.", ANSI.brandBlue, supportsAnsi())} Terminal`,
2156
- `${style("3.", ANSI.brandBlue, supportsAnsi())} Ajuda`,
2157
- `${style("4.", ANSI.brandBlue, supportsAnsi())} Sair`,
2390
+ { value: "setup", label: "Pairing setup" },
2391
+ { value: "terminal", label: "Terminal" },
2392
+ { value: "help", label: "Ajuda" },
2393
+ { value: "exit", label: "Sair" },
2158
2394
  ];
2159
- console.log(options.join("\n"));
2160
- const answer = await ask(rl, "Escolha");
2161
- if (!paired) {
2162
- if (answer === "1")
2163
- return "setup";
2164
- if (answer === "2")
2165
- return "terminal";
2166
- if (answer === "3")
2167
- return "help";
2395
+ if (!supportsAnsi() || typeof input.setRawMode !== "function" || !input.isTTY) {
2396
+ printSection("Home");
2397
+ console.log(options.map((option, index) => `${style(`${index + 1}.`, ANSI.brandBlue, supportsAnsi())} ${option.label}`).join("\n"));
2398
+ const answer = await ask(rl, "Escolha");
2399
+ const numericIndex = Number(answer);
2400
+ if (Number.isInteger(numericIndex) && numericIndex >= 1 && numericIndex <= options.length) {
2401
+ return options[numericIndex - 1].value;
2402
+ }
2168
2403
  return "exit";
2169
2404
  }
2170
- if (answer === "1")
2171
- return "console";
2172
- if (answer === "2")
2173
- return "terminal";
2174
- if (answer === "3")
2175
- return "setup";
2176
- if (answer === "4")
2177
- return "status";
2178
- if (answer === "5")
2179
- return "extensions";
2180
- if (answer === "6")
2181
- return "help";
2182
- return "exit";
2405
+ rl.pause();
2406
+ input.setRawMode(true);
2407
+ input.resume();
2408
+ return await new Promise((resolve, reject) => {
2409
+ let selectedIndex = 0;
2410
+ let controlBuffer = "";
2411
+ const cleanup = () => {
2412
+ input.removeListener("data", onData);
2413
+ input.setRawMode(false);
2414
+ input.pause();
2415
+ rl.resume();
2416
+ };
2417
+ const render = () => {
2418
+ renderBaseScreen();
2419
+ printSection("Home");
2420
+ console.log(options.map((option, index) => renderHomeOptionLine(option.label, index === selectedIndex)).join("\n"));
2421
+ console.log("");
2422
+ printSoft(CONSOLE_MENU_HINT);
2423
+ };
2424
+ const consumeControlBuffer = () => {
2425
+ while (controlBuffer.length > 0) {
2426
+ const parsed = tryConsumeControlSequence(controlBuffer);
2427
+ if (!parsed) {
2428
+ controlBuffer = "";
2429
+ return;
2430
+ }
2431
+ if (parsed.action === "incomplete") {
2432
+ return;
2433
+ }
2434
+ controlBuffer = controlBuffer.slice(parsed.consumed);
2435
+ if (parsed.action === "move_up") {
2436
+ selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : options.length - 1;
2437
+ render();
2438
+ continue;
2439
+ }
2440
+ if (parsed.action === "move_down") {
2441
+ selectedIndex = selectedIndex < options.length - 1 ? selectedIndex + 1 : 0;
2442
+ render();
2443
+ continue;
2444
+ }
2445
+ if (parsed.action === "ignore" || parsed.action === "cycle_approval" || parsed.action === "newline") {
2446
+ continue;
2447
+ }
2448
+ }
2449
+ };
2450
+ const onData = (chunk) => {
2451
+ const text = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
2452
+ for (const char of Array.from(text)) {
2453
+ if (controlBuffer || char === "\u001b") {
2454
+ controlBuffer += char;
2455
+ consumeControlBuffer();
2456
+ continue;
2457
+ }
2458
+ if (char === "\u0003") {
2459
+ cleanup();
2460
+ reject(createCliExitError());
2461
+ return;
2462
+ }
2463
+ if (char === "\r" || char === "\n") {
2464
+ cleanup();
2465
+ output.write("\n");
2466
+ resolve(options[selectedIndex].value);
2467
+ return;
2468
+ }
2469
+ }
2470
+ };
2471
+ render();
2472
+ input.on("data", onData);
2473
+ });
2183
2474
  }
2184
2475
  export async function launchInteractiveCli(options) {
2185
2476
  let rl = await createPromptInterface();
@@ -2188,7 +2479,7 @@ export async function launchInteractiveCli(options) {
2188
2479
  try {
2189
2480
  let config = await loadBridgeConfig();
2190
2481
  if (!config) {
2191
- printHubScreen(null, "fast");
2482
+ printHubScreen(null);
2192
2483
  const setup = await runSetupWizard(rl, options);
2193
2484
  config = setup.config;
2194
2485
  if (config && setup.openConsole) {
@@ -2205,8 +2496,8 @@ export async function launchInteractiveCli(options) {
2205
2496
  await runtimeSession.ensureStarted();
2206
2497
  await runtimeSession.waitForReady();
2207
2498
  for (;;) {
2208
- printHubScreen(runtimeSession, "fast");
2209
- const choice = await pickHomeChoice(rl, true);
2499
+ printHubScreen(runtimeSession);
2500
+ const choice = await pickHomeChoice(rl, true, () => printHubScreen(runtimeSession));
2210
2501
  if (choice === "exit") {
2211
2502
  break;
2212
2503
  }
@@ -2303,7 +2594,7 @@ export async function runSetupCommand(options) {
2303
2594
  const rl = await createPromptInterface();
2304
2595
  let runtimeSession = null;
2305
2596
  try {
2306
- printHubScreen(null, "fast");
2597
+ printHubScreen(null);
2307
2598
  const setup = await runSetupWizard(rl, options);
2308
2599
  if (setup.config && setup.openConsole) {
2309
2600
  runtimeSession = new CliRuntimeSession(setup.config);
@@ -2326,7 +2617,7 @@ export async function runConsoleCommand(initialPrompt) {
2326
2617
  const rl = await createPromptInterface();
2327
2618
  let runtimeSession = null;
2328
2619
  try {
2329
- printHubScreen(null, "fast");
2620
+ printHubScreen(null);
2330
2621
  let config = await loadBridgeConfig();
2331
2622
  if (!config) {
2332
2623
  const setup = await runSetupWizard(rl);
package/dist/main.js CHANGED
@@ -129,6 +129,7 @@ Console:
129
129
  /workspace clear
130
130
  /status
131
131
  /clear
132
+ /new
132
133
  /exit
133
134
 
134
135
  Examples:
package/dist/types.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export const BRIDGE_CONFIG_VERSION = 1;
2
- export const BRIDGE_VERSION = "1.1.1";
2
+ export const BRIDGE_VERSION = "1.1.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.1.1",
3
+ "version": "1.1.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.1.1");
27
+ console.log("\n[otto-bridge] Welcome to OTTOAI 1.1.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"], {