@leg3ndy/otto-bridge 1.0.4 → 1.0.6

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.4`, com estabilizacao do TTY, pensamento visivel no modo `Thinking` e replay web mais compacto, veja [`leg3ndy-ai-backend/docs/OTTO_BRIDGE_1_0_4_PATCH.md`](../leg3ndy-ai-backend/docs/OTTO_BRIDGE_1_0_4_PATCH.md).
18
+ Para a release atual `1.0.6`, com aviso de update no proprio CLI e input-box refinado do console, veja [`leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_0_6_PATCH.md`](../leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_0_6_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.4.tgz
41
+ npm install -g ./leg3ndy-otto-bridge-1.0.6.tgz
42
42
  ```
43
43
 
44
- Na linha `1.0.4`, `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.6`, `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.4` 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.6` 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.4` consolida o hub terminal como fluxo principal, estabiliza o TTY do console, deixa o modo `Thinking` visualmente claro e reduz o ruido do replay no modal web.
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.6` adiciona aviso de update no proprio CLI e refina o input-box do console sem voltar a poluir a superficie com aliases legados.
49
49
 
50
50
  ## Publicacao
51
51
 
@@ -147,9 +147,17 @@ No modo `OttoAI Thinking`, o terminal agora marca explicitamente o trecho de rac
147
147
 
148
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.
149
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
+
150
158
  ### WhatsApp Web em background
151
159
 
152
- Fluxo recomendado na linha `1.0.4`:
160
+ Fluxo recomendado na linha `1.0.6`:
153
161
 
154
162
  ```bash
155
163
  otto-bridge extensions --install whatsappweb
@@ -159,13 +167,13 @@ otto-bridge extensions --status whatsappweb
159
167
 
160
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.
161
169
 
162
- Contrato da linha `1.0.4`:
170
+ Contrato da linha `1.0.6`:
163
171
 
164
172
  - `otto-bridge extensions --setup whatsappweb`: autentica a sessao uma vez
165
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
166
174
  - ao fechar o `otto-bridge`: o browser em background e desligado, mas a sessao local fica lembrada para o proximo boot
167
175
 
168
- ## Handoff rapido da linha 1.0.4
176
+ ## Handoff rapido da linha 1.0.6
169
177
 
170
178
  Ja fechado no codigo:
171
179
 
@@ -1,4 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
+ import { spawn } from "node:child_process";
2
3
  import { createInterface } from "node:readline/promises";
3
4
  import { cursorTo, moveCursor, } from "node:readline";
4
5
  import process, { stdin as input, stdout as output } from "node:process";
@@ -49,6 +50,8 @@ const MAX_RENDERED_LIST_ENTRIES = 28;
49
50
  const MAX_RENDERED_LIST_ENTRIES_COMPACT = 10;
50
51
  const MAX_RENDERED_FILE_CHARS = 6_000;
