@leg3ndy/otto-bridge 1.0.4 → 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.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.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.4.tgz
41
+ npm install -g ./leg3ndy-otto-bridge-1.0.5.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.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.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.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.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.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
 
@@ -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.5`:
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.5`:
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.5
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;
@@ -486,36 +489,43 @@ function printConsoleScreen(runtimeSession, modelMode) {
486
489
  console.log("");
487
490
  console.log(renderInfoCard(buildWelcomeCard(runtimeSession, modelMode)));
488
491
  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");
491
- console.log("");
492
492
  }
493
493
  function renderPromptFrameLine(width, edgeLeft, edgeRight) {
494
494
  return style(`${edgeLeft}${"─".repeat(width)}${edgeRight}`, ANSI.brandBlue, supportsAnsi());
495
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
+ }
496
509
  async function askConsoleInput(rl) {
497
510
  if (!supportsAnsi()) {
511
+ printMuted(CONSOLE_PLACEHOLDER);
512
+ printSoft(`Comandos: ${CONSOLE_COMMAND_HINT}`);
498
513
  return normalizeText(await question(rl, "> "));
499
514
  }
500
515
  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}`);
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`);
514
523
  cursorTo(output, 0);
515
- moveCursor(output, 0, -1);
524
+ moveCursor(output, 0, -2);
516
525
  cursorTo(output, 4);
517
526
  const answer = normalizeText(await question(rl, ""));
518
- output.write("\n");
527
+ cursorTo(output, 0);
528
+ moveCursor(output, 0, 1);
519
529
  return answer;
520
530
  }
521
531
  function printAssistantMessage(message) {
@@ -645,6 +655,43 @@ function printStructuredOutcome(job) {
645
655
  console.log("");
646
656
  console.log(rendered);
647
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
+ }
648
695
  function buildConversationSummary(summary, job) {
649
696
  const rendered = renderStructuredOutcome(job, { compact: true });
650
697
  if (!rendered) {
@@ -764,7 +811,9 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
764
811
  const sessionId = randomUUID();
765
812
  const conversation = [];
766
813
  const printConsoleHelp = () => {
814
+ printSection("Console");
767
815
  printSoft("Comandos: /help, /model [fast|thinking], /status, /clear, /exit");
816
+ printSoft("Bridge: otto-bridge terminal, otto-bridge extensions --install <name>, otto-bridge update");
768
817
  };
769
818
  const handlePrompt = async (promptText) => {
770
819
  const normalizedPrompt = normalizeText(promptText);
@@ -927,12 +976,24 @@ async function printHelpView(rl) {
927
976
  { text: "otto-bridge", tone: "primary" },
928
977
  { text: "Abre o hub principal e sobe o runtime local automaticamente.", tone: "muted" },
929
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" },
930
982
  { text: "otto-bridge setup", tone: "primary" },
931
983
  { text: "Repareia o dispositivo e atualiza config/executor.", tone: "muted" },
932
984
  { text: "", tone: "muted" },
933
985
  { text: "otto-bridge console", tone: "primary" },
934
986
  { text: "Abre direto o console do Otto no terminal.", tone: "muted" },
935
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" },
936
997
  { text: "Dentro do console: /help, /model fast|thinking, /status, /clear, /exit", tone: "muted" },
937
998
  ]));
938
999
  await pauseForEnter(rl);
@@ -942,22 +1003,26 @@ async function pickHomeChoice(rl, paired) {
942
1003
  const options = paired
943
1004
  ? [
944
1005
  `${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`,
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`,
949
1010
  `${style("6.", ANSI.brandBlue, supportsAnsi())} Ajuda`,
1011
+ `${style("7.", ANSI.brandBlue, supportsAnsi())} Sair`,
950
1012
  ]
951
1013
  : [
952
1014
  `${style("1.", ANSI.brandBlue, supportsAnsi())} Pairing setup`,
953
- `${style("2.", ANSI.brandBlue, supportsAnsi())} Sair`,
1015
+ `${style("2.", ANSI.brandBlue, supportsAnsi())} Terminal`,
954
1016
  `${style("3.", ANSI.brandBlue, supportsAnsi())} Ajuda`,
1017
+ `${style("4.", ANSI.brandBlue, supportsAnsi())} Sair`,
955
1018
  ];
956
1019
  console.log(options.join("\n"));
957
1020
  const answer = await ask(rl, "Escolha");
958
1021
  if (!paired) {
959
1022
  if (answer === "1")
960
1023
  return "setup";
1024
+ if (answer === "2")
1025
+ return "terminal";
961
1026
  if (answer === "3")
962
1027
  return "help";
963
1028
  return "exit";
@@ -965,17 +1030,19 @@ async function pickHomeChoice(rl, paired) {
965
1030
  if (answer === "1")
966
1031
  return "console";
967
1032
  if (answer === "2")
968
- return "setup";
1033
+ return "terminal";
969
1034
  if (answer === "3")
970
- return "status";
1035
+ return "setup";
971
1036
  if (answer === "4")
1037
+ return "status";
1038
+ if (answer === "5")
972
1039
  return "extensions";
973
1040
  if (answer === "6")
974
1041
  return "help";
975
1042
  return "exit";
976
1043
  }
977
1044
  export async function launchInteractiveCli(options) {
978
- const rl = await createPromptInterface();
1045
+ let rl = await createPromptInterface();
979
1046
  let runtimeSession = null;
980
1047
  try {
981
1048
  let config = await loadBridgeConfig();
@@ -1006,6 +1073,31 @@ export async function launchInteractiveCli(options) {
1006
1073
  await printHelpView(rl);
1007
1074
  continue;
1008
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
+ }
1009
1101
  if (choice === "setup") {
1010
1102
  const setup = await runSetupWizard(rl);
1011
1103
  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/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.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.4",
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.4");
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"], {