@leg3ndy/otto-bridge 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,7 +15,7 @@ Para o estado atual da arquitetura, capacidades entregues, limitacoes e roadmap
15
15
 
16
16
  Para o corte de arquitetura do `0.9.0`, veja [`leg3ndy-ai-backend/docs/OTTO_BRIDGE_0_9_0_RELEASE.md`](../leg3ndy-ai-backend/docs/OTTO_BRIDGE_0_9_0_RELEASE.md).
17
17
 
18
- Para a release atual `1.0.1`, com runtime agentico formal, hub terminal e console alinhado ao Otto da web, veja [`leg3ndy-ai-backend/docs/OTTO_BRIDGE_1_0_1_PATCH.md`](../leg3ndy-ai-backend/docs/OTTO_BRIDGE_1_0_1_PATCH.md).
18
+ Para a release atual `1.0.2`, com runtime agentico formal, hub terminal refinado e console alinhado ao Otto da web, veja [`leg3ndy-ai-backend/docs/OTTO_BRIDGE_1_0_2_PATCH.md`](../leg3ndy-ai-backend/docs/OTTO_BRIDGE_1_0_2_PATCH.md).
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.1.tgz
41
+ npm install -g ./leg3ndy-otto-bridge-1.0.2.tgz
42
42
  ```
43
43
 
44
- Na linha `1.0.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.0.2`, `playwright` segue como dependencia obrigatoria no `otto-bridge`. O primeiro `npm install -g @leg3ndy/otto-bridge` pode demorar mais porque instala o browser persistente usado pelo WhatsApp Web e pelos fluxos web em background do bridge.
45
45
 
46
- No macOS, a linha `1.0.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.0.2` usa o provider `macos-helper`, um helper `WKWebView` sem Dock para o WhatsApp Web. O helper sobe com user-agent de Chrome moderno para evitar o bloqueio do WhatsApp ao detectar Safari/WebKit. O runtime antigo com Chromium/Playwright fica disponivel apenas como override explicito via `OTTO_BRIDGE_WHATSAPP_RUNTIME_PROVIDER=embedded-playwright`.
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.1` consolida o hub terminal como fluxo principal e alinha o console ao comportamento normal do Otto antes de fazer handoff local.
48
+ No nivel arquitetural, o `0.9.0` marcou a mudanca de papel do bridge: ele publica tools e resultados estruturados para o Otto, em vez de injetar resposta pronta como caminho principal do chat. O `1.0.0` oficializou isso como runtime agentico; o `1.0.2` consolida o hub terminal como fluxo principal, esconde aliases legados da superfície pública e alinha o console ao comportamento normal do Otto antes de fazer handoff local.
49
49
 
50
50
  ## Publicacao
51
51
 
@@ -80,7 +80,6 @@ otto-bridge --help
80
80
 
81
81
  ```bash
82
82
  otto-bridge
83
- otto-bridge home
84
83
  ```
85
84
 
86
85
  Em TTY, o comando sem argumentos agora abre o hub interativo com banner, setup, status, extensoes e o `Otto Console`. Se ja existir pairing salvo, o próprio `otto-bridge` sobe o runtime local automaticamente e mostra o estado da conexão no hub.
@@ -116,7 +115,7 @@ No macOS, o caminho recomendado agora e o executor nativo do Otto Bridge. Se nen
116
115
  otto-bridge
117
116
  ```
118
117
 
119
- Esse agora e o fluxo principal do produto: o hub abre, conecta o runtime local e deixa o Otto pronto para handoff e approvals sem depender de um `run` separado.
118
+ Esse agora e o fluxo principal do produto: o hub abre, conecta o runtime local e deixa o Otto pronto para handoff e approvals sem depender de nenhum comando separado de runtime.
120
119
 
121
120
  Se precisar do executor nativo no macOS sem reparar, atualize o pairing/config e reabra o hub:
122
121
 
@@ -124,8 +123,6 @@ Se precisar do executor nativo no macOS sem reparar, atualize o pairing/config e
124
123
  otto-bridge setup
125
124
  ```
126
125
 
127
- `otto-bridge run` continua existindo apenas como alias legado/headless para compatibilidade operacional.
128
-
129
126
  O adapter `clawd-cursor` continua disponivel como override opcional no pairing legado:
130
127
 
131
128
  ```bash
@@ -142,7 +139,7 @@ O console usa a mesma sessão local já ligada pelo `otto-bridge`, envia prompts
142
139
 
143
140
  ### WhatsApp Web em background
144
141
 
145
- Fluxo recomendado na linha `1.0.1`:
142
+ Fluxo recomendado na linha `1.0.2`:
146
143
 
147
144
  ```bash
148
145
  otto-bridge extensions --install whatsappweb
@@ -152,13 +149,13 @@ otto-bridge extensions --status whatsappweb
152
149
 
153
150
  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.
154
151
 
155
- Contrato da linha `1.0.1`:
152
+ Contrato da linha `1.0.2`:
156
153
 
157
154
  - `otto-bridge extensions --setup whatsappweb`: autentica a sessao uma vez
158
155
  - `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
159
156
  - ao fechar o `otto-bridge`: o browser em background e desligado, mas a sessao local fica lembrada para o proximo boot
160
157
 
161
- ## Handoff rapido da linha 1.0.1
158
+ ## Handoff rapido da linha 1.0.2
162
159
 
163
160
  Ja fechado no codigo:
164
161
 
@@ -1,6 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { createInterface } from "node:readline/promises";
3
- import { stdin as input, stdout as output } from "node:process";
3
+ import process, { stdin as input, stdout as output } from "node:process";
4
4
  import { defaultDeviceName, getBridgeConfigPath, loadBridgeConfig, resolveApiBaseUrl, resolveExecutorConfig, } from "./config.js";
5
5
  import { streamDeviceCliChat, } from "./chat_cli_client.js";
6
6
  import { formatManagedBridgeExtensionStatus, isManagedBridgeExtensionSlug, loadManagedBridgeExtensionState, } from "./extensions.js";
@@ -28,6 +28,7 @@ const OTTOAI_BANNER = [
28
28
  "╚██████╔╝ ██║ ██║ ╚██████╔╝ ██║ ██║██║",
29
29
  " ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝",
30
30
  ];
31
+ const CLI_EXIT_SENTINEL = "__OTTO_BRIDGE_CLI_EXIT__";
31
32
  class CliRuntimeSession {
32
33
  config;
33
34
  runtime = null;
@@ -158,7 +159,7 @@ function renderBanner() {
158
159
  const enabled = supportsAnsi();
159
160
  const lines = OTTOAI_BANNER.map((line) => style(line, ANSI.coral, enabled));
160
161
  const title = `${BRIDGE_PACKAGE_NAME} v${BRIDGE_VERSION}`;
161
- const subtitle = "Terminal bridge, pairing wizard and local Otto console";
162
+ const subtitle = "Paired local runtime and terminal console";
162
163
  return [
163
164
  lines.join("\n"),
164
165
  "",
@@ -166,6 +167,19 @@ function renderBanner() {
166
167
  `${style(subtitle, ANSI.dim, enabled)}`,
167
168
  ].join("\n");
168
169
  }
170
+ function createCliExitError() {
171
+ return new Error(CLI_EXIT_SENTINEL);
172
+ }
173
+ function isCliExitError(error) {
174
+ return error instanceof Error && error.message === CLI_EXIT_SENTINEL;
175
+ }
176
+ function isReadlineClosedError(error) {
177
+ const detail = error instanceof Error ? error.message : String(error || "");
178
+ const normalized = detail.toLowerCase();
179
+ return normalized.includes("readline was closed")
180
+ || normalized.includes("canceled")
181
+ || normalized.includes("aborted");
182
+ }
169
183
  function printSection(title) {
170
184
  const enabled = supportsAnsi();
171
185
  console.log(`\n${style(title, ANSI.blue, enabled)}`);
@@ -196,16 +210,31 @@ function delay(ms) {
196
210
  return new Promise((resolve) => setTimeout(resolve, ms));
197
211
  }
198
212
  async function createPromptInterface() {
199
- return createInterface({
213
+ const rl = createInterface({
200
214
  input,
201
215
  output,
202
216
  terminal: true,
203
217
  });
218
+ rl.on("SIGINT", () => {
219
+ rl.close();
220
+ });
221
+ return rl;
222
+ }
223
+ async function question(rl, prompt) {
224
+ try {
225
+ return await rl.question(prompt);
226
+ }
227
+ catch (error) {
228
+ if (isReadlineClosedError(error)) {
229
+ throw createCliExitError();
230
+ }
231
+ throw error;
232
+ }
204
233
  }
205
234
  async function ask(rl, label, options) {
206
235
  const defaultValue = normalizeText(options?.defaultValue);
207
236
  const suffix = defaultValue ? ` ${style(`[${defaultValue}]`, ANSI.dim, supportsAnsi())}` : "";
208
- const answer = normalizeText(await rl.question(`${style("›", ANSI.coral, supportsAnsi())} ${label}${suffix}: `));
237
+ const answer = normalizeText(await question(rl, `${style("›", ANSI.coral, supportsAnsi())} ${label}${suffix}: `));
209
238
  if (answer) {
210
239
  return answer;
211
240
  }
@@ -217,15 +246,15 @@ async function ask(rl, label, options) {
217
246
  }
218
247
  return await ask(rl, label, options);
219
248
  }
220
- async function askYesNo(rl, question, defaultValue = true) {
221
- const answer = normalizeText(await rl.question(`${style("?", ANSI.blue, supportsAnsi())} ${question} ${style(defaultValue ? "[Y/n]" : "[y/N]", ANSI.dim, supportsAnsi())}: `)).toLowerCase();
249
+ async function askYesNo(rl, promptText, defaultValue = true) {
250
+ const answer = normalizeText(await question(rl, `${style("?", ANSI.blue, supportsAnsi())} ${promptText} ${style(defaultValue ? "[Y/n]" : "[y/N]", ANSI.dim, supportsAnsi())}: `)).toLowerCase();
222
251
  if (!answer) {
223
252
  return defaultValue;
224
253
  }
225
254
  return ["y", "yes", "s", "sim"].includes(answer);
226
255
  }
227
256
  async function pauseForEnter(rl, message = "Pressione Enter para continuar") {
228
- await rl.question(`${style("↵", ANSI.dim, supportsAnsi())} ${message}`);
257
+ await question(rl, `${style("↵", ANSI.dim, supportsAnsi())} ${message}`);
229
258
  }
230
259
  async function chooseExecutor(rl, current) {
231
260
  const defaultType = current?.type || resolveExecutorConfig().type;
@@ -305,6 +334,72 @@ function renderStatusOverview(config, runtimeSession) {
305
334
  `${style("Config", ANSI.blue, supportsAnsi())}: ${getBridgeConfigPath()}`,
306
335
  ];
307
336
  }
337
+ function renderRuntimeHeadline(runtimeSession) {
338
+ if (!runtimeSession) {
339
+ return "bridge: aguardando pairing inicial";
340
+ }
341
+ const status = runtimeSession.getStatusLabel();
342
+ if (status === "online") {
343
+ return "bridge: pareado e pronto para uso local";
344
+ }
345
+ if (status === "starting") {
346
+ return "bridge: conectando o runtime local";
347
+ }
348
+ if (status === "reconnecting") {
349
+ return "bridge: reconectando com o backend";
350
+ }
351
+ if (status === "error") {
352
+ return "bridge: com problema de conexao, veja /status";
353
+ }
354
+ return "bridge: offline";
355
+ }
356
+ function padRight(text, width) {
357
+ return text.length >= width ? text : `${text}${" ".repeat(width - text.length)}`;
358
+ }
359
+ function renderInfoCard(lines) {
360
+ const normalized = lines.map((line) => truncate(line, 78));
361
+ const width = Math.max(38, ...normalized.map((line) => line.length));
362
+ return [
363
+ `+${"-".repeat(width + 2)}+`,
364
+ ...normalized.map((line) => `| ${padRight(line, width)} |`),
365
+ `+${"-".repeat(width + 2)}+`,
366
+ ].join("\n");
367
+ }
368
+ function printHubScreen(runtimeSession) {
369
+ console.clear();
370
+ console.log(renderBanner());
371
+ console.log("");
372
+ console.log(renderInfoCard([
373
+ "Welcome to OttoAI",
374
+ "/status for bridge details, /help inside the console",
375
+ "model: OttoAI local console",
376
+ `cwd: ${process.cwd()}`,
377
+ renderRuntimeHeadline(runtimeSession),
378
+ ]));
379
+ }
380
+ function printConsoleScreen(runtimeSession) {
381
+ console.clear();
382
+ console.log(renderBanner());
383
+ console.log("");
384
+ console.log(renderInfoCard([
385
+ "Welcome to OttoAI",
386
+ "/help for commands, /status for bridge details",
387
+ "model: OttoAI local console",
388
+ `cwd: ${process.cwd()}`,
389
+ renderRuntimeHeadline(runtimeSession),
390
+ ]));
391
+ console.log("");
392
+ }
393
+ async function askConsoleInput(rl) {
394
+ return normalizeText(await question(rl, `${style(">", ANSI.white, supportsAnsi())} `));
395
+ }
396
+ function printAssistantMessage(message) {
397
+ const text = normalizeText(message);
398
+ if (!text) {
399
+ return;
400
+ }
401
+ console.log(`${style("•", ANSI.coral, supportsAnsi())} ${text}`);
402
+ }
308
403
  async function printExtensionsOverview(config) {
309
404
  printSection("Extensions");
310
405
  if (!config.installedExtensions.length) {
@@ -371,7 +466,7 @@ async function followConsoleJob(rl, config, jobId) {
371
466
  const stepId = extractJobStepId(job);
372
467
  if (status !== lastStatus || stepId !== lastStepId) {
373
468
  const statusLabel = `${status}${stepId ? ` · ${stepId}` : ""}`;
374
- console.log(`${style("runtime", ANSI.teal, supportsAnsi())} ${statusLabel}`);
469
+ printMuted(`local: ${statusLabel}`);
375
470
  lastStatus = status;
376
471
  lastStepId = stepId;
377
472
  }
@@ -401,16 +496,15 @@ async function followConsoleJob(rl, config, jobId) {
401
496
  : status === "failed"
402
497
  ? "Execução local falhou."
403
498
  : "Execução local cancelada.");
404
- console.log(`${style("otto", ANSI.coral, supportsAnsi())} ${summary}`);
499
+ printAssistantMessage(summary);
405
500
  return summary;
406
501
  }
407
502
  await delay(1400);
408
503
  }
409
504
  }
410
505
  async function runOttoConsole(rl, config, runtimeSession, options) {
411
- printSection("Otto Console");
412
- printMuted("Este console usa o mesmo runtime local ligado pelo `otto-bridge`.");
413
- printMuted(`Runtime: ${runtimeSession.getStatusLabel()} · ${runtimeSession.getStatusDetail()}`);
506
+ printConsoleScreen(runtimeSession);
507
+ printMuted("Digite sua mensagem normalmente. O handoff local acontece quando fizer sentido.");
414
508
  const sessionId = randomUUID();
415
509
  const conversation = [];
416
510
  const printConsoleHelp = () => {
@@ -427,6 +521,7 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
427
521
  }
428
522
  if (normalizedPrompt === "/clear") {
429
523
  conversation.splice(0, conversation.length);
524
+ printConsoleScreen(runtimeSession);
430
525
  printMuted("Contexto local do console limpo.");
431
526
  return;
432
527
  }
@@ -439,13 +534,12 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
439
534
  return;
440
535
  }
441
536
  if (normalizedPrompt === "/exit") {
442
- throw new Error("__OTTO_CONSOLE_EXIT__");
537
+ throw createCliExitError();
443
538
  }
444
539
  conversation.push({ role: "user", content: normalizedPrompt });
445
540
  while (conversation.length > 18) {
446
541
  conversation.shift();
447
542
  }
448
- console.log(`${style("você", ANSI.white, supportsAnsi())} ${normalizedPrompt}`);
449
543
  let streamedAssistant = "";
450
544
  let assistantPrefixPrinted = false;
451
545
  let handoffPayload = null;
@@ -475,7 +569,7 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
475
569
  return;
476
570
  }
477
571
  if (!assistantPrefixPrinted) {
478
- output.write(`${style("otto", ANSI.coral, supportsAnsi())} `);
572
+ output.write(`${style("", ANSI.coral, supportsAnsi())} `);
479
573
  assistantPrefixPrinted = true;
480
574
  }
481
575
  output.write(contentChunk);
@@ -489,7 +583,7 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
489
583
  const handoffData = handoffPayload;
490
584
  const bridgeSummary = extractBridgeHandoffSummary(handoffData);
491
585
  if (bridgeSummary) {
492
- console.log(`${style("otto", ANSI.coral, supportsAnsi())} ${bridgeSummary}`);
586
+ printAssistantMessage(bridgeSummary);
493
587
  }
494
588
  const job = handoffData.job && typeof handoffData.job === "object"
495
589
  ? handoffData.job
@@ -514,12 +608,12 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
514
608
  await handlePrompt(options.initialPrompt);
515
609
  }
516
610
  for (;;) {
517
- const promptText = await ask(rl, "OTTO", { allowEmpty: true });
611
+ const promptText = await askConsoleInput(rl);
518
612
  try {
519
613
  await handlePrompt(promptText);
520
614
  }
521
615
  catch (error) {
522
- if (error instanceof Error && error.message === "__OTTO_CONSOLE_EXIT__") {
616
+ if (isCliExitError(error)) {
523
617
  break;
524
618
  }
525
619
  throw error;
@@ -537,8 +631,8 @@ async function pickHomeChoice(rl, paired) {
537
631
  const options = paired
538
632
  ? [
539
633
  `${style("1.", ANSI.coral, supportsAnsi())} Otto Console`,
540
- `${style("2.", ANSI.coral, supportsAnsi())} Re-pair / setup`,
541
- `${style("3.", ANSI.coral, supportsAnsi())} Status do bridge`,
634
+ `${style("2.", ANSI.coral, supportsAnsi())} Setup / parear novamente`,
635
+ `${style("3.", ANSI.coral, supportsAnsi())} Status detalhado`,
542
636
  `${style("4.", ANSI.coral, supportsAnsi())} Extensões instaladas`,
543
637
  `${style("5.", ANSI.coral, supportsAnsi())} Sair`,
544
638
  ]
@@ -565,10 +659,9 @@ export async function launchInteractiveCli(options) {
565
659
  const rl = await createPromptInterface();
566
660
  let runtimeSession = null;
567
661
  try {
568
- console.clear();
569
- console.log(renderBanner());
570
662
  let config = await loadBridgeConfig();
571
663
  if (!config) {
664
+ printHubScreen(null);
572
665
  const setup = await runSetupWizard(rl, options);
573
666
  config = setup.config;
574
667
  if (config && setup.openConsole) {
@@ -583,8 +676,7 @@ export async function launchInteractiveCli(options) {
583
676
  runtimeSession = runtimeSession || new CliRuntimeSession(config);
584
677
  await runtimeSession.ensureStarted();
585
678
  for (;;) {
586
- console.log("");
587
- renderStatusOverview(config, runtimeSession).forEach((line) => console.log(line));
679
+ printHubScreen(runtimeSession);
588
680
  const choice = await pickHomeChoice(rl, true);
589
681
  if (choice === "exit") {
590
682
  break;
@@ -620,6 +712,11 @@ export async function launchInteractiveCli(options) {
620
712
  }
621
713
  }
622
714
  }
715
+ catch (error) {
716
+ if (!isCliExitError(error)) {
717
+ throw error;
718
+ }
719
+ }
623
720
  finally {
624
721
  await runtimeSession?.stop().catch(() => undefined);
625
722
  rl.close();
@@ -629,8 +726,7 @@ export async function runSetupCommand(options) {
629
726
  const rl = await createPromptInterface();
630
727
  let runtimeSession = null;
631
728
  try {
632
- console.clear();
633
- console.log(renderBanner());
729
+ printHubScreen(null);
634
730
  const setup = await runSetupWizard(rl, options);
635
731
  if (setup.config && setup.openConsole) {
636
732
  runtimeSession = new CliRuntimeSession(setup.config);
@@ -638,6 +734,11 @@ export async function runSetupCommand(options) {
638
734
  await runOttoConsole(rl, setup.config, runtimeSession);
639
735
  }
640
736
  }
737
+ catch (error) {
738
+ if (!isCliExitError(error)) {
739
+ throw error;
740
+ }
741
+ }
641
742
  finally {
642
743
  await runtimeSession?.stop().catch(() => undefined);
643
744
  rl.close();
@@ -647,8 +748,7 @@ export async function runConsoleCommand(initialPrompt) {
647
748
  const rl = await createPromptInterface();
648
749
  let runtimeSession = null;
649
750
  try {
650
- console.clear();
651
- console.log(renderBanner());
751
+ printHubScreen(null);
652
752
  let config = await loadBridgeConfig();
653
753
  if (!config) {
654
754
  const setup = await runSetupWizard(rl);
@@ -661,6 +761,11 @@ export async function runConsoleCommand(initialPrompt) {
661
761
  await runtimeSession.ensureStarted();
662
762
  await runOttoConsole(rl, config, runtimeSession, { initialPrompt });
663
763
  }
764
+ catch (error) {
765
+ if (!isCliExitError(error)) {
766
+ throw error;
767
+ }
768
+ }
664
769
  finally {
665
770
  await runtimeSession?.stop().catch(() => undefined);
666
771
  rl.close();
@@ -4563,7 +4563,7 @@ return {
4563
4563
  qrStabilityWindowMs: WHATSAPP_EXPECTED_CONNECTED_QR_STABILITY_WINDOW_MS,
4564
4564
  });
4565
4565
  if (state.connected) {
4566
- await this.syncWhatsAppExtensionState("connected", "Sessao local do WhatsApp Web mantida em background enquanto `otto-bridge run` estiver ativo.", { runtimeAttached: true });
4566
+ await this.syncWhatsAppExtensionState("connected", "Sessao local do WhatsApp Web mantida em background enquanto `otto-bridge` estiver ativo.", { runtimeAttached: true });
4567
4567
  return;
4568
4568
  }
4569
4569
  await this.syncWhatsAppExtensionState("session_expired", state.qrVisible
package/dist/main.js CHANGED
@@ -4,7 +4,6 @@ import process from "node:process";
4
4
  import { clearBridgeConfig, getBridgeConfigPath, loadBridgeConfig, normalizeInstalledExtensions, resolveApiBaseUrl, resolveExecutorConfig, saveBridgeConfig, } from "./config.js";
5
5
  import { buildInstalledManagedExtensionState, formatManagedBridgeExtensionStatus, getManagedBridgeExtensionDefinition, isManagedBridgeExtensionSlug, loadManagedBridgeExtensionState, removeManagedBridgeExtensionState, saveManagedBridgeExtensionState, } from "./extensions.js";
6
6
  import { pairDevice } from "./pairing.js";
7
- import { BridgeRuntime } from "./runtime.js";
8
7
  import { detectWhatsAppBackgroundStatus, runWhatsAppBackgroundSetup, } from "./whatsapp_background.js";
9
8
  import { BRIDGE_PACKAGE_NAME, BRIDGE_VERSION, DEFAULT_PAIR_TIMEOUT_SECONDS, DEFAULT_POLL_INTERVAL_MS, } from "./types.js";
10
9
  import { launchInteractiveCli, runConsoleCommand, runSetupCommand, } from "./cli_terminal.js";
@@ -13,8 +12,8 @@ const UPDATE_RETRY_DELAYS_MS = [0, 8_000, 20_000];
13
12
  function parseArgs(argv) {
14
13
  const [maybeCommand, ...rest] = argv;
15
14
  if (!maybeCommand) {
16
- const interactiveDefault = process.stdout.isTTY && process.stdin.isTTY && process.env.OTTO_BRIDGE_LEGACY_DEFAULT_RUN !== "1";
17
- return { command: interactiveDefault ? "home" : "run", options: new Map() };
15
+ const interactiveDefault = process.stdout.isTTY && process.stdin.isTTY;
16
+ return { command: interactiveDefault ? "home" : "help", options: new Map() };
18
17
  }
19
18
  if (maybeCommand === "--help" || maybeCommand === "-h") {
20
19
  return { command: "help", options: new Map() };
@@ -106,16 +105,14 @@ function resolveExecutorOverrides(args, current) {
106
105
  function printUsage() {
107
106
  console.log(`Usage:
108
107
  otto-bridge
109
- otto-bridge home
110
108
  otto-bridge setup
111
109
  otto-bridge console
112
- otto-bridge pair --api http://localhost:8000 --code ABC123 [--name "Meu PC"] [--executor native-macos|mock|clawd-cursor]
113
110
  otto-bridge status
114
111
  otto-bridge extensions --list
115
- otto-bridge extensions --install github
116
- otto-bridge extensions --setup whatsappweb
117
- otto-bridge extensions --status whatsappweb
118
- otto-bridge extensions --uninstall github
112
+ otto-bridge extensions --install <name>
113
+ otto-bridge extensions --setup <name>
114
+ otto-bridge extensions --status <name>
115
+ otto-bridge extensions --uninstall <name>
119
116
  otto-bridge version
120
117
  otto-bridge update [--tag latest|next] [--dry-run]
121
118
  otto-bridge unpair
@@ -124,13 +121,11 @@ Examples:
124
121
  otto-bridge
125
122
  otto-bridge setup
126
123
  otto-bridge console
127
- otto-bridge pair --api https://api.leg3ndy.com.br --code ABC123
128
- otto-bridge extensions --install whatsappweb
129
- otto-bridge extensions --setup whatsappweb
130
- otto-bridge extensions --status whatsappweb
124
+ otto-bridge extensions --install <name>
125
+ otto-bridge extensions --setup <name>
126
+ otto-bridge extensions --status <name>
131
127
  otto-bridge extensions --list