51
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";
52
55
  class CliRuntimeSession {
53
56
  config;
54
57
  runtime = null;
@@ -56,6 +59,7 @@ class CliRuntimeSession {
56
59
  status = "offline";
57
60
  detail = "Aguardando pairing.";
58
61
  lastError = null;
62
+ releaseNotice = null;
59
63
  constructor(config) {
60
64
  this.config = config;
61
65
  }
@@ -89,8 +93,24 @@ class CliRuntimeSession {
89
93
  }
90
94
  return;
91
95
  case "update_required":
96
+ this.releaseNotice = {
97
+ kind: "required",
98
+ message: event.message,
99
+ currentVersion: normalizeText(event.currentVersion),
100
+ latestVersion: normalizeText(event.latestVersion),
101
+ minSupportedVersion: normalizeText(event.minSupportedVersion),
102
+ updateCommand: normalizeText(event.updateCommand) || "otto-bridge update",
103
+ };
104
+ return;
92
105
  case "update_available":
93
- this.detail = truncate(event.message || this.detail, 160);
106
+ this.releaseNotice = {
107
+ kind: "available",
108
+ message: event.message,
109
+ currentVersion: normalizeText(event.currentVersion),
110
+ latestVersion: normalizeText(event.latestVersion),
111
+ minSupportedVersion: normalizeText(event.minSupportedVersion),
112
+ updateCommand: normalizeText(event.updateCommand) || "otto-bridge update",
113
+ };
94
114
  return;
95
115
  default:
96
116
  return;
@@ -177,6 +197,9 @@ class CliRuntimeSession {
177
197
  getLastError() {
178
198
  return this.lastError;
179
199
  }
200
+ getReleaseNotice() {
201
+ return this.releaseNotice;
202
+ }
180
203
  }
181
204
  function style(text, color, enabled = true) {
182
205
  if (!enabled) {
@@ -397,6 +420,7 @@ function extractConfirmationPrompt(job) {
397
420
  || payload.confirmation_message) || "O Otto está aguardando sua confirmação para continuar.";
398
421
  }
399
422
  function renderStatusOverview(config, runtimeSession) {
423
+ const releaseNotice = runtimeSession?.getReleaseNotice();
400
424
  return [
401
425
  `${style("Device", ANSI.brandBlue, supportsAnsi())}: ${config.deviceName}`,
402
426
  `${style("Device ID", ANSI.brandBlue, supportsAnsi())}: ${config.deviceId}`,
@@ -407,6 +431,21 @@ function renderStatusOverview(config, runtimeSession) {
407
431
  ...(runtimeSession?.getStatusDetail()
408
432
  ? [`${style("Runtime note", ANSI.brandBlue, supportsAnsi())}: ${runtimeSession.getStatusDetail()}`]
409
433
  : []),
434
+ ...(releaseNotice
435
+ ? [
436
+ `${style("Bridge update", ANSI.brandBlue, supportsAnsi())}: ${releaseNotice.kind === "required" ? "required" : "available"}`,
437
+ ...(releaseNotice.currentVersion
438
+ ? [`${style("Current version", ANSI.brandBlue, supportsAnsi())}: ${releaseNotice.currentVersion}`]
439
+ : []),
440
+ ...(releaseNotice.latestVersion
441
+ ? [`${style("Latest version", ANSI.brandBlue, supportsAnsi())}: ${releaseNotice.latestVersion}`]
442
+ : []),
443
+ ...(releaseNotice.minSupportedVersion
444
+ ? [`${style("Min supported", ANSI.brandBlue, supportsAnsi())}: ${releaseNotice.minSupportedVersion}`]
445
+ : []),
446
+ `${style("Update command", ANSI.brandBlue, supportsAnsi())}: ${releaseNotice.updateCommand || "otto-bridge update"}`,
447
+ ]
448
+ : []),
410
449
  `${style("Config", ANSI.brandBlue, supportsAnsi())}: ${getBridgeConfigPath()}`,
411
450
  ];
412
451
  }
@@ -439,6 +478,9 @@ function styleCardLine(line, tone, enabled) {
439
478
  if (tone === "title") {
440
479
  return `${ANSI.bold}${ANSI.brandBlue}${line}${ANSI.reset}`;
441
480
  }
481
+ if (tone === "warning") {
482
+ return `${ANSI.bold}${ANSI.amber}${line}${ANSI.reset}`;
483
+ }
442
484
  if (tone === "muted") {
443
485
  return `${ANSI.slateItalic}${line}${ANSI.reset}`;
444
486
  }
@@ -474,49 +516,151 @@ function buildWelcomeCard(runtimeSession, modelMode) {
474
516
  { text: renderRuntimeHeadline(runtimeSession), tone: "muted" },
475
517
  ];
476
518
  }
519
+ function buildBridgeReleaseCard(notice) {
520
+ const isRequired = notice.kind === "required";
521
+ return [
522
+ {
523
+ text: isRequired ? "Atualização obrigatória do bridge" : "Atualização disponível do bridge",
524
+ tone: "warning",
525
+ },
526
+ { text: "", tone: "muted" },
527
+ {
528
+ text: isRequired
529
+ ? `Atual: ${notice.currentVersion || "desconhecida"} | mínima: ${notice.minSupportedVersion || "desconhecida"}`
530
+ : `Atual: ${notice.currentVersion || "desconhecida"} | nova: ${notice.latestVersion || "desconhecida"}`,
531
+ tone: "muted",
532
+ },
533
+ {
534
+ text: isRequired
535
+ ? "Este bridge precisa ser atualizado para continuar suportado."
536
+ : "Há uma nova versão do Otto Bridge pronta para instalar.",
537
+ tone: "muted",
538
+ },
539
+ { text: `Rode: ${notice.updateCommand || "otto-bridge update"}`, tone: "primary" },
540
+ ];
541
+ }
477
542
  function printHubScreen(runtimeSession, modelMode) {
478
543
  clearScreen();
479
544
  console.log(renderBanner());
480
545
  console.log("");
481
546
  console.log(renderInfoCard(buildWelcomeCard(runtimeSession, modelMode)));
547
+ const releaseNotice = runtimeSession?.getReleaseNotice();
548
+ if (releaseNotice) {
549
+ console.log("");
550
+ console.log(renderInfoCard(buildBridgeReleaseCard(releaseNotice)));
551
+ }
482
552
  }
483
553
  function printConsoleScreen(runtimeSession, modelMode) {
484
554
  clearScreen();
485
555
  console.log(renderBanner());
486
556
  console.log("");
487
557
  console.log(renderInfoCard(buildWelcomeCard(runtimeSession, modelMode)));
488
- console.log("");
489
- console.log(style("Peça algo ao Otto", `${ANSI.bold}${ANSI.white}`, supportsAnsi()));
490
- printSoft("Comandos: /help, /model [fast|thinking], /status, /clear, /exit");
558
+ const releaseNotice = runtimeSession.getReleaseNotice();
559
+ if (releaseNotice) {
560
+ console.log("");
561
+ console.log(renderInfoCard(buildBridgeReleaseCard(releaseNotice)));
562
+ }
491
563
  console.log("");
492
564
  }
493
565
  function renderPromptFrameLine(width, edgeLeft, edgeRight) {
494
566
  return style(`${edgeLeft}${"─".repeat(width)}${edgeRight}`, ANSI.brandBlue, supportsAnsi());
495
567
  }
568
+ function renderConsolePromptContentLine(text, width, tone) {
569
+ const enabled = supportsAnsi();
570
+ const border = style("│", ANSI.brandBlue, enabled);
571
+ if (tone === "input") {
572
+ const prompt = `${style(">", `${ANSI.bold}${ANSI.white}`, enabled)} ${" ".repeat(Math.max(0, width - 2))}`;
573
+ return `${border} ${prompt} ${border}`;
574
+ }
575
+ const clipped = padRight(truncate(text, width), width);
576
+ const body = tone === "placeholder"
577
+ ? style(clipped, `${ANSI.bold}${ANSI.white}`, enabled)
578
+ : style(clipped, ANSI.slateItalic, enabled);
579
+ return `${border} ${body} ${border}`;
580
+ }
496
581
  async function askConsoleInput(rl) {
497
- if (!supportsAnsi()) {
582
+ if (!supportsAnsi() || typeof input.setRawMode !== "function" || !input.isTTY) {
583
+ printSoft(`Comandos: ${CONSOLE_COMMAND_HINT}`);
584
+ console.log("");
498
585
  return normalizeText(await question(rl, "> "));
499
586
  }
500
- const availableWidth = Number(output.columns || 96);
501
- const inputWidth = Math.max(28, Math.min(availableWidth - 12, 92));
502
- const top = renderPromptFrameLine(inputWidth + 4, "┌", "┐");
503
- const bottom = renderPromptFrameLine(inputWidth + 4, "└", "┘");
504
- const middle = [
505
- style("│", ANSI.brandBlue, true),
506
- " ",
507
- style(">", `${ANSI.bold}${ANSI.white}`, true),
508
- " ",
509
- " ".repeat(inputWidth),
510
- " ",
511
- style("│", ANSI.brandBlue, true),
512
- ].join("");
513
- output.write(`${top}\n${middle}\n${bottom}`);
514
- cursorTo(output, 0);
515
- moveCursor(output, 0, -1);
516
- cursorTo(output, 4);
517
- const answer = normalizeText(await question(rl, ""));
518
- output.write("\n");
519
- return answer;
587
+ rl.pause();
588
+ input.setRawMode(true);
589
+ return await new Promise((resolve, reject) => {
590
+ const enabled = supportsAnsi();
591
+ const availableWidth = Number(output.columns || 96);
592
+ const innerWidth = Math.max(42, Math.min(availableWidth - 8, 116));
593
+ const sectionTopOffsetFromInputLine = 3;
594
+ let renderedOnce = false;
595
+ let value = "";
596
+ const cleanup = () => {
597
+ input.removeListener("data", onData);
598
+ input.setRawMode(false);
599
+ rl.resume();
600
+ };
601
+ const renderInputContent = () => {
602
+ const promptPlain = "> ";
603
+ const promptStyled = style(">", `${ANSI.bold}${ANSI.white}`, enabled);
604
+ const maxValueLength = Math.max(0, innerWidth - promptPlain.length);
605
+ if (!value) {
606
+ const placeholder = truncate(CONSOLE_PLACEHOLDER, maxValueLength);
607
+ const padded = `${style(placeholder, ANSI.slate, enabled)}${" ".repeat(Math.max(0, maxValueLength - placeholder.length))}`;
608
+ return `${promptStyled} ${padded}`;
609
+ }
610
+ const visibleValue = value.length > maxValueLength
611
+ ? value.slice(value.length - maxValueLength)
612
+ : value;
613
+ return `${promptStyled} ${visibleValue}${" ".repeat(Math.max(0, maxValueLength - visibleValue.length))}`;
614
+ };
615
+ const render = () => {
616
+ if (renderedOnce) {
617
+ cursorTo(output, 0);
618
+ moveCursor(output, 0, -sectionTopOffsetFromInputLine);
619
+ }
620
+ else {
621
+ renderedOnce = true;
622
+ }
623
+ const commands = style(`Comandos: ${CONSOLE_COMMAND_HINT}`, ANSI.slateItalic, enabled);
624
+ const top = renderPromptFrameLine(innerWidth + 2, "┌", "┐");
625
+ const border = style("│", ANSI.brandBlue, enabled);
626
+ const middle = `${border} ${renderInputContent()} ${border}`;
627
+ const bottom = renderPromptFrameLine(innerWidth + 2, "└", "┘");
628
+ output.write("\u001b[J");
629
+ output.write(`${commands}\n\n${top}\n${middle}\n${bottom}\n`);
630
+ cursorTo(output, 0);
631
+ moveCursor(output, 0, -2);
632
+ const visibleValueLength = Math.min(value.length, Math.max(0, innerWidth - 2));
633
+ cursorTo(output, 4 + visibleValueLength);
634
+ };
635
+ const onData = (chunk) => {
636
+ const text = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
637
+ for (const char of Array.from(text)) {
638
+ if (char === "\u0003") {
639
+ cleanup();
640
+ reject(createCliExitError());
641
+ return;
642
+ }
643
+ if (char === "\r" || char === "\n") {
644
+ cleanup();
645
+ output.write("\n");
646
+ resolve(normalizeText(value));
647
+ return;
648
+ }
649
+ if (char === "\u007f" || char === "\b") {
650
+ value = value.slice(0, -1);
651
+ render();
652
+ continue;
653
+ }
654
+ if (char === "\u001b") {
655
+ continue;
656
+ }
657
+ value += char;
658
+ render();
659
+ }
660
+ };
661
+ render();
662
+ input.on("data", onData);
663
+ });
520
664
  }
521
665
  function printAssistantMessage(message) {
522
666
  const text = normalizeText(message);
@@ -637,6 +781,16 @@ export function renderStructuredOutcome(job, options) {
637
781
  }
638
782
  return lines.join("\n").trim();
639
783
  }
784
+ export function renderBridgeReleaseNoticeCard(notice) {
785
+ return renderInfoCard(buildBridgeReleaseCard({
786
+ kind: notice.kind,
787
+ message: normalizeText(notice.message),
788
+ currentVersion: normalizeText(notice.currentVersion),
789
+ latestVersion: normalizeText(notice.latestVersion),
790
+ minSupportedVersion: normalizeText(notice.minSupportedVersion),
791
+ updateCommand: normalizeText(notice.updateCommand) || "otto-bridge update",
792
+ }));
793
+ }
640
794
  function printStructuredOutcome(job) {
641
795
  const rendered = renderStructuredOutcome(job);
642
796
  if (!rendered) {
@@ -645,6 +799,43 @@ function printStructuredOutcome(job) {
645
799
  console.log("");
646
800
  console.log(rendered);
647
801
  }
802
+ function resolveTerminalShellCommand() {
803
+ if (process.platform === "win32") {
804
+ return {
805
+ command: process.env.COMSPEC || "cmd.exe",
806
+ args: [],
807
+ };
808
+ }
809
+ return {
810
+ command: process.env.SHELL || "/bin/zsh",
811
+ args: ["-l"],
812
+ };
813
+ }
814
+ export async function runTerminalShell(options) {
815
+ clearScreen();
816
+ console.log(renderBanner());
817
+ console.log("");
818
+ printSection("Terminal");
819
+ printMuted(options?.fromHub
820
+ ? "Saindo temporariamente para o shell local. Digite `exit` para voltar ao Otto Bridge."
821
+ : "Abrindo o shell local. Digite `exit` para encerrar e voltar ao terminal anterior.");
822
+ console.log("");
823
+ const shell = resolveTerminalShellCommand();
824
+ await new Promise((resolve, reject) => {
825
+ const child = spawn(shell.command, shell.args, {
826
+ stdio: "inherit",
827
+ env: process.env,
828
+ });
829
+ child.on("error", reject);
830
+ child.on("exit", (code, signal) => {
831
+ if (signal || code === 0 || code === 130) {
832
+ resolve();
833
+ return;
834
+ }
835
+ reject(new Error(`Terminal local encerrou com code ${code ?? "unknown"}.`));
836
+ });
837
+ });
838
+ }
648
839
  function buildConversationSummary(summary, job) {
649
840
  const rendered = renderStructuredOutcome(job, { compact: true });
650
841
  if (!rendered) {
@@ -764,7 +955,9 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
764
955
  const sessionId = randomUUID();
765
956
  const conversation = [];
766
957
  const printConsoleHelp = () => {
958
+ printSection("Console");
767
959
  printSoft("Comandos: /help, /model [fast|thinking], /status, /clear, /exit");
960
+ printSoft("Bridge: otto-bridge terminal, otto-bridge extensions --install <name>, otto-bridge update");
768
961
  };
769
962
  const handlePrompt = async (promptText) => {
770
963
  const normalizedPrompt = normalizeText(promptText);
@@ -927,12 +1120,24 @@ async function printHelpView(rl) {
927
1120
  { text: "otto-bridge", tone: "primary" },
928
1121
  { text: "Abre o hub principal e sobe o runtime local automaticamente.", tone: "muted" },
929
1122
  { text: "", tone: "muted" },
1123
+ { text: "otto-bridge terminal", tone: "primary" },
1124
+ { text: "Abre um shell local interativo sem desligar o bridge.", tone: "muted" },
1125
+ { text: "", tone: "muted" },
930
1126
  { text: "otto-bridge setup", tone: "primary" },
931
1127
  { text: "Repareia o dispositivo e atualiza config/executor.", tone: "muted" },
932
1128
  { text: "", tone: "muted" },
933
1129
  { text: "otto-bridge console", tone: "primary" },
934
1130
  { text: "Abre direto o console do Otto no terminal.", tone: "muted" },
935
1131
  { text: "", tone: "muted" },
1132
+ { text: "otto-bridge extensions --install <name>", tone: "primary" },
1133
+ { text: "Instala uma extensao local do bridge.", tone: "muted" },
1134
+ { text: "", tone: "muted" },
1135
+ { text: "otto-bridge update --dry-run | otto-bridge update", tone: "primary" },
1136
+ { text: "Mostra ou aplica a atualizacao global do pacote.", tone: "muted" },
1137
+ { text: "", tone: "muted" },
1138
+ { text: "otto-bridge version | otto-bridge unpair", tone: "primary" },
1139
+ { text: "Mostra a versao instalada ou remove o pairing local.", tone: "muted" },
1140
+ { text: "", tone: "muted" },
936
1141
  { text: "Dentro do console: /help, /model fast|thinking, /status, /clear, /exit", tone: "muted" },
937
1142
  ]));
938
1143
  await pauseForEnter(rl);
@@ -942,22 +1147,26 @@ async function pickHomeChoice(rl, paired) {
942
1147
  const options = paired
943
1148
  ? [
944
1149
  `${style("1.", ANSI.brandBlue, supportsAnsi())} Otto Console`,
945
- `${style("2.", ANSI.brandBlue, supportsAnsi())} Setup / parear novamente`,
946
- `${style("3.", ANSI.brandBlue, supportsAnsi())} Status detalhado`,
947
- `${style("4.", ANSI.brandBlue, supportsAnsi())} Extensões instaladas`,
948
- `${style("5.", ANSI.brandBlue, supportsAnsi())} Sair`,
1150
+ `${style("2.", ANSI.brandBlue, supportsAnsi())} Terminal`,
1151
+ `${style("3.", ANSI.brandBlue, supportsAnsi())} Setup / parear novamente`,
1152
+ `${style("4.", ANSI.brandBlue, supportsAnsi())} Status detalhado`,
1153
+ `${style("5.", ANSI.brandBlue, supportsAnsi())} Extensões instaladas`,
949
1154
  `${style("6.", ANSI.brandBlue, supportsAnsi())} Ajuda`,
1155
+ `${style("7.", ANSI.brandBlue, supportsAnsi())} Sair`,
950
1156
  ]
951
1157
  : [
952
1158
  `${style("1.", ANSI.brandBlue, supportsAnsi())} Pairing setup`,
953
- `${style("2.", ANSI.brandBlue, supportsAnsi())} Sair`,
1159
+ `${style("2.", ANSI.brandBlue, supportsAnsi())} Terminal`,
954
1160
  `${style("3.", ANSI.brandBlue, supportsAnsi())} Ajuda`,
1161
+ `${style("4.", ANSI.brandBlue, supportsAnsi())} Sair`,
955
1162
  ];
956
1163
  console.log(options.join("\n"));
957
1164
  const answer = await ask(rl, "Escolha");
958
1165
  if (!paired) {
959
1166
  if (answer === "1")
960
1167
  return "setup";
1168
+ if (answer === "2")
1169
+ return "terminal";
961
1170
  if (answer === "3")
962
1171
  return "help";
963
1172
  return "exit";
@@ -965,17 +1174,19 @@ async function pickHomeChoice(rl, paired) {
965
1174
  if (answer === "1")
966
1175
  return "console";
967
1176
  if (answer === "2")
968
- return "setup";
1177
+ return "terminal";
969
1178
  if (answer === "3")
970
- return "status";
1179
+ return "setup";
971
1180
  if (answer === "4")
1181
+ return "status";
1182
+ if (answer === "5")
972
1183
  return "extensions";
973
1184
  if (answer === "6")
974
1185
  return "help";
975
1186
  return "exit";
976
1187
  }
977
1188
  export async function launchInteractiveCli(options) {
978
- const rl = await createPromptInterface();
1189
+ let rl = await createPromptInterface();
979
1190
  let runtimeSession = null;
980
1191
  try {
981
1192
  let config = await loadBridgeConfig();
@@ -1006,6 +1217,31 @@ export async function launchInteractiveCli(options) {
1006
1217
  await printHelpView(rl);
1007
1218
  continue;
1008
1219
  }
1220
+ if (choice === "terminal") {
1221
+ rl.close();
1222
+ await runTerminalShell({ fromHub: true });
1223
+ rl = await createPromptInterface();
1224
+ const refreshedConfig = await loadBridgeConfig();
1225
+ if (!refreshedConfig) {
1226
+ await runtimeSession?.stop().catch(() => undefined);
1227
+ runtimeSession = null;
1228
+ printWarning("Pairing local removido no terminal. Encerrando o hub.");
1229
+ break;
1230
+ }
1231
+ if (JSON.stringify(refreshedConfig) !== JSON.stringify(config)) {
1232
+ config = refreshedConfig;
1233
+ if (runtimeSession) {
1234
+ await runtimeSession.replaceConfig(refreshedConfig);
1235
+ await runtimeSession.waitForReady();
1236
+ }
1237
+ else {
1238
+ runtimeSession = new CliRuntimeSession(refreshedConfig);
1239
+ await runtimeSession.ensureStarted();
1240
+ await runtimeSession.waitForReady();
1241
+ }
1242
+ }
1243
+ continue;
1244
+ }
1009
1245
  if (choice === "setup") {
1010
1246
  const setup = await runSetupWizard(rl);
1011
1247
  if (setup.config) {
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) {
@@ -107,6 +107,7 @@ function printUsage() {
107
107
  otto-bridge abre o hub principal e sobe o runtime local
108
108
  otto-bridge setup refaz o pairing no terminal
109
109
  otto-bridge console abre direto o console do Otto
110
+ otto-bridge terminal abre um shell local e volta ao hub ao sair
110
111
  otto-bridge status mostra o pairing atual em JSON
111
112
  otto-bridge extensions --list
112
113
  otto-bridge extensions --install <name>
@@ -127,9 +128,13 @@ Console:
127
128
 
128
129
  Examples:
129
130
  otto-bridge
131
+ otto-bridge terminal
132
+ otto-bridge status
130
133
  otto-bridge extensions --install <name>
131
134
  otto-bridge extensions --setup <name>
135
+ otto-bridge update
132
136
  otto-bridge update --dry-run
137
+ otto-bridge unpair
133
138
  otto-bridge --version`);
134
139
  }
135
140
  function printVersion() {
@@ -560,6 +565,9 @@ async function main() {
560
565
  case "console":
561
566
  await runConsoleCommand(option(args, "prompt"));
562
567
  return;
568
+ case "terminal":
569
+ await runTerminalShell();
570
+ return;
563
571
  case "pair":
564
572
  await runPairCommand(args);
565
573
  return;
package/dist/runtime.js CHANGED
@@ -389,13 +389,27 @@ export class BridgeRuntime {
389
389
  if (updateRequired) {
390
390
  const message = `[otto-bridge] update required current=${this.config.bridgeVersion} min_supported=${minSupportedVersion || "unknown"} latest=${latestVersion || "unknown"} command="${updateCommand}"`;
391
391
  this.logWarn(message);
392
- this.emit({ type: "update_required", message });
392
+ this.emit({
393
+ type: "update_required",
394
+ message,
395
+ currentVersion: this.config.bridgeVersion,
396
+ latestVersion,
397
+ minSupportedVersion,
398
+ updateCommand,
399
+ });
393
400
  return;
394
401
  }
395
402
  if (updateAvailable) {
396
403
  const message = `[otto-bridge] update available current=${this.config.bridgeVersion} latest=${latestVersion || "unknown"} command="${updateCommand}"`;
397
404
  this.logInfo(message);
398
- this.emit({ type: "update_available", message });
405
+ this.emit({
406
+ type: "update_available",
407
+ message,
408
+ currentVersion: this.config.bridgeVersion,
409
+ latestVersion,
410
+ minSupportedVersion,
411
+ updateCommand,
412
+ });
399
413
  }
400
414
  }
401
415
  clearPendingConfirmations(jobId) {
package/dist/types.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export const BRIDGE_CONFIG_VERSION = 1;
2
- export const BRIDGE_VERSION = "1.0.4";
2
+ export const BRIDGE_VERSION = "1.0.6";
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.4",
3
+ "version": "1.0.6",
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.4");
27
+ console.log("\n[otto-bridge] Welcome to OTTOAI 1.0.6");
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"], {