@leg3ndy/otto-bridge 1.0.3 → 1.0.5

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
@@ -11,11 +11,11 @@ Companion local do Otto para:
11
11
 
12
12
  Para um passo a passo de instalacao, pareamento, uso, desconexao e desinstalacao, veja [USER_GUIDE.md](https://github.com/LGCYYL/ottoai/blob/main/otto-bridge/USER_GUIDE.md).
13
13
 
14
- Para o estado atual da arquitetura, capacidades entregues, limitacoes e roadmap do Otto Bridge, veja [`leg3ndy-ai-backend/docs/OTTO_BRIDGE_ARCHITECTURE.md`](../leg3ndy-ai-backend/docs/OTTO_BRIDGE_ARCHITECTURE.md).
14
+ Para o estado atual da arquitetura, capacidades entregues, limitacoes e roadmap do Otto Bridge, veja [`leg3ndy-ai-backend/docs/otto-bridge/OTTO_BRIDGE_ARCHITECTURE.md`](../leg3ndy-ai-backend/docs/otto-bridge/OTTO_BRIDGE_ARCHITECTURE.md).
15
15
 
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).
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 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).
18
+ Para a release atual `1.0.5`, com novo modo `Terminal`, input-box mais proximo do estilo Claude e ajuda expandida, veja [`leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_0_5_PATCH.md`](../leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_0_5_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.3.tgz
41
+ npm install -g ./leg3ndy-otto-bridge-1.0.5.tgz
42
42
  ```
43
43
 
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.
44
+ Na linha `1.0.5`, `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.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`.
46
+ No macOS, a linha `1.0.5` 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.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.
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.5` adiciona o modo `Terminal`, reorganiza o input-box do console para um fluxo mais proximo do Claude e amplia a ajuda publica sem voltar a poluir a superficie com aliases legados.
49
49
 
50
50
  ## Publicacao
51
51
 
@@ -143,11 +143,21 @@ Dentro do console, use:
143
143
  - `/model thinking` para `OttoAI Thinking`
144
144
  - `/status` para ver detalhes técnicos do bridge e do runtime
145
145
 
146
+ 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.
147
+
146
148
  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
149
 
150
+ ### Abrir um shell local pelo hub
151
+
152
+ ```bash
153
+ otto-bridge terminal
154
+ ```
155
+
156
+ Esse comando abre um shell local interativo para instalar extensoes, rodar comandos externos e fazer operacao manual sem encerrar o bridge. No hub principal, a opcao `Terminal` faz a mesma coisa e volta ao menu quando voce sair com `exit`.
157
+
148
158
  ### WhatsApp Web em background
149
159
 
150
- Fluxo recomendado na linha `1.0.3`:
160
+ Fluxo recomendado na linha `1.0.5`:
151
161
 
152
162
  ```bash
153
163
  otto-bridge extensions --install whatsappweb
@@ -157,13 +167,13 @@ otto-bridge extensions --status whatsappweb
157
167
 
158
168
  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.
159
169
 
160
- Contrato da linha `1.0.3`:
170
+ Contrato da linha `1.0.5`:
161
171
 
162
172
  - `otto-bridge extensions --setup whatsappweb`: autentica a sessao uma vez
163
173
  - `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
164
174
  - ao fechar o `otto-bridge`: o browser em background e desligado, mas a sessao local fica lembrada para o proximo boot
165
175
 
166
- ## Handoff rapido da linha 1.0.3
176
+ ## Handoff rapido da linha 1.0.5
167
177
 
168
178
  Ja fechado no codigo:
169
179
 
@@ -1,5 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
+ import { spawn } from "node:child_process";
2
3
  import { createInterface } from "node:readline/promises";
4
+ import { cursorTo, moveCursor, } from "node:readline";
3
5
  import process, { stdin as input, stdout as output } from "node:process";
4
6
  import { defaultDeviceName, getBridgeConfigPath, loadBridgeConfig, resolveApiBaseUrl, resolveExecutorConfig, } from "./config.js";
5
7
  import { streamDeviceCliChat, } from "./chat_cli_client.js";
@@ -48,6 +50,8 @@ const MAX_RENDERED_LIST_ENTRIES = 28;
48
50
  const MAX_RENDERED_LIST_ENTRIES_COMPACT = 10;
49
51
  const MAX_RENDERED_FILE_CHARS = 6_000;
50
52
  const MAX_RENDERED_FILE_CHARS_COMPACT = 1_400;
53
+ const CONSOLE_PLACEHOLDER = "Peça algo ao Otto";
54
+ const CONSOLE_COMMAND_HINT = "/help, /model [fast|thinking], /status, /clear, /exit";
51
55
  class CliRuntimeSession {
52
56
  config;
53
57
  runtime = null;
@@ -119,6 +123,18 @@ class CliRuntimeSession {
119
123
  });
120
124
  await delay(350);
121
125
  }