132
128
  otto-bridge version
133
- otto-bridge update
134
129
  otto-bridge update --dry-run
135
130
  otto-bridge --version`);
136
131
  }
@@ -249,7 +244,7 @@ async function detectManagedExtensionStatus(slug, currentState) {
249
244
  if (hasFreshRuntimeAttachment(currentState)) {
250
245
  return {
251
246
  status: "connected",
252
- notes: currentState?.notes || "Sessao local do WhatsApp Web mantida em background enquanto `otto-bridge run` estiver ativo.",
247
+ notes: currentState?.notes || "Sessao local do WhatsApp Web mantida em background enquanto `otto-bridge` estiver ativo.",
253
248
  };
254
249
  }
255
250
  const detected = await detectManagedWhatsAppWebStatus();
@@ -290,44 +285,10 @@ async function runPairCommand(args) {
290
285
  async function loadRequiredBridgeConfig() {
291
286
  const config = await loadBridgeConfig();
292
287
  if (!config) {
293
- throw new Error("No local pairing found. Run `otto-bridge pair --code <CODE>` first.");
288
+ throw new Error("Nenhum pairing local encontrado. Rode `otto-bridge` ou `otto-bridge setup` primeiro.");
294
289
  }
295
290
  return config;
296
291
  }
297
- async function runRuntimeCommand(args) {
298
- console.log("[otto-bridge] `run` agora é um alias legado. Prefira `otto-bridge`.");
299
- const config = await loadRequiredBridgeConfig();
300
- const runtimeConfig = {
301
- ...config,
302
- executor: resolveExecutorOverrides(args, config.executor),
303
- };
304
- const runtime = new BridgeRuntime(runtimeConfig);
305
- let stopping = false;
306
- const shutdown = async (signal) => {
307
- if (stopping) {
308
- return;
309
- }
310
- stopping = true;
311
- console.log(`[otto-bridge] shutting down runtime after ${signal}`);
312
- await runtime.stop().catch(() => undefined);
313
- };
314
- const handleSigint = () => {
315
- void shutdown("SIGINT");
316
- };
317
- const handleSigterm = () => {
318
- void shutdown("SIGTERM");
319
- };
320
- process.once("SIGINT", handleSigint);
321
- process.once("SIGTERM", handleSigterm);
322
- try {
323
- await runtime.start();
324
- }
325
- finally {
326
- process.off("SIGINT", handleSigint);
327
- process.off("SIGTERM", handleSigterm);
328
- await runtime.stop().catch(() => undefined);
329
- }
330
- }
331
292
  async function runStatusCommand() {
332
293
  const config = await loadBridgeConfig();
333
294
  if (!config) {
@@ -395,7 +356,7 @@ async function runExtensionsCommand(args) {
395
356
  }
396
357
  console.log(`[otto-bridge] proximo passo para ${extension}: otto-bridge extensions --setup ${extension}`);
397
358
  }
398
- console.log("[otto-bridge] rode `otto-bridge run` novamente se quiser sincronizar agora com a web");
359
+ console.log("[otto-bridge] reabra `otto-bridge` se quiser sincronizar agora com a web");
399
360
  return;
400
361
  }
401
362
  if (setupValue) {
@@ -488,7 +449,7 @@ async function runExtensionsCommand(args) {
488
449
  }
489
450
  }
490
451
  console.log(`[otto-bridge] extensoes removidas: ${removed.join(", ")}`);
491
- console.log("[otto-bridge] rode `otto-bridge run` novamente se quiser sincronizar agora com a web");
452
+ console.log("[otto-bridge] reabra `otto-bridge` se quiser sincronizar agora com a web");
492
453
  return;
493
454
  }
494
455
  if (!config.installedExtensions.length) {
@@ -600,7 +561,7 @@ async function main() {
600
561
  await runPairCommand(args);
601
562
  return;
602
563
  case "run":
603
- await runRuntimeCommand(args);
564
+ await launchInteractiveCli();
604
565
  return;
605
566
  case "status":
606
567
  await runStatusCommand();
package/dist/runtime.js CHANGED
@@ -84,6 +84,8 @@ export class BridgeRuntime {
84
84
  localAutomationRuntime;
85
85
  lastBridgeReleaseNoticeKey = null;
86
86
  activeSocket = null;
87
+ reconnectTimer = null;
88
+ reconnectWaitResolver = null;
87
89
  stopped = false;
88
90
  started = false;
89
91
  pendingConfirmations = new Map();
@@ -206,12 +208,21 @@ export class BridgeRuntime {
206
208
  }
207
209
  this.logInfo(`[otto-bridge] reconnecting in ${this.reconnectDelayMs}ms`);
208
210
  this.emit({ type: "reconnecting", delayMs: this.reconnectDelayMs });
209
- await delay(this.reconnectDelayMs);
211
+ await this.waitForReconnect(this.reconnectDelayMs);
210
212
  this.reconnectDelayMs = Math.min(this.reconnectDelayMs * 2, DEFAULT_RECONNECT_MAX_DELAY_MS);
211
213
  }
212
214
  }
213
215
  async stop() {
214
216
  this.stopped = true;
217
+ if (this.reconnectTimer) {
218
+ clearTimeout(this.reconnectTimer);
219
+ this.reconnectTimer = null;
220
+ }
221
+ if (this.reconnectWaitResolver) {
222
+ const resolve = this.reconnectWaitResolver;
223
+ this.reconnectWaitResolver = null;
224
+ resolve();
225
+ }
215
226
  for (const [jobId, cancel] of this.activeCancels.entries()) {
216
227
  try {
217
228
  await cancel();
@@ -237,6 +248,27 @@ export class BridgeRuntime {
237
248
  await this.executor.close();
238
249
  }
239
250
  }
251
+ async waitForReconnect(ms) {
252
+ if (ms <= 0 || this.stopped) {
253
+ return;
254
+ }
255
+ await new Promise((resolve) => {
256
+ this.reconnectWaitResolver = () => {
257
+ if (this.reconnectTimer) {
258
+ clearTimeout(this.reconnectTimer);
259
+ this.reconnectTimer = null;
260
+ }
261
+ this.reconnectWaitResolver = null;
262
+ resolve();
263
+ };
264
+ this.reconnectTimer = setTimeout(() => {
265
+ const finish = this.reconnectWaitResolver;
266
+ this.reconnectTimer = null;
267
+ this.reconnectWaitResolver = null;
268
+ finish?.();
269
+ }, ms);
270
+ });
271
+ }
240
272
  async connectOnce() {
241
273
  const socket = new WebSocket(this.config.wsUrl, ["device", this.config.deviceToken]);
242
274
  this.activeSocket = socket;
package/dist/types.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export const BRIDGE_CONFIG_VERSION = 1;
2
- export const BRIDGE_VERSION = "1.0.1";
2
+ export const BRIDGE_VERSION = "1.0.2";
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.1",
3
+ "version": "1.0.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Local companion for Otto Bridge device pairing and WebSocket runtime.",
@@ -24,7 +24,7 @@ if (!existsSync(mainPath)) {
24
24
  process.exit(0);
25
25
  }
26
26
 
27
- console.log("\n[otto-bridge] Welcome to OTTOAI 1.0.1");
27
+ console.log("\n[otto-bridge] Welcome to OTTOAI 1.0.2");
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"], {