@leg3ndy/otto-bridge 1.0.7 → 1.0.9
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 +8 -8
- package/dist/cli_terminal.js +333 -56
- package/dist/main.js +32 -2
- package/dist/types.js +1 -1
- package/package.json +1 -1
- package/scripts/postinstall.mjs +1 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ Para o estado atual da arquitetura, capacidades entregues, limitacoes e roadmap
|
|
|
15
15
|
|
|
16
16
|
Para o corte de arquitetura do `0.9.0`, veja [`leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_0_9_0_RELEASE.md`](../leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_0_9_0_RELEASE.md).
|
|
17
17
|
|
|
18
|
-
Para a release atual `1.0.
|
|
18
|
+
Para a release atual `1.0.9`, com composer multiline no console TTY e quebra de linha dentro da moldura, veja [`leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_0_9_PATCH.md`](../leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_0_9_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.
|
|
41
|
+
npm install -g ./leg3ndy-otto-bridge-1.0.9.tgz
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
Na linha `1.0.
|
|
44
|
+
Na linha `1.0.9`, `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.
|
|
46
|
+
No macOS, a linha `1.0.9` 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.
|
|
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.9` preserva esse fluxo, mantem o input-box TTY estavel, adiciona composer multiline e melhora as views de status/extensoes e o refresh ao sair do `Terminal`.
|
|
49
49
|
|
|
50
50
|
## Publicacao
|
|
51
51
|
|
|
@@ -157,7 +157,7 @@ Esse comando abre um shell local interativo para instalar extensoes, rodar coman
|
|
|
157
157
|
|
|
158
158
|
### WhatsApp Web em background
|
|
159
159
|
|
|
160
|
-
Fluxo recomendado na linha `1.0.
|
|
160
|
+
Fluxo recomendado na linha `1.0.9`:
|
|
161
161
|
|
|
162
162
|
```bash
|
|
163
163
|
otto-bridge extensions --install whatsappweb
|
|
@@ -167,13 +167,13 @@ otto-bridge extensions --status whatsappweb
|
|
|
167
167
|
|
|
168
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.
|
|
169
169
|
|
|
170
|
-
Contrato da linha `1.0.
|
|
170
|
+
Contrato da linha `1.0.9`:
|
|
171
171
|
|
|
172
172
|
- `otto-bridge extensions --setup whatsappweb`: autentica a sessao uma vez
|
|
173
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
|
|
174
174
|
- ao fechar o `otto-bridge`: o browser em background e desligado, mas a sessao local fica lembrada para o proximo boot
|
|
175
175
|
|
|
176
|
-
## Handoff rapido da linha 1.0.
|
|
176
|
+
## Handoff rapido da linha 1.0.9
|
|
177
177
|
|
|
178
178
|
Ja fechado no codigo:
|
|
179
179
|
|
package/dist/cli_terminal.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
3
4
|
import { createInterface } from "node:readline/promises";
|
|
4
5
|
import { cursorTo, moveCursor, } from "node:readline";
|
|
5
6
|
import process, { stdin as input, stdout as output } from "node:process";
|
|
6
|
-
import { defaultDeviceName, getBridgeConfigPath, loadBridgeConfig, resolveApiBaseUrl, resolveExecutorConfig, } from "./config.js";
|
|
7
|
+
import { defaultDeviceName, getBridgeConfigPath, loadBridgeConfig, normalizeInstalledExtensions, resolveApiBaseUrl, resolveExecutorConfig, } from "./config.js";
|
|
7
8
|
import { streamDeviceCliChat, } from "./chat_cli_client.js";
|
|
8
9
|
import { formatManagedBridgeExtensionStatus, isManagedBridgeExtensionSlug, loadManagedBridgeExtensionState, } from "./extensions.js";
|
|
9
10
|
import { pairDevice } from "./pairing.js";
|
|
@@ -308,6 +309,47 @@ export function resolveCliModelMode(value) {
|
|
|
308
309
|
function delay(ms) {
|
|
309
310
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
310
311
|
}
|
|
312
|
+
async function resolveInstalledBridgeVersion() {
|
|
313
|
+
try {
|
|
314
|
+
const raw = await readFile(new URL("../package.json", import.meta.url), "utf8");
|
|
315
|
+
const parsed = JSON.parse(raw);
|
|
316
|
+
return normalizeText(parsed.version) || BRIDGE_VERSION;
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
return BRIDGE_VERSION;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
async function captureBridgeRuntimeSnapshot(config) {
|
|
323
|
+
const managedExtensionState = await Promise.all(normalizeInstalledExtensions(config?.installedExtensions || [])
|
|
324
|
+
.filter(isManagedBridgeExtensionSlug)
|
|
325
|
+
.sort((left, right) => left.localeCompare(right))
|
|
326
|
+
.map(async (slug) => {
|
|
327
|
+
const state = await loadManagedBridgeExtensionState(slug);
|
|
328
|
+
return {
|
|
329
|
+
slug,
|
|
330
|
+
status: normalizeText(state?.status),
|
|
331
|
+
lastSetupAt: normalizeText(state?.lastSetupAt),
|
|
332
|
+
notes: normalizeText(state?.notes),
|
|
333
|
+
};
|
|
334
|
+
}));
|
|
335
|
+
return {
|
|
336
|
+
bridgeVersion: await resolveInstalledBridgeVersion(),
|
|
337
|
+
configFingerprint: JSON.stringify(config || null),
|
|
338
|
+
extensionStateFingerprint: JSON.stringify(managedExtensionState),
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
export function diffBridgeRuntimeSnapshots(before, after) {
|
|
342
|
+
const versionChanged = before.bridgeVersion !== after.bridgeVersion;
|
|
343
|
+
const configChanged = before.configFingerprint !== after.configFingerprint;
|
|
344
|
+
const extensionStateChanged = before.extensionStateFingerprint !== after.extensionStateFingerprint;
|
|
345
|
+
return {
|
|
346
|
+
versionChanged,
|
|
347
|
+
configChanged,
|
|
348
|
+
extensionStateChanged,
|
|
349
|
+
requiresCliRestart: versionChanged,
|
|
350
|
+
requiresRuntimeRefresh: configChanged || extensionStateChanged,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
311
353
|
async function createPromptInterface() {
|
|
312
354
|
const rl = createInterface({
|
|
313
355
|
input,
|
|
@@ -539,6 +581,72 @@ function buildBridgeReleaseCard(notice) {
|
|
|
539
581
|
{ text: `Rode: ${notice.updateCommand || "otto-bridge update"}`, tone: "primary" },
|
|
540
582
|
];
|
|
541
583
|
}
|
|
584
|
+
function buildStatusCard(config, runtimeSession) {
|
|
585
|
+
const releaseNotice = runtimeSession.getReleaseNotice();
|
|
586
|
+
return [
|
|
587
|
+
{ text: "Bridge Status", tone: "title" },
|
|
588
|
+
{ text: "", tone: "muted" },
|
|
589
|
+
{ text: `device: ${config.deviceName}`, tone: "primary" },
|
|
590
|
+
{ text: `device id: ${config.deviceId}`, tone: "primary" },
|
|
591
|
+
{ text: `api: ${config.apiBaseUrl}`, tone: "primary" },
|
|
592
|
+
{ text: `executor: ${config.executor.type}`, tone: "primary" },
|
|
593
|
+
{ text: `approval: ${config.approvalMode}`, tone: "primary" },
|
|
594
|
+
{ text: `runtime: ${runtimeSession.getStatusLabel()}`, tone: "primary" },
|
|
595
|
+
...(runtimeSession.getStatusDetail()
|
|
596
|
+
? [{ text: `runtime note: ${runtimeSession.getStatusDetail()}`, tone: "muted" }]
|
|
597
|
+
: []),
|
|
598
|
+
...(releaseNotice
|
|
599
|
+
? [
|
|
600
|
+
{ text: "", tone: "muted" },
|
|
601
|
+
{
|
|
602
|
+
text: `bridge update: ${releaseNotice.kind === "required" ? "required" : "available"}`,
|
|
603
|
+
tone: releaseNotice.kind === "required" ? "warning" : "primary",
|
|
604
|
+
},
|
|
605
|
+
...(releaseNotice.currentVersion
|
|
606
|
+
? [{ text: `current version: ${releaseNotice.currentVersion}`, tone: "primary" }]
|
|
607
|
+
: []),
|
|
608
|
+
...(releaseNotice.latestVersion
|
|
609
|
+
? [{ text: `latest version: ${releaseNotice.latestVersion}`, tone: "primary" }]
|
|
610
|
+
: []),
|
|
611
|
+
...(releaseNotice.minSupportedVersion
|
|
612
|
+
? [{ text: `min supported: ${releaseNotice.minSupportedVersion}`, tone: "primary" }]
|
|
613
|
+
: []),
|
|
614
|
+
{ text: `update command: ${releaseNotice.updateCommand || "otto-bridge update"}`, tone: "muted" },
|
|
615
|
+
]
|
|
616
|
+
: []),
|
|
617
|
+
{ text: "", tone: "muted" },
|
|
618
|
+
{ text: `config: ${getBridgeConfigPath()}`, tone: "muted" },
|
|
619
|
+
];
|
|
620
|
+
}
|
|
621
|
+
async function buildExtensionsCard(config) {
|
|
622
|
+
const lines = [
|
|
623
|
+
{ text: "Extensões", tone: "title" },
|
|
624
|
+
{ text: "", tone: "muted" },
|
|
625
|
+
];
|
|
626
|
+
if (!config.installedExtensions.length) {
|
|
627
|
+
lines.push({ text: "Nenhuma extensão instalada neste bridge.", tone: "muted" });
|
|
628
|
+
return lines;
|
|
629
|
+
}
|
|
630
|
+
for (const extension of config.installedExtensions) {
|
|
631
|
+
if (!isManagedBridgeExtensionSlug(extension)) {
|
|
632
|
+
lines.push({ text: `• ${extension}`, tone: "primary" });
|
|
633
|
+
lines.push({ text: "Extensão local sem status gerenciado.", tone: "muted" });
|
|
634
|
+
lines.push({ text: "", tone: "muted" });
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
const state = await loadManagedBridgeExtensionState(extension);
|
|
638
|
+
const status = state ? formatManagedBridgeExtensionStatus(state.status) : "sem estado salvo";
|
|
639
|
+
lines.push({ text: `• ${extension}: ${status}`, tone: "primary" });
|
|
640
|
+
if (state?.notes) {
|
|
641
|
+
lines.push({ text: truncate(state.notes, 160), tone: "muted" });
|
|
642
|
+
}
|
|
643
|
+
lines.push({ text: "", tone: "muted" });
|
|
644
|
+
}
|
|
645
|
+
while (lines.length > 2 && !normalizeText(lines[lines.length - 1]?.text)) {
|
|
646
|
+
lines.pop();
|
|
647
|
+
}
|
|
648
|
+
return lines;
|
|
649
|
+
}
|
|
542
650
|
function printHubScreen(runtimeSession, modelMode) {
|
|
543
651
|
clearScreen();
|
|
544
652
|
console.log(renderBanner());
|
|
@@ -561,6 +669,8 @@ function printConsoleScreen(runtimeSession, modelMode) {
|
|
|
561
669
|
console.log(renderInfoCard(buildBridgeReleaseCard(releaseNotice)));
|
|
562
670
|
}
|
|
563
671
|
console.log("");
|
|
672
|
+
printSoft(`Comandos: ${CONSOLE_COMMAND_HINT}`);
|
|
673
|
+
console.log("");
|
|
564
674
|
}
|
|
565
675
|
function renderPromptFrameLine(width, edgeLeft, edgeRight) {
|
|
566
676
|
return style(`${edgeLeft}${"─".repeat(width)}${edgeRight}`, ANSI.brandBlue, supportsAnsi());
|
|
@@ -578,10 +688,130 @@ function renderConsolePromptContentLine(text, width, tone) {
|
|
|
578
688
|
: style(clipped, ANSI.slateItalic, enabled);
|
|
579
689
|
return `${border} ${body} ${border}`;
|
|
580
690
|
}
|
|
691
|
+
function sliceByWidth(text, width) {
|
|
692
|
+
if (width <= 0) {
|
|
693
|
+
return [""];
|
|
694
|
+
}
|
|
695
|
+
if (!text.length) {
|
|
696
|
+
return [""];
|
|
697
|
+
}
|
|
698
|
+
const parts = [];
|
|
699
|
+
let offset = 0;
|
|
700
|
+
while (offset < text.length) {
|
|
701
|
+
parts.push(text.slice(offset, offset + width));
|
|
702
|
+
offset += width;
|
|
703
|
+
}
|
|
704
|
+
return parts.length ? parts : [""];
|
|
705
|
+
}
|
|
706
|
+
export function buildConsoleComposerLayout(value, innerWidth) {
|
|
707
|
+
const promptWidth = 2;
|
|
708
|
+
const lineContentWidth = Math.max(1, innerWidth - promptWidth);
|
|
709
|
+
if (!value) {
|
|
710
|
+
return {
|
|
711
|
+
lines: [CONSOLE_PLACEHOLDER],
|
|
712
|
+
cursorLineIndex: 0,
|
|
713
|
+
cursorColumn: 0,
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
const logicalLines = value.split("\n");
|
|
717
|
+
const lastLogicalLine = logicalLines[logicalLines.length - 1] ?? "";
|
|
718
|
+
const lines = [];
|
|
719
|
+
for (const logicalLine of logicalLines) {
|
|
720
|
+
const wrapped = sliceByWidth(logicalLine, lineContentWidth);
|
|
721
|
+
lines.push(...wrapped);
|
|
722
|
+
}
|
|
723
|
+
const trailingNewline = value.endsWith("\n");
|
|
724
|
+
const needsSoftWrapCursorRow = !trailingNewline
|
|
725
|
+
&& lastLogicalLine.length > 0
|
|
726
|
+
&& lastLogicalLine.length % lineContentWidth === 0;
|
|
727
|
+
if (trailingNewline || needsSoftWrapCursorRow) {
|
|
728
|
+
lines.push("");
|
|
729
|
+
}
|
|
730
|
+
const cursorLineIndex = trailingNewline
|
|
731
|
+
? lines.length - 1
|
|
732
|
+
: needsSoftWrapCursorRow
|
|
733
|
+
? lines.length - 1
|
|
734
|
+
: Math.max(0, lines.length - 1);
|
|
735
|
+
const cursorColumn = trailingNewline
|
|
736
|
+
? 0
|
|
737
|
+
: needsSoftWrapCursorRow
|
|
738
|
+
? 0
|
|
739
|
+
: lastLogicalLine.length % lineContentWidth;
|
|
740
|
+
return {
|
|
741
|
+
lines,
|
|
742
|
+
cursorLineIndex,
|
|
743
|
+
cursorColumn,
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
function renderConsoleComposerLines(value, innerWidth, enabled) {
|
|
747
|
+
const layout = buildConsoleComposerLayout(value, innerWidth);
|
|
748
|
+
const lineContentWidth = Math.max(1, innerWidth - 2);
|
|
749
|
+
const promptStyled = style(">", `${ANSI.bold}${ANSI.white}`, enabled);
|
|
750
|
+
if (!value) {
|
|
751
|
+
const placeholder = truncate(CONSOLE_PLACEHOLDER, lineContentWidth);
|
|
752
|
+
return {
|
|
753
|
+
renderedLines: [
|
|
754
|
+
`${promptStyled} ${style(placeholder, ANSI.slate, enabled)}${" ".repeat(Math.max(0, lineContentWidth - placeholder.length))}`,
|
|
755
|
+
],
|
|
756
|
+
cursorLineIndex: 0,
|
|
757
|
+
cursorColumn: 0,
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
return {
|
|
761
|
+
renderedLines: layout.lines.map((line, index) => {
|
|
762
|
+
const prefix = index === 0 ? `${promptStyled} ` : " ";
|
|
763
|
+
return `${prefix}${line}${" ".repeat(Math.max(0, lineContentWidth - line.length))}`;
|
|
764
|
+
}),
|
|
765
|
+
cursorLineIndex: layout.cursorLineIndex,
|
|
766
|
+
cursorColumn: layout.cursorColumn,
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
function tryConsumeControlSequence(buffer) {
|
|
770
|
+
const knownNewlineSequences = [
|
|
771
|
+
"\u001b[13;2u",
|
|
772
|
+
"\u001b[27;2;13~",
|
|
773
|
+
"\u001b[1;2M",
|
|
774
|
+
"\u001b\r",
|
|
775
|
+
"\u001b\n",
|
|
776
|
+
];
|
|
777
|
+
for (const sequence of knownNewlineSequences) {
|
|
778
|
+
if (buffer.startsWith(sequence)) {
|
|
779
|
+
return {
|
|
780
|
+
consumed: sequence.length,
|
|
781
|
+
action: "newline",
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
if (buffer === "\u001b" || /^\u001b\[[0-9;?]*$/.test(buffer)) {
|
|
786
|
+
return {
|
|
787
|
+
consumed: 0,
|
|
788
|
+
action: "incomplete",
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
const csiMatch = buffer.match(/^\u001b\[[0-9;?]*[ -/]*[@-~]/);
|
|
792
|
+
if (csiMatch) {
|
|
793
|
+
return {
|
|
794
|
+
consumed: csiMatch[0].length,
|
|
795
|
+
action: "ignore",
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
const ss3Match = buffer.match(/^\u001bO./);
|
|
799
|
+
if (ss3Match) {
|
|
800
|
+
return {
|
|
801
|
+
consumed: ss3Match[0].length,
|
|
802
|
+
action: "ignore",
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
if (buffer.startsWith("\u001b")) {
|
|
806
|
+
return {
|
|
807
|
+
consumed: 1,
|
|
808
|
+
action: "ignore",
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
return null;
|
|
812
|
+
}
|
|
581
813
|
async function askConsoleInput(rl) {
|
|
582
814
|
if (!supportsAnsi() || typeof input.setRawMode !== "function" || !input.isTTY) {
|
|
583
|
-
printSoft(`Comandos: ${CONSOLE_COMMAND_HINT}`);
|
|
584
|
-
console.log("");
|
|
585
815
|
return normalizeText(await question(rl, "> "));
|
|
586
816
|
}
|
|
587
817
|
rl.pause();
|
|
@@ -591,71 +821,90 @@ async function askConsoleInput(rl) {
|
|
|
591
821
|
const enabled = supportsAnsi();
|
|
592
822
|
const availableWidth = Number(output.columns || 96);
|
|
593
823
|
const innerWidth = Math.max(42, Math.min(availableWidth - 8, 116));
|
|
594
|
-
const sectionTopOffsetFromInputLine = 3;
|
|
595
824
|
let renderedOnce = false;
|
|
596
825
|
let value = "";
|
|
826
|
+
let pendingControlBuffer = "";
|
|
827
|
+
let previousCursorRowsFromTop = 0;
|
|
597
828
|
const cleanup = () => {
|
|
598
829
|
input.removeListener("data", onData);
|
|
599
830
|
input.setRawMode(false);
|
|
600
831
|
input.pause();
|
|
601
832
|
rl.resume();
|
|
602
833
|
};
|
|
603
|
-
const renderInputContent = () => {
|
|
604
|
-
const promptPlain = "> ";
|
|
605
|
-
const promptStyled = style(">", `${ANSI.bold}${ANSI.white}`, enabled);
|
|
606
|
-
const maxValueLength = Math.max(0, innerWidth - promptPlain.length);
|
|
607
|
-
if (!value) {
|
|
608
|
-
const placeholder = truncate(CONSOLE_PLACEHOLDER, maxValueLength);
|
|
609
|
-
const padded = `${style(placeholder, ANSI.slate, enabled)}${" ".repeat(Math.max(0, maxValueLength - placeholder.length))}`;
|
|
610
|
-
return `${promptStyled} ${padded}`;
|
|
611
|
-
}
|
|
612
|
-
const visibleValue = value.length > maxValueLength
|
|
613
|
-
? value.slice(value.length - maxValueLength)
|
|
614
|
-
: value;
|
|
615
|
-
return `${promptStyled} ${visibleValue}${" ".repeat(Math.max(0, maxValueLength - visibleValue.length))}`;
|
|
616
|
-
};
|
|
617
834
|
const render = () => {
|
|
835
|
+
const composer = renderConsoleComposerLines(value, innerWidth, enabled);
|
|
618
836
|
if (renderedOnce) {
|
|
619
837
|
cursorTo(output, 0);
|
|
620
|
-
moveCursor(output, 0, -
|
|
838
|
+
moveCursor(output, 0, -previousCursorRowsFromTop);
|
|
621
839
|
}
|
|
622
840
|
else {
|
|
623
841
|
renderedOnce = true;
|
|
624
842
|
}
|
|
625
|
-
const commands = style(`Comandos: ${CONSOLE_COMMAND_HINT}`, ANSI.slateItalic, enabled);
|
|
626
843
|
const top = renderPromptFrameLine(innerWidth + 2, "┌", "┐");
|
|
627
844
|
const border = style("│", ANSI.brandBlue, enabled);
|
|
628
|
-
const middle = `${border} ${renderInputContent()} ${border}`;
|
|
629
845
|
const bottom = renderPromptFrameLine(innerWidth + 2, "└", "┘");
|
|
846
|
+
const content = composer.renderedLines.map((line) => `${border} ${line} ${border}`).join("\n");
|
|
630
847
|
output.write("\u001b[J");
|
|
631
|
-
output.write(`${
|
|
848
|
+
output.write(`${top}\n${content}\n${bottom}\n`);
|
|
632
849
|
cursorTo(output, 0);
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
cursorTo(output, 4 +
|
|
850
|
+
previousCursorRowsFromTop = 1 + composer.cursorLineIndex;
|
|
851
|
+
moveCursor(output, 0, -previousCursorRowsFromTop);
|
|
852
|
+
cursorTo(output, 4 + composer.cursorColumn);
|
|
853
|
+
};
|
|
854
|
+
const insertNewline = () => {
|
|
855
|
+
value = `${value}\n`;
|
|
856
|
+
render();
|
|
857
|
+
};
|
|
858
|
+
const submitPrompt = () => {
|
|
859
|
+
cleanup();
|
|
860
|
+
output.write("\n");
|
|
861
|
+
resolve(normalizeText(value));
|
|
636
862
|
};
|
|
637
863
|
const onData = (chunk) => {
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
if (
|
|
864
|
+
pendingControlBuffer += Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
|
|
865
|
+
while (pendingControlBuffer.length > 0) {
|
|
866
|
+
if (pendingControlBuffer.startsWith("\u0003")) {
|
|
641
867
|
cleanup();
|
|
642
868
|
reject(createCliExitError());
|
|
643
869
|
return;
|
|
644
870
|
}
|
|
645
|
-
if (
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
871
|
+
if (pendingControlBuffer.startsWith("\r")) {
|
|
872
|
+
if (pendingControlBuffer.startsWith("\r\n")) {
|
|
873
|
+
pendingControlBuffer = pendingControlBuffer.slice(2);
|
|
874
|
+
}
|
|
875
|
+
else {
|
|
876
|
+
pendingControlBuffer = pendingControlBuffer.slice(1);
|
|
877
|
+
}
|
|
878
|
+
submitPrompt();
|
|
649
879
|
return;
|
|
650
880
|
}
|
|
651
|
-
if (
|
|
881
|
+
if (pendingControlBuffer.startsWith("\n")) {
|
|
882
|
+
pendingControlBuffer = pendingControlBuffer.slice(1);
|
|
883
|
+
insertNewline();
|
|
884
|
+
continue;
|
|
885
|
+
}
|
|
886
|
+
if (pendingControlBuffer.startsWith("\u007f") || pendingControlBuffer.startsWith("\b")) {
|
|
887
|
+
pendingControlBuffer = pendingControlBuffer.slice(1);
|
|
652
888
|
value = value.slice(0, -1);
|
|
653
889
|
render();
|
|
654
890
|
continue;
|
|
655
891
|
}
|
|
656
|
-
|
|
892
|
+
const controlSequence = tryConsumeControlSequence(pendingControlBuffer);
|
|
893
|
+
if (controlSequence) {
|
|
894
|
+
if (controlSequence.action === "incomplete") {
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
pendingControlBuffer = pendingControlBuffer.slice(controlSequence.consumed);
|
|
898
|
+
if (controlSequence.action === "newline") {
|
|
899
|
+
insertNewline();
|
|
900
|
+
}
|
|
657
901
|
continue;
|
|
658
902
|
}
|
|
903
|
+
const [char] = Array.from(pendingControlBuffer);
|
|
904
|
+
if (!char) {
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
pendingControlBuffer = pendingControlBuffer.slice(char.length);
|
|
659
908
|
value += char;
|
|
660
909
|
render();
|
|
661
910
|
}
|
|
@@ -838,6 +1087,30 @@ export async function runTerminalShell(options) {
|
|
|
838
1087
|
});
|
|
839
1088
|
});
|
|
840
1089
|
}
|
|
1090
|
+
async function showRuntimeRefreshProgress(options) {
|
|
1091
|
+
clearScreen();
|
|
1092
|
+
console.log(renderBanner());
|
|
1093
|
+
console.log("");
|
|
1094
|
+
console.log(renderInfoCard([
|
|
1095
|
+
{ text: options.title, tone: "title" },
|
|
1096
|
+
{ text: "", tone: "muted" },
|
|
1097
|
+
{ text: options.detail, tone: "muted" },
|
|
1098
|
+
]));
|
|
1099
|
+
console.log("");
|
|
1100
|
+
const enabled = supportsAnsi();
|
|
1101
|
+
const width = 28;
|
|
1102
|
+
const steps = Math.max(8, options.steps || width);
|
|
1103
|
+
const delayMs = Math.max(20, options.delayMs || 45);
|
|
1104
|
+
for (let step = 0; step <= steps; step += 1) {
|
|
1105
|
+
const filled = Math.round((step / steps) * width);
|
|
1106
|
+
const empty = Math.max(0, width - filled);
|
|
1107
|
+
const bar = `${"█".repeat(filled)}${"░".repeat(empty)}`;
|
|
1108
|
+
const line = `[${bar}] ${Math.round((step / steps) * 100)}%`;
|
|
1109
|
+
output.write(`\r${style(line, ANSI.brandBlue, enabled)}`);
|
|
1110
|
+
await delay(delayMs);
|
|
1111
|
+
}
|
|
1112
|
+
output.write("\n\n");
|
|
1113
|
+
}
|
|
841
1114
|
function buildConversationSummary(summary, job) {
|
|
842
1115
|
const rendered = renderStructuredOutcome(job, { compact: true });
|
|
843
1116
|
if (!rendered) {
|
|
@@ -849,23 +1122,8 @@ function buildConversationSummary(summary, job) {
|
|
|
849
1122
|
return `${summary}\n${rendered}`.slice(0, 4_000).trim();
|
|
850
1123
|
}
|
|
851
1124
|
async function printExtensionsOverview(config) {
|
|
852
|
-
printSection("
|
|
853
|
-
|
|
854
|
-
printMuted("Nenhuma extensão instalada neste bridge.");
|
|
855
|
-
return;
|
|
856
|
-
}
|
|
857
|
-
for (const extension of config.installedExtensions) {
|
|
858
|
-
if (!isManagedBridgeExtensionSlug(extension)) {
|
|
859
|
-
console.log(`- ${extension}`);
|
|
860
|
-
continue;
|
|
861
|
-
}
|
|
862
|
-
const state = await loadManagedBridgeExtensionState(extension);
|
|
863
|
-
const status = state ? formatManagedBridgeExtensionStatus(state.status) : "sem estado salvo";
|
|
864
|
-
console.log(`- ${extension}: ${status}`);
|
|
865
|
-
if (state?.notes) {
|
|
866
|
-
printMuted(` ${truncate(state.notes, 140)}`);
|
|
867
|
-
}
|
|
868
|
-
}
|
|
1125
|
+
printSection("Extensões");
|
|
1126
|
+
console.log(renderInfoCard(await buildExtensionsCard(config)));
|
|
869
1127
|
}
|
|
870
1128
|
async function runSetupWizard(rl, options) {
|
|
871
1129
|
printSection(options?.postinstall ? "Setup Inicial" : "Pairing Setup");
|
|
@@ -960,6 +1218,7 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
960
1218
|
printSection("Console");
|
|
961
1219
|
printSoft("Comandos: /help, /model [fast|thinking], /status, /clear, /exit");
|
|
962
1220
|
printSoft("Bridge: otto-bridge terminal, otto-bridge extensions --install <name>, otto-bridge update");
|
|
1221
|
+
printSoft("Composer: Enter envia; Shift+Enter quebra linha quando suportado; Ctrl+J tambem insere newline.");
|
|
963
1222
|
};
|
|
964
1223
|
const handlePrompt = async (promptText) => {
|
|
965
1224
|
const normalizedPrompt = normalizeText(promptText);
|
|
@@ -1111,7 +1370,7 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
1111
1370
|
}
|
|
1112
1371
|
async function printStatusView(rl, config, runtimeSession) {
|
|
1113
1372
|
printSection("Bridge Status");
|
|
1114
|
-
|
|
1373
|
+
console.log(renderInfoCard(buildStatusCard(config, runtimeSession)));
|
|
1115
1374
|
await pauseForEnter(rl);
|
|
1116
1375
|
}
|
|
1117
1376
|
async function printHelpView(rl) {
|
|
@@ -1152,7 +1411,7 @@ async function pickHomeChoice(rl, paired) {
|
|
|
1152
1411
|
`${style("2.", ANSI.brandBlue, supportsAnsi())} Terminal`,
|
|
1153
1412
|
`${style("3.", ANSI.brandBlue, supportsAnsi())} Setup / parear novamente`,
|
|
1154
1413
|
`${style("4.", ANSI.brandBlue, supportsAnsi())} Status detalhado`,
|
|
1155
|
-
`${style("5.", ANSI.brandBlue, supportsAnsi())} Extensões
|
|
1414
|
+
`${style("5.", ANSI.brandBlue, supportsAnsi())} Extensões`,
|
|
1156
1415
|
`${style("6.", ANSI.brandBlue, supportsAnsi())} Ajuda`,
|
|
1157
1416
|
`${style("7.", ANSI.brandBlue, supportsAnsi())} Sair`,
|
|
1158
1417
|
]
|
|
@@ -1190,6 +1449,7 @@ async function pickHomeChoice(rl, paired) {
|
|
|
1190
1449
|
export async function launchInteractiveCli(options) {
|
|
1191
1450
|
let rl = await createPromptInterface();
|
|
1192
1451
|
let runtimeSession = null;
|
|
1452
|
+
let restartRequested = false;
|
|
1193
1453
|
try {
|
|
1194
1454
|
let config = await loadBridgeConfig();
|
|
1195
1455
|
if (!config) {
|
|
@@ -1203,7 +1463,7 @@ export async function launchInteractiveCli(options) {
|
|
|
1203
1463
|
await runOttoConsole(rl, config, runtimeSession);
|
|
1204
1464
|
}
|
|
1205
1465
|
if (!config) {
|
|
1206
|
-
return;
|
|
1466
|
+
return { restartRequested: false };
|
|
1207
1467
|
}
|
|
1208
1468
|
}
|
|
1209
1469
|
runtimeSession = runtimeSession || new CliRuntimeSession(config);
|
|
@@ -1220,18 +1480,34 @@ export async function launchInteractiveCli(options) {
|
|
|
1220
1480
|
continue;
|
|
1221
1481
|
}
|
|
1222
1482
|
if (choice === "terminal") {
|
|
1483
|
+
const snapshotBefore = await captureBridgeRuntimeSnapshot(config);
|
|
1223
1484
|
rl.close();
|
|
1224
1485
|
await runTerminalShell({ fromHub: true });
|
|
1225
|
-
rl = await createPromptInterface();
|
|
1226
1486
|
const refreshedConfig = await loadBridgeConfig();
|
|
1487
|
+
const snapshotAfter = await captureBridgeRuntimeSnapshot(refreshedConfig);
|
|
1488
|
+
const runtimeDiff = diffBridgeRuntimeSnapshots(snapshotBefore, snapshotAfter);
|
|
1489
|
+
rl = await createPromptInterface();
|
|
1227
1490
|
if (!refreshedConfig) {
|
|
1228
1491
|
await runtimeSession?.stop().catch(() => undefined);
|
|
1229
1492
|
runtimeSession = null;
|
|
1230
1493
|
printWarning("Pairing local removido no terminal. Encerrando o hub.");
|
|
1231
1494
|
break;
|
|
1232
1495
|
}
|
|
1233
|
-
if (
|
|
1496
|
+
if (runtimeDiff.requiresCliRestart) {
|
|
1497
|
+
config = refreshedConfig;
|
|
1498
|
+
restartRequested = true;
|
|
1499
|
+
await showRuntimeRefreshProgress({
|
|
1500
|
+
title: "Reiniciando Otto Bridge",
|
|
1501
|
+
detail: "Aguarde um minuto, o Otto Bridge está reiniciando para aplicar mudanças.",
|
|
1502
|
+
});
|
|
1503
|
+
break;
|
|
1504
|
+
}
|
|
1505
|
+
if (runtimeDiff.requiresRuntimeRefresh) {
|
|
1234
1506
|
config = refreshedConfig;
|
|
1507
|
+
await showRuntimeRefreshProgress({
|
|
1508
|
+
title: "Aplicando mudanças do runtime",
|
|
1509
|
+
detail: "Aguarde um instante, o Otto Bridge está atualizando o runtime local para aplicar mudanças.",
|
|
1510
|
+
});
|
|
1235
1511
|
if (runtimeSession) {
|
|
1236
1512
|
await runtimeSession.replaceConfig(refreshedConfig);
|
|
1237
1513
|
await runtimeSession.waitForReady();
|
|
@@ -1286,6 +1562,7 @@ export async function launchInteractiveCli(options) {
|
|
|
1286
1562
|
await runtimeSession?.stop().catch(() => undefined);
|
|
1287
1563
|
rl.close();
|
|
1288
1564
|
}
|
|
1565
|
+
return { restartRequested };
|
|
1289
1566
|
}
|
|
1290
1567
|
export async function runSetupCommand(options) {
|
|
1291
1568
|
const rl = await createPromptInterface();
|
package/dist/main.js
CHANGED
|
@@ -140,6 +140,32 @@ Examples:
|
|
|
140
140
|
function printVersion() {
|
|
141
141
|
console.log(`${BRIDGE_PACKAGE_NAME} ${BRIDGE_VERSION}`);
|
|
142
142
|
}
|
|
143
|
+
async function restartCurrentCliProcess() {
|
|
144
|
+
const command = process.platform === "win32" ? "otto-bridge.cmd" : "otto-bridge";
|
|
145
|
+
const fallbackEntrypoint = process.argv[1];
|
|
146
|
+
const spawnRestart = (binary, args) => new Promise((resolve, reject) => {
|
|
147
|
+
const child = spawn(binary, args, {
|
|
148
|
+
stdio: "inherit",
|
|
149
|
+
env: process.env,
|
|
150
|
+
});
|
|
151
|
+
child.once("spawn", () => {
|
|
152
|
+
resolve();
|
|
153
|
+
});
|
|
154
|
+
child.once("error", (error) => {
|
|
155
|
+
reject(error);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
try {
|
|
159
|
+
await spawnRestart(command, []);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
if (!fallbackEntrypoint) {
|
|
164
|
+
throw new Error("Nao foi possivel relancar o Otto Bridge apos atualizar.");
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
await spawnRestart(process.execPath, [fallbackEntrypoint]);
|
|
168
|
+
}
|
|
143
169
|
function runChildCommand(command, args) {
|
|
144
170
|
return new Promise((resolve, reject) => {
|
|
145
171
|
const child = spawn(command, args, {
|
|
@@ -555,7 +581,9 @@ async function main() {
|
|
|
555
581
|
const args = parseArgs(process.argv.slice(2));
|
|
556
582
|
switch (args.command) {
|
|
557
583
|
case "home":
|
|
558
|
-
await launchInteractiveCli()
|
|
584
|
+
if ((await launchInteractiveCli()).restartRequested) {
|
|
585
|
+
await restartCurrentCliProcess();
|
|
586
|
+
}
|
|
559
587
|
return;
|
|
560
588
|
case "setup":
|
|
561
589
|
await runSetupCommand({
|
|
@@ -572,7 +600,9 @@ async function main() {
|
|
|
572
600
|
await runPairCommand(args);
|
|
573
601
|
return;
|
|
574
602
|
case "run":
|
|
575
|
-
await launchInteractiveCli()
|
|
603
|
+
if ((await launchInteractiveCli()).restartRequested) {
|
|
604
|
+
await restartCurrentCliProcess();
|
|
605
|
+
}
|
|
576
606
|
return;
|
|
577
607
|
case "status":
|
|
578
608
|
await runStatusCommand();
|
package/dist/types.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const BRIDGE_CONFIG_VERSION = 1;
|
|
2
|
-
export const BRIDGE_VERSION = "1.0.
|
|
2
|
+
export const BRIDGE_VERSION = "1.0.9";
|
|
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
package/scripts/postinstall.mjs
CHANGED
|
@@ -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.
|
|
27
|
+
console.log("\n[otto-bridge] Welcome to OTTOAI 1.0.9");
|
|
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"], {
|