126
+ async waitForReady(timeoutMs = 4500) {
127
+ const startedAt = Date.now();
128
+ for (;;) {
129
+ if (this.status !== "starting") {
130
+ return;
131
+ }
132
+ if (Date.now() - startedAt >= timeoutMs) {
133
+ return;
134
+ }
135
+ await delay(120);
136
+ }
137
+ }
122
138
  async replaceConfig(nextConfig) {
123
139
  await this.stop();
124
140
  this.config = nextConfig;
@@ -174,6 +190,13 @@ function style(text, color, enabled = true) {
174
190
  function supportsAnsi() {
175
191
  return Boolean(output.isTTY);
176
192
  }
193
+ function clearScreen() {
194
+ if (supportsAnsi()) {
195
+ output.write("\u001b[2J\u001b[3J\u001b[H");
196
+ return;
197
+ }
198
+ console.clear();
199
+ }
177
200
  function renderBanner() {
178
201
  const enabled = supportsAnsi();
179
202
  const lines = OTTOAI_BANNER.map((line) => style(line, ANSI.brandBlue, enabled));
@@ -455,30 +478,54 @@ function buildWelcomeCard(runtimeSession, modelMode) {
455
478
  ];
456
479
  }
457
480
  function printHubScreen(runtimeSession, modelMode) {
458
- console.clear();
481
+ clearScreen();
459
482
  console.log(renderBanner());
460
483
  console.log("");
461
484
  console.log(renderInfoCard(buildWelcomeCard(runtimeSession, modelMode)));
462
485
  }
463
486
  function printConsoleScreen(runtimeSession, modelMode) {
464
- console.clear();
487
+ clearScreen();
465
488
  console.log(renderBanner());
466
489
  console.log("");
467
490
  console.log(renderInfoCard(buildWelcomeCard(runtimeSession, modelMode)));
468
491
  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
492
  }
473
493
  function renderPromptFrameLine(width, edgeLeft, edgeRight) {
474
494
  return style(`${edgeLeft}${"─".repeat(width)}${edgeRight}`, ANSI.brandBlue, supportsAnsi());
475
495
  }
496
+ function renderConsolePromptContentLine(text, width, tone) {
497
+ const enabled = supportsAnsi();
498
+ const border = style("│", ANSI.brandBlue, enabled);
499
+ if (tone === "input") {
500
+ const prompt = `${style(">", `${ANSI.bold}${ANSI.white}`, enabled)} ${" ".repeat(Math.max(0, width - 2))}`;
501
+ return `${border} ${prompt} ${border}`;
502
+ }
503
+ const clipped = padRight(truncate(text, width), width);
504
+ const body = tone === "placeholder"
505
+ ? style(clipped, `${ANSI.bold}${ANSI.white}`, enabled)
506
+ : style(clipped, ANSI.slateItalic, enabled);
507
+ return `${border} ${body} ${border}`;
508
+ }
476
509
  async function askConsoleInput(rl) {
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, "└", "┘"));
510
+ if (!supportsAnsi()) {
511
+ printMuted(CONSOLE_PLACEHOLDER);
512
+ printSoft(`Comandos: ${CONSOLE_COMMAND_HINT}`);
513
+ return normalizeText(await question(rl, "> "));
514
+ }
515
+ const availableWidth = Number(output.columns || 96);
516
+ const inputWidth = Math.max(42, Math.min(availableWidth - 8, 116));
517
+ const top = renderPromptFrameLine(inputWidth + 2, "┌", "┐");
518
+ const placeholderLine = renderConsolePromptContentLine(CONSOLE_PLACEHOLDER, inputWidth, "placeholder");
519
+ const commandsLine = renderConsolePromptContentLine(`Comandos: ${CONSOLE_COMMAND_HINT}`, inputWidth, "commands");
520
+ const inputLine = renderConsolePromptContentLine("", inputWidth, "input");
521
+ const bottom = renderPromptFrameLine(inputWidth + 2, "└", "┘");
522
+ output.write(`${top}\n${placeholderLine}\n${commandsLine}\n${inputLine}\n${bottom}\n`);
523
+ cursorTo(output, 0);
524
+ moveCursor(output, 0, -2);
525
+ cursorTo(output, 4);
526
+ const answer = normalizeText(await question(rl, ""));
527
+ cursorTo(output, 0);
528
+ moveCursor(output, 0, 1);
482
529
  return answer;
483
530
  }
484
531
  function printAssistantMessage(message) {
@@ -492,9 +539,42 @@ function extractJobOutcome(job) {
492
539
  const result = job.result && typeof job.result === "object"
493
540
  ? job.result
494
541
  : {};
495
- return result.outcome && typeof result.outcome === "object"
542
+ const outcome = result.outcome && typeof result.outcome === "object"
496
543
  ? result.outcome
497
544
  : {};
545
+ if (Object.keys(outcome).length > 0) {
546
+ return outcome;
547
+ }
548
+ const payload = job.payload && typeof job.payload === "object"
549
+ ? job.payload
550
+ : {};
551
+ const actions = Array.isArray(payload.actions) ? payload.actions : [];
552
+ const firstAction = actions.find((item) => item && typeof item === "object");
553
+ const actionType = normalizeText(firstAction?.type).toLowerCase();
554
+ if (actionType === "list_files" && result.file_listing && typeof result.file_listing === "object") {
555
+ const listing = result.file_listing;
556
+ return {
557
+ action_type: "list_files",
558
+ path: normalizeText(firstAction?.path),
559
+ resolved_path: normalizeText(listing.resolved_path),
560
+ listed_item_count: listing.item_count,
561
+ total_item_count: listing.total_item_count,
562
+ limit_applied: listing.limit_applied,
563
+ entries: Array.isArray(listing.entries) ? listing.entries : [],
564
+ };
565
+ }
566
+ if (actionType === "read_file" && result.read_file && typeof result.read_file === "object") {
567
+ const readFile = result.read_file;
568
+ return {
569
+ action_type: "read_file",
570
+ path: normalizeText(firstAction?.path),
571
+ resolved_path: normalizeText(readFile.resolved_path),
572
+ content: typeof readFile.content === "string" ? readFile.content : undefined,
573
+ content_chunks: Array.isArray(readFile.chunks) ? readFile.chunks : [],
574
+ binary_notice: normalizeText(readFile.binary_notice),
575
+ };
576
+ }
577
+ return {};
498
578
  }
499
579
  function outcomePathLabel(outcome) {
500
580
  return normalizeText(outcome.resolved_path || outcome.path);
@@ -572,8 +652,46 @@ function printStructuredOutcome(job) {
572
652
  if (!rendered) {
573
653
  return;
574
654
  }
655
+ console.log("");
575
656
  console.log(rendered);
576
657
  }
658
+ function resolveTerminalShellCommand() {
659
+ if (process.platform === "win32") {
660
+ return {
661
+ command: process.env.COMSPEC || "cmd.exe",
662
+ args: [],
663
+ };
664
+ }
665
+ return {
666
+ command: process.env.SHELL || "/bin/zsh",
667
+ args: ["-l"],
668
+ };
669
+ }
670
+ export async function runTerminalShell(options) {
671
+ clearScreen();
672
+ console.log(renderBanner());
673
+ console.log("");
674
+ printSection("Terminal");
675
+ printMuted(options?.fromHub
676
+ ? "Saindo temporariamente para o shell local. Digite `exit` para voltar ao Otto Bridge."
677
+ : "Abrindo o shell local. Digite `exit` para encerrar e voltar ao terminal anterior.");
678
+ console.log("");
679
+ const shell = resolveTerminalShellCommand();
680
+ await new Promise((resolve, reject) => {
681
+ const child = spawn(shell.command, shell.args, {
682
+ stdio: "inherit",
683
+ env: process.env,
684
+ });
685
+ child.on("error", reject);
686
+ child.on("exit", (code, signal) => {
687
+ if (signal || code === 0 || code === 130) {
688
+ resolve();
689
+ return;
690
+ }
691
+ reject(new Error(`Terminal local encerrou com code ${code ?? "unknown"}.`));
692
+ });
693
+ });
694
+ }
577
695
  function buildConversationSummary(summary, job) {
578
696
  const rendered = renderStructuredOutcome(job, { compact: true });
579
697
  if (!rendered) {
@@ -693,7 +811,9 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
693
811
  const sessionId = randomUUID();
694
812
  const conversation = [];
695
813
  const printConsoleHelp = () => {
814
+ printSection("Console");
696
815
  printSoft("Comandos: /help, /model [fast|thinking], /status, /clear, /exit");
816
+ printSoft("Bridge: otto-bridge terminal, otto-bridge extensions --install <name>, otto-bridge update");
697
817
  };
698
818
  const handlePrompt = async (promptText) => {
699
819
  const normalizedPrompt = normalizeText(promptText);
@@ -748,6 +868,8 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
748
868
  }
749
869
  let streamedAssistant = "";
750
870
  let assistantPrefixPrinted = false;
871
+ let reasoningPrefixPrinted = false;
872
+ let contentSeparatedFromReasoning = false;
751
873
  let handoffPayload = null;
752
874
  await streamDeviceCliChat(config, {
753
875
  messages: conversation,
@@ -771,10 +893,25 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
771
893
  if (errorMessage) {
772
894
  throw new Error(errorMessage);
773
895
  }
896
+ const reasoningChunk = typeof event.content === "string" && eventType === "reasoning"
897
+ ? event.content
898
+ : "";
899
+ if (reasoningChunk) {
900
+ if (!reasoningPrefixPrinted) {
901
+ output.write(`${style("Pensando (OttoAI Thinking)\n", ANSI.brandBlue, supportsAnsi())}`);
902
+ reasoningPrefixPrinted = true;
903
+ }
904
+ output.write(style(reasoningChunk, ANSI.slateItalic, supportsAnsi()));
905
+ return;
906
+ }
774
907
  const contentChunk = typeof event.content === "string" ? event.content : "";
775
908
  if (!contentChunk) {
776
909
  return;
777
910
  }
911
+ if (reasoningPrefixPrinted && !contentSeparatedFromReasoning) {
912
+ output.write("\n\n");
913
+ contentSeparatedFromReasoning = true;
914
+ }
778
915
  if (!assistantPrefixPrinted) {
779
916
  output.write(`${style("•", ANSI.brandBlue, supportsAnsi())} `);
780
917
  assistantPrefixPrinted = true;
@@ -789,13 +926,13 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
789
926
  if (handoffPayload) {
790
927
  const handoffData = handoffPayload;
791
928
  const bridgeSummary = extractBridgeHandoffSummary(handoffData);
792
- if (bridgeSummary) {
793
- printAssistantMessage(bridgeSummary);
794
- }
795
929
  const job = handoffData.job && typeof handoffData.job === "object"
796
930
  ? handoffData.job
797
931
  : null;
798
932
  const jobId = normalizeText(job?.id);
933
+ if (bridgeSummary && !jobId) {
934
+ printAssistantMessage(bridgeSummary);
935
+ }
799
936
  if (jobId) {
800
937
  finalAssistantSummary = await followConsoleJob(rl, config, jobId);
801
938
  }
@@ -810,7 +947,6 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
810
947
  }
811
948
  }
812
949
  };
813
- printConsoleHelp();
814
950
  if (options?.initialPrompt) {
815
951
  await handlePrompt(options.initialPrompt);
816
952
  }
@@ -830,7 +966,36 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
830
966
  async function printStatusView(rl, config, runtimeSession) {
831
967
  printSection("Bridge Status");
832
968
  renderStatusOverview(config, runtimeSession).forEach((line) => console.log(line));
833
- await printExtensionsOverview(config);
969
+ await pauseForEnter(rl);
970
+ }
971
+ async function printHelpView(rl) {
972
+ printSection("Ajuda");
973
+ console.log(renderInfoCard([
974
+ { text: "Otto Bridge CLI", tone: "title" },
975
+ { text: "", tone: "muted" },
976
+ { text: "otto-bridge", tone: "primary" },
977
+ { text: "Abre o hub principal e sobe o runtime local automaticamente.", tone: "muted" },
978
+ { text: "", tone: "muted" },
979
+ { text: "otto-bridge terminal", tone: "primary" },
980
+ { text: "Abre um shell local interativo sem desligar o bridge.", tone: "muted" },
981
+ { text: "", tone: "muted" },
982
+ { text: "otto-bridge setup", tone: "primary" },
983
+ { text: "Repareia o dispositivo e atualiza config/executor.", tone: "muted" },
984
+ { text: "", tone: "muted" },
985
+ { text: "otto-bridge console", tone: "primary" },
986
+ { text: "Abre direto o console do Otto no terminal.", tone: "muted" },
987
+ { text: "", tone: "muted" },
988
+ { text: "otto-bridge extensions --install <name>", tone: "primary" },
989
+ { text: "Instala uma extensao local do bridge.", tone: "muted" },
990
+ { text: "", tone: "muted" },
991
+ { text: "otto-bridge update --dry-run | otto-bridge update", tone: "primary" },
992
+ { text: "Mostra ou aplica a atualizacao global do pacote.", tone: "muted" },
993
+ { text: "", tone: "muted" },
994
+ { text: "otto-bridge version | otto-bridge unpair", tone: "primary" },
995
+ { text: "Mostra a versao instalada ou remove o pairing local.", tone: "muted" },
996
+ { text: "", tone: "muted" },
997
+ { text: "Dentro do console: /help, /model fast|thinking, /status, /clear, /exit", tone: "muted" },
998
+ ]));
834
999
  await pauseForEnter(rl);
835
1000
  }
836
1001
  async function pickHomeChoice(rl, paired) {
@@ -838,32 +1003,46 @@ async function pickHomeChoice(rl, paired) {
838
1003
  const options = paired
839
1004
  ? [
840
1005
  `${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`,
1006
+ `${style("2.", ANSI.brandBlue, supportsAnsi())} Terminal`,
1007
+ `${style("3.", ANSI.brandBlue, supportsAnsi())} Setup / parear novamente`,
1008
+ `${style("4.", ANSI.brandBlue, supportsAnsi())} Status detalhado`,
1009
+ `${style("5.", ANSI.brandBlue, supportsAnsi())} Extensões instaladas`,
1010
+ `${style("6.", ANSI.brandBlue, supportsAnsi())} Ajuda`,
1011
+ `${style("7.", ANSI.brandBlue, supportsAnsi())} Sair`,
845
1012
  ]
846
1013
  : [
847
1014
  `${style("1.", ANSI.brandBlue, supportsAnsi())} Pairing setup`,
848
- `${style("2.", ANSI.brandBlue, supportsAnsi())} Sair`,
1015
+ `${style("2.", ANSI.brandBlue, supportsAnsi())} Terminal`,
1016
+ `${style("3.", ANSI.brandBlue, supportsAnsi())} Ajuda`,
1017
+ `${style("4.", ANSI.brandBlue, supportsAnsi())} Sair`,
849
1018
  ];
850
1019
  console.log(options.join("\n"));
851
1020
  const answer = await ask(rl, "Escolha");
852
1021
  if (!paired) {
853
- return answer === "1" ? "setup" : "exit";
1022
+ if (answer === "1")
1023
+ return "setup";
1024
+ if (answer === "2")
1025
+ return "terminal";
1026
+ if (answer === "3")
1027
+ return "help";
1028
+ return "exit";
854
1029
  }
855
1030
  if (answer === "1")
856
1031
  return "console";
857
1032
  if (answer === "2")
858
- return "setup";
1033
+ return "terminal";
859
1034
  if (answer === "3")
860
- return "status";
1035
+ return "setup";
861
1036
  if (answer === "4")
1037
+ return "status";
1038
+ if (answer === "5")
862
1039
  return "extensions";
1040
+ if (answer === "6")
1041
+ return "help";
863
1042
  return "exit";
864
1043
  }
865
1044
  export async function launchInteractiveCli(options) {
866
- const rl = await createPromptInterface();
1045
+ let rl = await createPromptInterface();
867
1046
  let runtimeSession = null;
868
1047
  try {
869
1048
  let config = await loadBridgeConfig();
@@ -874,6 +1053,7 @@ export async function launchInteractiveCli(options) {
874
1053
  if (config && setup.openConsole) {
875
1054
  runtimeSession = new CliRuntimeSession(config);
876
1055
  await runtimeSession.ensureStarted();
1056
+ await runtimeSession.waitForReady();
877
1057
  await runOttoConsole(rl, config, runtimeSession);
878
1058
  }
879
1059
  if (!config) {
@@ -882,22 +1062,54 @@ export async function launchInteractiveCli(options) {
882
1062
  }
883
1063
  runtimeSession = runtimeSession || new CliRuntimeSession(config);
884
1064
  await runtimeSession.ensureStarted();
1065
+ await runtimeSession.waitForReady();
885
1066
  for (;;) {
886
1067
  printHubScreen(runtimeSession, "fast");
887
1068
  const choice = await pickHomeChoice(rl, true);
888
1069
  if (choice === "exit") {
889
1070
  break;
890
1071
  }
1072
+ if (choice === "help") {
1073
+ await printHelpView(rl);
1074
+ continue;
1075
+ }
1076
+ if (choice === "terminal") {
1077
+ rl.close();
1078
+ await runTerminalShell({ fromHub: true });
1079
+ rl = await createPromptInterface();
1080
+ const refreshedConfig = await loadBridgeConfig();
1081
+ if (!refreshedConfig) {
1082
+ await runtimeSession?.stop().catch(() => undefined);
1083
+ runtimeSession = null;
1084
+ printWarning("Pairing local removido no terminal. Encerrando o hub.");
1085
+ break;
1086
+ }
1087
+ if (JSON.stringify(refreshedConfig) !== JSON.stringify(config)) {
1088
+ config = refreshedConfig;
1089
+ if (runtimeSession) {
1090
+ await runtimeSession.replaceConfig(refreshedConfig);
1091
+ await runtimeSession.waitForReady();
1092
+ }
1093
+ else {
1094
+ runtimeSession = new CliRuntimeSession(refreshedConfig);
1095
+ await runtimeSession.ensureStarted();
1096
+ await runtimeSession.waitForReady();
1097
+ }
1098
+ }
1099
+ continue;
1100
+ }
891
1101
  if (choice === "setup") {
892
1102
  const setup = await runSetupWizard(rl);
893
1103
  if (setup.config) {
894
1104
  config = setup.config;
895
1105
  if (runtimeSession) {
896
1106
  await runtimeSession.replaceConfig(setup.config);
1107
+ await runtimeSession.waitForReady();
897
1108
  }
898
1109
  else {
899
1110
  runtimeSession = new CliRuntimeSession(setup.config);
900
1111
  await runtimeSession.ensureStarted();
1112
+ await runtimeSession.waitForReady();
901
1113
  }
902
1114
  }
903
1115
  if (setup.config && setup.openConsole && runtimeSession) {
@@ -938,6 +1150,7 @@ export async function runSetupCommand(options) {
938
1150
  if (setup.config && setup.openConsole) {
939
1151
  runtimeSession = new CliRuntimeSession(setup.config);
940
1152
  await runtimeSession.ensureStarted();
1153
+ await runtimeSession.waitForReady();
941
1154
  await runOttoConsole(rl, setup.config, runtimeSession);
942
1155
  }
943
1156
  }
@@ -966,6 +1179,7 @@ export async function runConsoleCommand(initialPrompt) {
966
1179
  }
967
1180
  runtimeSession = new CliRuntimeSession(config);
968
1181
  await runtimeSession.ensureStarted();
1182
+ await runtimeSession.waitForReady();
969
1183
  await runOttoConsole(rl, config, runtimeSession, { initialPrompt });
970
1184
  }
971
1185
  catch (error) {
package/dist/main.js CHANGED
@@ -6,7 +6,7 @@ import { buildInstalledManagedExtensionState, formatManagedBridgeExtensionStatus
6
6
  import { pairDevice } from "./pairing.js";
7
7
  import { detectWhatsAppBackgroundStatus, runWhatsAppBackgroundSetup, } from "./whatsapp_background.js";
8
8
  import { BRIDGE_PACKAGE_NAME, BRIDGE_VERSION, DEFAULT_PAIR_TIMEOUT_SECONDS, DEFAULT_POLL_INTERVAL_MS, } from "./types.js";
9
- import { launchInteractiveCli, runConsoleCommand, runSetupCommand, } from "./cli_terminal.js";
9
+ import { launchInteractiveCli, runConsoleCommand, runSetupCommand, runTerminalShell, } from "./cli_terminal.js";
10
10
  const RUNTIME_STATUS_FRESHNESS_MS = 90_000;
11
11
  const UPDATE_RETRY_DELAYS_MS = [0, 8_000, 20_000];
12
12
  function parseArgs(argv) {
@@ -104,29 +104,37 @@ function resolveExecutorOverrides(args, current) {
104
104
  }
105
105
  function printUsage() {
106
106
  console.log(`Usage:
107
- otto-bridge
108
- otto-bridge setup
109
- otto-bridge console
110
- otto-bridge status
107
+ otto-bridge abre o hub principal e sobe o runtime local
108
+ otto-bridge setup refaz o pairing no terminal
109
+ otto-bridge console abre direto o console do Otto
110
+ otto-bridge terminal abre um shell local e volta ao hub ao sair
111
+ otto-bridge status mostra o pairing atual em JSON
111
112
  otto-bridge extensions --list
112
113
  otto-bridge extensions --install <name>
113
114
  otto-bridge extensions --setup <name>
114
115
  otto-bridge extensions --status <name>
115
116
  otto-bridge extensions --uninstall <name>
116
- otto-bridge version
117
117
  otto-bridge update [--tag latest|next] [--dry-run]
118
118
  otto-bridge unpair
119
+ otto-bridge version
120
+
121
+ Console:
122
+ /help
123
+ /model fast
124
+ /model thinking
125
+ /status
126
+ /clear
127
+ /exit
119
128
 
120
129
  Examples:
121
130
  otto-bridge
122
- otto-bridge setup
123
- otto-bridge console
131
+ otto-bridge terminal
132
+ otto-bridge status
124
133
  otto-bridge extensions --install <name>
125
134
  otto-bridge extensions --setup <name>
126
- otto-bridge extensions --status <name>
127
- otto-bridge extensions --list
128
- otto-bridge version
135
+ otto-bridge update
129
136
  otto-bridge update --dry-run
137
+ otto-bridge unpair
130
138
  otto-bridge --version`);
131
139
  }
132
140
  function printVersion() {
@@ -557,6 +565,9 @@ async function main() {
557
565
  case "console":
558
566
  await runConsoleCommand(option(args, "prompt"));
559
567
  return;
568
+ case "terminal":
569
+ await runTerminalShell();
570
+ return;
560
571
  case "pair":
561
572
  await runPairCommand(args);
562
573
  return;
package/dist/types.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export const BRIDGE_CONFIG_VERSION = 1;
2
- export const BRIDGE_VERSION = "1.0.3";
2
+ export const BRIDGE_VERSION = "1.0.5";
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.3",
3
+ "version": "1.0.5",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Local companion for Otto Bridge device pairing and WebSocket runtime.",
@@ -40,6 +40,7 @@
40
40
  "pair": "node dist/main.js pair",
41
41
  "setup": "node dist/main.js setup",
42
42
  "console": "node dist/main.js console",
43
+ "terminal": "node dist/main.js terminal",
43
44
  "run": "node dist/main.js run",
44
45
  "status": "node dist/main.js status",
45
46
  "version:cli": "node dist/main.js version",
@@ -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.3");
27
+ console.log("\n[otto-bridge] Welcome to OTTOAI 1.0.5");
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"], {