@leg3ndy/otto-bridge 1.1.1 → 1.1.3
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 +10 -9
- package/dist/cli_terminal.js +410 -119
- package/dist/main.js +1 -0
- 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 o patch atual `1.1.
|
|
18
|
+
Para o patch atual `1.1.3`, veja [`leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_1_3_PATCH.md`](../leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_1_3_PATCH.md). Para o corte funcional da linha `1.1.0`, veja [`leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_1_0_RELEASE.md`](../leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_1_0_RELEASE.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.1.
|
|
41
|
+
npm install -g ./leg3ndy-otto-bridge-1.1.3.tgz
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
Na linha `1.1.
|
|
44
|
+
Na linha `1.1.3`, `playwright` segue como dependencia obrigatoria no `otto-bridge`. O primeiro `npm install -g @leg3ndy/otto-bridge` pode demorar mais porque instala o browser persistente usado pelo WhatsApp Web e pelos fluxos web em background do bridge.
|
|
45
45
|
|
|
46
|
-
No macOS, a linha `1.1.
|
|
46
|
+
No macOS, a linha `1.1.3` usa o provider `macos-helper`, um helper `WKWebView` sem Dock para o WhatsApp Web. O helper sobe com user-agent de Chrome moderno para evitar o bloqueio do WhatsApp ao detectar Safari/WebKit. O runtime antigo com Chromium/Playwright fica disponivel apenas como override explicito via `OTTO_BRIDGE_WHATSAPP_RUNTIME_PROVIDER=embedded-playwright`.
|
|
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.1.
|
|
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.1.3` mantem a camada workspace-first com rail de coding, trust/policy por workspace, source control first-class, working set persistido e grounding remoto por repositório, enquanto troca o `Otto Console` para scrollback real, menu com setas e palette de comandos ao digitar `/`.
|
|
49
49
|
|
|
50
50
|
## Publicacao
|
|
51
51
|
|
|
@@ -152,8 +152,9 @@ Dentro do console, use:
|
|
|
152
152
|
- `/workspace attach <path>` para anexar uma pasta/repo novo pelo helper autenticado
|
|
153
153
|
- `/workspace use <id|n>` para fixar um workspace anexado na sessão atual
|
|
154
154
|
- `/workspace clear` para limpar o binding atual do chat/sessão
|
|
155
|
+
- `/new` para iniciar uma nova sessão e limpar o contexto local do console
|
|
155
156
|
|
|
156
|
-
No TTY, o
|
|
157
|
+
No TTY, o console agora reaproveita a própria tela do bridge quando você entra no `Otto Console`, em vez de abrir uma segunda viewport logo abaixo do hub. O header é impresso uma vez no topo da sessão, o histórico completo fica salvo no scrollback real do terminal e o conteúdo vai empurrando os blocos para cima naturalmente. O composer continua com placeholder `Peça algo ao Otto`, o footer mantém modelo/tokens/aprovação, e ao digitar `/` o bridge abre uma palette navegável por setas, com `Enter` preenchendo comandos como `/new` para iniciar uma nova sessão local.
|
|
157
158
|
|
|
158
159
|
No modo `OttoAI Thinking`, o terminal agora marca explicitamente o trecho de raciocínio com `Pensando (OttoAI Thinking)` e separa esse bloco da resposta final do Otto.
|
|
159
160
|
|
|
@@ -169,7 +170,7 @@ Esse comando abre um shell local interativo para instalar extensoes, rodar coman
|
|
|
169
170
|
|
|
170
171
|
### WhatsApp Web em background
|
|
171
172
|
|
|
172
|
-
Fluxo recomendado na linha `1.1.
|
|
173
|
+
Fluxo recomendado na linha `1.1.3`:
|
|
173
174
|
|
|
174
175
|
```bash
|
|
175
176
|
otto-bridge extensions --install whatsappweb
|
|
@@ -179,13 +180,13 @@ otto-bridge extensions --status whatsappweb
|
|
|
179
180
|
|
|
180
181
|
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.
|
|
181
182
|
|
|
182
|
-
Contrato da linha `1.1.
|
|
183
|
+
Contrato da linha `1.1.3`:
|
|
183
184
|
|
|
184
185
|
- `otto-bridge extensions --setup whatsappweb`: autentica a sessao uma vez
|
|
185
186
|
- `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
|
|
186
187
|
- ao fechar o `otto-bridge`: o browser em background e desligado, mas a sessao local fica lembrada para o proximo boot
|
|
187
188
|
|
|
188
|
-
## Handoff rapido da linha 1.1.
|
|
189
|
+
## Handoff rapido da linha 1.1.3
|
|
189
190
|
|
|
190
191
|
Ja fechado no codigo:
|
|
191
192
|
|
package/dist/cli_terminal.js
CHANGED
|
@@ -56,11 +56,74 @@ const MAX_RENDERED_LIST_ENTRIES_COMPACT = 10;
|
|
|
56
56
|
const MAX_RENDERED_FILE_CHARS = 6_000;
|
|
57
57
|
const MAX_RENDERED_FILE_CHARS_COMPACT = 1_400;
|
|
58
58
|
const CONSOLE_PLACEHOLDER = "Peça algo ao Otto";
|
|
59
|
-
const CONSOLE_COMMAND_HINT = "/help, /model [fast|thinking], /approval [preview|confirm|trusted], /workspace, /status, /clear, /exit";
|
|
59
|
+
const CONSOLE_COMMAND_HINT = "/help, /model [fast|thinking], /approval [preview|confirm|trusted], /workspace, /status, /clear, /new, /exit";
|
|
60
60
|
const CONSOLE_COMPOSER_PROMPT_WIDTH = 2;
|
|
61
61
|
const CONSOLE_COMPOSER_CURSOR_COLUMN = 2;
|
|
62
62
|
const CONSOLE_EST_CHARS_PER_TOKEN = 4;
|
|
63
63
|
const CONSOLE_EST_MESSAGE_OVERHEAD_TOKENS = 8;
|
|
64
|
+
const CONSOLE_MENU_HINT = "↑ ↓ navegar • Enter selecionar";
|
|
65
|
+
const CONSOLE_SLASH_SUGGESTIONS = [
|
|
66
|
+
{
|
|
67
|
+
command: "/new",
|
|
68
|
+
insertText: "/new",
|
|
69
|
+
description: "inicia uma nova sessão e limpa o contexto local",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
command: "/help",
|
|
73
|
+
insertText: "/help",
|
|
74
|
+
description: "mostra os comandos disponíveis no console",
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
command: "/model fast",
|
|
78
|
+
insertText: "/model fast",
|
|
79
|
+
description: "troca para OttoAI Fast",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
command: "/model thinking",
|
|
83
|
+
insertText: "/model thinking",
|
|
84
|
+
description: "troca para OttoAI Thinking",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
command: "/approval preview",
|
|
88
|
+
insertText: "/approval preview",
|
|
89
|
+
description: "usa permissões padrão no device",
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
command: "/approval confirm",
|
|
93
|
+
insertText: "/approval confirm",
|
|
94
|
+
description: "pede confirmação manual por passo",
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
command: "/approval trusted",
|
|
98
|
+
insertText: "/approval trusted",
|
|
99
|
+
description: "ativa bypass de permissões no device",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
command: "/workspace",
|
|
103
|
+
insertText: "/workspace",
|
|
104
|
+
description: "mostra o workspace ativo e os anexados",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
command: "/workspace list",
|
|
108
|
+
insertText: "/workspace list",
|
|
109
|
+
description: "lista os workspaces anexados ao device",
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
command: "/status",
|
|
113
|
+
insertText: "/status",
|
|
114
|
+
description: "mostra status técnico do bridge e do runtime",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
command: "/clear",
|
|
118
|
+
insertText: "/clear",
|
|
119
|
+
description: "limpa a tela e o contexto local desta sessão",
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
command: "/exit",
|
|
123
|
+
insertText: "/exit",
|
|
124
|
+
description: "sai do Otto Console",
|
|
125
|
+
},
|
|
126
|
+
];
|
|
64
127
|
class CliRuntimeSession {
|
|
65
128
|
config;
|
|
66
129
|
runtime = null;
|
|
@@ -238,6 +301,9 @@ function renderBanner() {
|
|
|
238
301
|
`${style(subtitle, ANSI.slate, enabled)}`,
|
|
239
302
|
].join("\n");
|
|
240
303
|
}
|
|
304
|
+
function getCardMaxContentWidth() {
|
|
305
|
+
return Math.max(24, Number(output.columns || 96) - 4);
|
|
306
|
+
}
|
|
241
307
|
function createCliExitError() {
|
|
242
308
|
return new Error(CLI_EXIT_SENTINEL);
|
|
243
309
|
}
|
|
@@ -384,6 +450,16 @@ function getNextApprovalMode(mode) {
|
|
|
384
450
|
}
|
|
385
451
|
return "preview";
|
|
386
452
|
}
|
|
453
|
+
export function resolveConsoleSlashSuggestions(value) {
|
|
454
|
+
const normalized = normalizeText(value).toLowerCase();
|
|
455
|
+
if (!normalized.startsWith("/") || normalized.includes("\n")) {
|
|
456
|
+
return [];
|
|
457
|
+
}
|
|
458
|
+
if (normalized === "/") {
|
|
459
|
+
return [...CONSOLE_SLASH_SUGGESTIONS];
|
|
460
|
+
}
|
|
461
|
+
return CONSOLE_SLASH_SUGGESTIONS.filter((item) => item.command.startsWith(normalized));
|
|
462
|
+
}
|
|
387
463
|
function styleTranscriptLine(text, tone, enabled) {
|
|
388
464
|
if (!enabled) {
|
|
389
465
|
return text;
|
|
@@ -425,16 +501,30 @@ function buildConsoleFooterStatusLine(width, modelMode, usageTokens, enabled) {
|
|
|
425
501
|
const usageRatio = contextLimit > 0 ? usageTokens / contextLimit : 0;
|
|
426
502
|
const percent = Math.round(Math.max(0, Math.min(usageRatio, 1)) * 100);
|
|
427
503
|
const barWidth = Math.max(12, Math.min(28, Math.floor(width * 0.18)));
|
|
428
|
-
|
|
504
|
+
const modelLabel = style(getCliModelLabel(modelMode), ANSI.slate, enabled);
|
|
505
|
+
return `${modelLabel} | ${buildConsoleUsageBar(barWidth, usageRatio, enabled)} ${percent}% | ${formatCompactTokenCount(usageTokens)} tokens`;
|
|
429
506
|
}
|
|
430
507
|
function buildConsoleFooterApprovalLine(mode, enabled, statusSuffix) {
|
|
431
508
|
const state = getConsoleApprovalFooterState(mode);
|
|
432
509
|
const hint = statusSuffix || "Shift+Tab para alternar";
|
|
433
510
|
return style(`${state.label} (${hint})`, state.tone === "warning" ? ANSI.red : state.tone === "primary" ? ANSI.amber : ANSI.slate, enabled);
|
|
434
511
|
}
|
|
512
|
+
function styleTranscriptPrefix(text, tone, enabled) {
|
|
513
|
+
if (!enabled || !text) {
|
|
514
|
+
return text;
|
|
515
|
+
}
|
|
516
|
+
if (tone === "assistant") {
|
|
517
|
+
return `${ANSI.bold}${ANSI.brandBlue}${text}${ANSI.reset}`;
|
|
518
|
+
}
|
|
519
|
+
if (tone === "user") {
|
|
520
|
+
return `${ANSI.bold}${ANSI.white}${text}${ANSI.reset}`;
|
|
521
|
+
}
|
|
522
|
+
return text;
|
|
523
|
+
}
|
|
435
524
|
class ConsoleScreenRenderer {
|
|
436
525
|
modelMode;
|
|
437
526
|
approvalMode;
|
|
527
|
+
headerFactory;
|
|
438
528
|
transcript = [];
|
|
439
529
|
onResize = () => {
|
|
440
530
|
this.render();
|
|
@@ -444,15 +534,19 @@ class ConsoleScreenRenderer {
|
|
|
444
534
|
draftValue = "";
|
|
445
535
|
conversationMessages = [];
|
|
446
536
|
approvalStatusSuffix = null;
|
|
447
|
-
|
|
537
|
+
usingAlternateBuffer = false;
|
|
538
|
+
constructor(modelMode, approvalMode, headerFactory = () => []) {
|
|
448
539
|
this.modelMode = modelMode;
|
|
449
540
|
this.approvalMode = approvalMode;
|
|
541
|
+
this.headerFactory = headerFactory;
|
|
450
542
|
}
|
|
451
543
|
activate() {
|
|
452
544
|
if (this.active || !supportsAnsi() || !input.isTTY || !output.isTTY) {
|
|
453
545
|
return;
|
|
454
546
|
}
|
|
455
547
|
this.active = true;
|
|
548
|
+
output.write("\u001b[?1049h");
|
|
549
|
+
this.usingAlternateBuffer = true;
|
|
456
550
|
output.on("resize", this.onResize);
|
|
457
551
|
this.render();
|
|
458
552
|
}
|
|
@@ -463,6 +557,10 @@ class ConsoleScreenRenderer {
|
|
|
463
557
|
output.off("resize", this.onResize);
|
|
464
558
|
this.active = false;
|
|
465
559
|
output.write("\u001b[?25h");
|
|
560
|
+
if (this.usingAlternateBuffer) {
|
|
561
|
+
output.write("\u001b[?1049l");
|
|
562
|
+
this.usingAlternateBuffer = false;
|
|
563
|
+
}
|
|
466
564
|
}
|
|
467
565
|
isActive() {
|
|
468
566
|
return this.active;
|
|
@@ -523,7 +621,8 @@ class ConsoleScreenRenderer {
|
|
|
523
621
|
const wrapped = sliceByWidth(logicalLine, Math.max(1, width - currentPrefix.length));
|
|
524
622
|
wrapped.forEach((segment, index) => {
|
|
525
623
|
const linePrefix = isFirstVisualLine && index === 0 ? prefix : continuationPrefix;
|
|
526
|
-
|
|
624
|
+
const prefixTone = isFirstVisualLine && index === 0 ? entry.prefixTone : undefined;
|
|
625
|
+
rendered.push(`${styleTranscriptPrefix(linePrefix, prefixTone, supportsAnsi())}${styleTranscriptLine(segment, entry.tone, supportsAnsi())}`);
|
|
527
626
|
});
|
|
528
627
|
if (logicalLine.length === 0 && wrapped.length === 0) {
|
|
529
628
|
rendered.push("");
|
|
@@ -539,6 +638,7 @@ class ConsoleScreenRenderer {
|
|
|
539
638
|
const enabled = supportsAnsi();
|
|
540
639
|
const width = Math.max(48, Number(output.columns || 96));
|
|
541
640
|
const height = Math.max(12, Number(output.rows || 24));
|
|
641
|
+
const headerLines = this.headerFactory();
|
|
542
642
|
const separator = style("─".repeat(width), ANSI.brandBlue, enabled);
|
|
543
643
|
const composer = renderConsoleComposerLines(this.draftValue, width, enabled);
|
|
544
644
|
const usageTokens = Math.min(estimateConsoleContextTokens(this.conversationMessages, this.draftValue), getCliModelContextWindowTokens(this.modelMode));
|
|
@@ -549,7 +649,8 @@ class ConsoleScreenRenderer {
|
|
|
549
649
|
buildConsoleFooterStatusLine(width, this.modelMode, usageTokens, enabled),
|
|
550
650
|
buildConsoleFooterApprovalLine(this.approvalMode, enabled, this.approvalStatusSuffix),
|
|
551
651
|
];
|
|
552
|
-
const
|
|
652
|
+
const visibleHeader = headerLines.slice(0, Math.max(0, height - footerLines.length));
|
|
653
|
+
const transcriptHeight = Math.max(0, height - visibleHeader.length - footerLines.length);
|
|
553
654
|
const transcriptLines = this.transcript.flatMap((entry) => this.buildEntryLines(entry, width));
|
|
554
655
|
const visibleTranscript = transcriptLines.slice(-transcriptHeight);
|
|
555
656
|
const paddedTranscript = [
|
|
@@ -558,11 +659,14 @@ class ConsoleScreenRenderer {
|
|
|
558
659
|
];
|
|
559
660
|
output.write("\u001b[?25l");
|
|
560
661
|
output.write("\u001b[H\u001b[2J");
|
|
561
|
-
output.write([...paddedTranscript, ...footerLines].join("\n"));
|
|
562
|
-
cursorTo(output, Math.min(width - 1, CONSOLE_COMPOSER_CURSOR_COLUMN + composer.cursorColumn), transcriptHeight + 1 + composer.cursorLineIndex);
|
|
662
|
+
output.write([...visibleHeader, ...paddedTranscript, ...footerLines].join("\n"));
|
|
663
|
+
cursorTo(output, Math.min(width - 1, CONSOLE_COMPOSER_CURSOR_COLUMN + composer.cursorColumn), visibleHeader.length + transcriptHeight + 1 + composer.cursorLineIndex);
|
|
563
664
|
output.write("\u001b[?25h");
|
|
564
665
|
}
|
|
565
666
|
}
|
|
667
|
+
function createConsoleScreenRenderer(_modelMode, _approvalMode, _runtimeSession) {
|
|
668
|
+
return null;
|
|
669
|
+
}
|
|
566
670
|
export function resolveCliModelMode(value) {
|
|
567
671
|
const normalized = normalizeText(value).toLowerCase();
|
|
568
672
|
if (!normalized) {
|
|
@@ -797,13 +901,14 @@ function styleCardLine(line, tone, enabled) {
|
|
|
797
901
|
}
|
|
798
902
|
return `${ANSI.white}${line}${ANSI.reset}`;
|
|
799
903
|
}
|
|
800
|
-
function renderInfoCard(lines) {
|
|
904
|
+
export function renderInfoCard(lines, maxContentWidth = 82) {
|
|
801
905
|
const enabled = supportsAnsi();
|
|
906
|
+
const safeMaxContentWidth = Math.max(20, maxContentWidth);
|
|
802
907
|
const normalized = lines.map((line) => ({
|
|
803
|
-
text: truncate(line.text,
|
|
908
|
+
text: truncate(line.text, safeMaxContentWidth),
|
|
804
909
|
tone: line.tone,
|
|
805
910
|
}));
|
|
806
|
-
const width = Math.max(44, ...normalized.map((line) => line.text.length));
|
|
911
|
+
const width = Math.max(Math.min(44, safeMaxContentWidth), ...normalized.map((line) => line.text.length));
|
|
807
912
|
const top = style(`┌${"─".repeat(width + 2)}┐`, ANSI.brandBlue, enabled);
|
|
808
913
|
const bottom = style(`└${"─".repeat(width + 2)}┘`, ANSI.brandBlue, enabled);
|
|
809
914
|
return [
|
|
@@ -816,17 +921,31 @@ function renderInfoCard(lines) {
|
|
|
816
921
|
bottom,
|
|
817
922
|
].join("\n");
|
|
818
923
|
}
|
|
819
|
-
function buildWelcomeCard(runtimeSession
|
|
924
|
+
export function buildWelcomeCard(runtimeSession) {
|
|
820
925
|
return [
|
|
821
926
|
{ text: "Welcome to OttoAI", tone: "title" },
|
|
822
927
|
{ text: "", tone: "muted" },
|
|
823
928
|
{ text: "/help inside the console, /status for bridge details", tone: "muted" },
|
|
824
929
|
{ text: "", tone: "muted" },
|
|
825
|
-
{ text: `model: ${getCliModelLabel(modelMode)}`, tone: "muted" },
|
|
826
930
|
{ text: `cwd: ${process.cwd()}`, tone: "muted" },
|
|
827
931
|
{ text: renderRuntimeHeadline(runtimeSession), tone: "muted" },
|
|
828
932
|
];
|
|
829
933
|
}
|
|
934
|
+
function buildConsoleHeaderLines(runtimeSession) {
|
|
935
|
+
const lines = [
|
|
936
|
+
...renderBanner().split("\n"),
|
|
937
|
+
"",
|
|
938
|
+
...renderInfoCard(buildWelcomeCard(runtimeSession), getCardMaxContentWidth()).split("\n"),
|
|
939
|
+
];
|
|
940
|
+
const releaseNotice = runtimeSession.getReleaseNotice();
|
|
941
|
+
if (releaseNotice) {
|
|
942
|
+
lines.push("");
|
|
943
|
+
lines.push(...renderInfoCard(buildBridgeReleaseCard(releaseNotice), getCardMaxContentWidth()).split("\n"));
|
|
944
|
+
}
|
|
945
|
+
lines.push("");
|
|
946
|
+
lines.push(style(`Comandos: ${CONSOLE_COMMAND_HINT}`, ANSI.slateItalic, supportsAnsi()));
|
|
947
|
+
return lines;
|
|
948
|
+
}
|
|
830
949
|
function buildBridgeReleaseCard(notice) {
|
|
831
950
|
const isRequired = notice.kind === "required";
|
|
832
951
|
return [
|
|
@@ -916,29 +1035,20 @@ async function buildExtensionsCard(config) {
|
|
|
916
1035
|
}
|
|
917
1036
|
return lines;
|
|
918
1037
|
}
|
|
919
|
-
function printHubScreen(runtimeSession
|
|
1038
|
+
function printHubScreen(runtimeSession) {
|
|
920
1039
|
clearScreen();
|
|
921
1040
|
console.log(renderBanner());
|
|
922
1041
|
console.log("");
|
|
923
|
-
console.log(renderInfoCard(buildWelcomeCard(runtimeSession,
|
|
1042
|
+
console.log(renderInfoCard(buildWelcomeCard(runtimeSession), getCardMaxContentWidth()));
|
|
924
1043
|
const releaseNotice = runtimeSession?.getReleaseNotice();
|
|
925
1044
|
if (releaseNotice) {
|
|
926
1045
|
console.log("");
|
|
927
|
-
console.log(renderInfoCard(buildBridgeReleaseCard(releaseNotice)));
|
|
1046
|
+
console.log(renderInfoCard(buildBridgeReleaseCard(releaseNotice), getCardMaxContentWidth()));
|
|
928
1047
|
}
|
|
929
1048
|
}
|
|
930
|
-
function printConsoleScreen(runtimeSession
|
|
1049
|
+
function printConsoleScreen(runtimeSession) {
|
|
931
1050
|
clearScreen();
|
|
932
|
-
console.log(
|
|
933
|
-
console.log("");
|
|
934
|
-
console.log(renderInfoCard(buildWelcomeCard(runtimeSession, modelMode)));
|
|
935
|
-
const releaseNotice = runtimeSession.getReleaseNotice();
|
|
936
|
-
if (releaseNotice) {
|
|
937
|
-
console.log("");
|
|
938
|
-
console.log(renderInfoCard(buildBridgeReleaseCard(releaseNotice)));
|
|
939
|
-
}
|
|
940
|
-
console.log("");
|
|
941
|
-
printSoft(`Comandos: ${CONSOLE_COMMAND_HINT}`);
|
|
1051
|
+
console.log(buildConsoleHeaderLines(runtimeSession).join("\n"));
|
|
942
1052
|
console.log("");
|
|
943
1053
|
}
|
|
944
1054
|
function formatConsoleWorkspaceLine(workspace, options) {
|
|
@@ -1202,6 +1312,30 @@ export function tryConsumeControlSequence(buffer) {
|
|
|
1202
1312
|
};
|
|
1203
1313
|
}
|
|
1204
1314
|
}
|
|
1315
|
+
const moveUpSequences = [
|
|
1316
|
+
"\u001b[A",
|
|
1317
|
+
"\u001bOA",
|
|
1318
|
+
];
|
|
1319
|
+
for (const sequence of moveUpSequences) {
|
|
1320
|
+
if (buffer.startsWith(sequence)) {
|
|
1321
|
+
return {
|
|
1322
|
+
consumed: sequence.length,
|
|
1323
|
+
action: "move_up",
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
const moveDownSequences = [
|
|
1328
|
+
"\u001b[B",
|
|
1329
|
+
"\u001bOB",
|
|
1330
|
+
];
|
|
1331
|
+
for (const sequence of moveDownSequences) {
|
|
1332
|
+
if (buffer.startsWith(sequence)) {
|
|
1333
|
+
return {
|
|
1334
|
+
consumed: sequence.length,
|
|
1335
|
+
action: "move_down",
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1205
1339
|
if (buffer === "\u001b" || /^\u001b\[[0-9;?]*$/.test(buffer)) {
|
|
1206
1340
|
return {
|
|
1207
1341
|
consumed: 0,
|
|
@@ -1240,55 +1374,100 @@ async function askConsoleInput(rl, options) {
|
|
|
1240
1374
|
return await new Promise((resolve, reject) => {
|
|
1241
1375
|
const enabled = supportsAnsi();
|
|
1242
1376
|
const ui = options?.ui && options.ui.isActive() ? options.ui : null;
|
|
1243
|
-
const availableWidth = Number(output.columns || 96);
|
|
1244
|
-
const innerWidth = Math.max(42, Math.min(availableWidth - 8, 116));
|
|
1245
|
-
const sectionTopOffsetFromInputLine = 1;
|
|
1246
1377
|
let value = "";
|
|
1247
1378
|
let renderedOnce = false;
|
|
1379
|
+
let renderedCursorLineIndex = 0;
|
|
1248
1380
|
let controlBuffer = "";
|
|
1381
|
+
let selectedSuggestionIndex = 0;
|
|
1382
|
+
const getPromptWidth = () => Math.max(48, Number(output.columns || 96));
|
|
1383
|
+
const getModelMode = () => options?.getModelMode?.() || "fast";
|
|
1384
|
+
const getApprovalMode = () => options?.getApprovalMode?.() || "preview";
|
|
1385
|
+
const getConversationMessages = () => options?.getConversationMessages?.() || [];
|
|
1386
|
+
const getVisibleSuggestions = () => {
|
|
1387
|
+
const suggestions = resolveConsoleSlashSuggestions(value).slice(0, 6);
|
|
1388
|
+
if (selectedSuggestionIndex >= suggestions.length) {
|
|
1389
|
+
selectedSuggestionIndex = Math.max(0, suggestions.length - 1);
|
|
1390
|
+
}
|
|
1391
|
+
return suggestions;
|
|
1392
|
+
};
|
|
1249
1393
|
const cleanup = () => {
|
|
1250
1394
|
input.removeListener("data", onData);
|
|
1395
|
+
output.off("resize", renderPromptBlock);
|
|
1396
|
+
if (!ui && renderedOnce) {
|
|
1397
|
+
cursorTo(output, 0);
|
|
1398
|
+
moveCursor(output, 0, -renderedCursorLineIndex);
|
|
1399
|
+
clearScreenDown(output);
|
|
1400
|
+
}
|
|
1251
1401
|
input.setRawMode(false);
|
|
1252
1402
|
input.pause();
|
|
1253
1403
|
rl.resume();
|
|
1254
1404
|
ui?.setDraftValue("");
|
|
1255
1405
|
};
|
|
1256
|
-
const
|
|
1257
|
-
const
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
if (!value) {
|
|
1261
|
-
const placeholder = truncate(CONSOLE_PLACEHOLDER, maxValueLength);
|
|
1262
|
-
const padded = `${style(placeholder, ANSI.slate, enabled)}${" ".repeat(Math.max(0, maxValueLength - placeholder.length))}`;
|
|
1263
|
-
return `${promptStyled} ${padded}`;
|
|
1406
|
+
const renderSuggestionLines = (width) => {
|
|
1407
|
+
const suggestions = getVisibleSuggestions();
|
|
1408
|
+
if (!suggestions.length) {
|
|
1409
|
+
return [];
|
|
1264
1410
|
}
|
|
1265
|
-
const
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1411
|
+
const suggestionWidth = Math.max(24, width - 4);
|
|
1412
|
+
return suggestions.map((item, index) => {
|
|
1413
|
+
const selected = index === selectedSuggestionIndex;
|
|
1414
|
+
const prefix = selected
|
|
1415
|
+
? style("›", `${ANSI.bold}${ANSI.brandBlue}`, enabled)
|
|
1416
|
+
: style(" ", ANSI.slate, enabled);
|
|
1417
|
+
const command = style(item.command, selected ? `${ANSI.bold}${ANSI.white}` : ANSI.brandBlue, enabled);
|
|
1418
|
+
const description = style(truncate(item.description, Math.max(12, suggestionWidth - item.command.length - 4)), selected ? ANSI.white : ANSI.slate, enabled);
|
|
1419
|
+
return `${prefix} ${command} ${description}`;
|
|
1420
|
+
});
|
|
1269
1421
|
};
|
|
1270
|
-
const
|
|
1422
|
+
const renderPromptBlock = () => {
|
|
1271
1423
|
if (ui) {
|
|
1272
1424
|
ui.setDraftValue(value);
|
|
1273
1425
|
return;
|
|
1274
1426
|
}
|
|
1427
|
+
const width = getPromptWidth();
|
|
1428
|
+
const composer = renderConsoleComposerLines(value, width, enabled);
|
|
1429
|
+
const modelMode = getModelMode();
|
|
1430
|
+
const approvalMode = getApprovalMode();
|
|
1431
|
+
const usageTokens = Math.min(estimateConsoleContextTokens(getConversationMessages(), value), getCliModelContextWindowTokens(modelMode));
|
|
1432
|
+
const suggestionLines = renderSuggestionLines(width);
|
|
1433
|
+
const blockLines = [
|
|
1434
|
+
style("─".repeat(width), ANSI.brandBlue, enabled),
|
|
1435
|
+
...composer.renderedLines,
|
|
1436
|
+
...suggestionLines,
|
|
1437
|
+
style("─".repeat(width), ANSI.brandBlue, enabled),
|
|
1438
|
+
buildConsoleFooterStatusLine(width, modelMode, usageTokens, enabled),
|
|
1439
|
+
buildConsoleFooterApprovalLine(approvalMode, enabled),
|
|
1440
|
+
];
|
|
1275
1441
|
if (renderedOnce) {
|
|
1276
1442
|
cursorTo(output, 0);
|
|
1277
|
-
moveCursor(output, 0, -
|
|
1443
|
+
moveCursor(output, 0, -renderedCursorLineIndex);
|
|
1444
|
+
clearScreenDown(output);
|
|
1278
1445
|
}
|
|
1279
1446
|
else {
|
|
1280
1447
|
renderedOnce = true;
|
|
1281
1448
|
}
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
const
|
|
1285
|
-
const bottom = renderPromptFrameLine(innerWidth + 2, "└", "┘");
|
|
1286
|
-
clearScreenDown(output);
|
|
1287
|
-
output.write(`${top}\n${middle}\n${bottom}\n`);
|
|
1449
|
+
output.write(blockLines.join("\n"));
|
|
1450
|
+
renderedCursorLineIndex = 1 + composer.cursorLineIndex;
|
|
1451
|
+
const linesBelowCursor = blockLines.length - 1 - renderedCursorLineIndex;
|
|
1288
1452
|
cursorTo(output, 0);
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1453
|
+
if (linesBelowCursor > 0) {
|
|
1454
|
+
moveCursor(output, 0, -linesBelowCursor);
|
|
1455
|
+
}
|
|
1456
|
+
cursorTo(output, Math.min(width - 1, CONSOLE_COMPOSER_CURSOR_COLUMN + composer.cursorColumn));
|
|
1457
|
+
};
|
|
1458
|
+
const applySelectedSuggestion = () => {
|
|
1459
|
+
const suggestions = getVisibleSuggestions();
|
|
1460
|
+
const selected = suggestions[selectedSuggestionIndex];
|
|
1461
|
+
if (!selected) {
|
|
1462
|
+
return false;
|
|
1463
|
+
}
|
|
1464
|
+
if (normalizeText(value) === selected.insertText) {
|
|
1465
|
+
return false;
|
|
1466
|
+
}
|
|
1467
|
+
value = selected.insertText;
|
|
1468
|
+
selectedSuggestionIndex = 0;
|
|
1469
|
+
renderPromptBlock();
|
|
1470
|
+
return true;
|
|
1292
1471
|
};
|
|
1293
1472
|
const consumeControlBuffer = () => {
|
|
1294
1473
|
while (controlBuffer.length > 0) {
|
|
@@ -1305,12 +1484,31 @@ async function askConsoleInput(rl, options) {
|
|
|
1305
1484
|
continue;
|
|
1306
1485
|
}
|
|
1307
1486
|
if (parsed.action === "cycle_approval") {
|
|
1308
|
-
void options?.onCycleApprovalMode?.()
|
|
1487
|
+
void Promise.resolve(options?.onCycleApprovalMode?.()).finally(() => {
|
|
1488
|
+
renderPromptBlock();
|
|
1489
|
+
});
|
|
1490
|
+
continue;
|
|
1491
|
+
}
|
|
1492
|
+
if (parsed.action === "move_up") {
|
|
1493
|
+
const suggestions = getVisibleSuggestions();
|
|
1494
|
+
if (suggestions.length > 0) {
|
|
1495
|
+
selectedSuggestionIndex = selectedSuggestionIndex > 0 ? selectedSuggestionIndex - 1 : suggestions.length - 1;
|
|
1496
|
+
renderPromptBlock();
|
|
1497
|
+
}
|
|
1498
|
+
continue;
|
|
1499
|
+
}
|
|
1500
|
+
if (parsed.action === "move_down") {
|
|
1501
|
+
const suggestions = getVisibleSuggestions();
|
|
1502
|
+
if (suggestions.length > 0) {
|
|
1503
|
+
selectedSuggestionIndex = selectedSuggestionIndex < suggestions.length - 1 ? selectedSuggestionIndex + 1 : 0;
|
|
1504
|
+
renderPromptBlock();
|
|
1505
|
+
}
|
|
1309
1506
|
continue;
|
|
1310
1507
|
}
|
|
1311
1508
|
if (parsed.action === "newline") {
|
|
1312
1509
|
value += "\n";
|
|
1313
|
-
|
|
1510
|
+
selectedSuggestionIndex = 0;
|
|
1511
|
+
renderPromptBlock();
|
|
1314
1512
|
}
|
|
1315
1513
|
}
|
|
1316
1514
|
};
|
|
@@ -1328,26 +1526,30 @@ async function askConsoleInput(rl, options) {
|
|
|
1328
1526
|
return;
|
|
1329
1527
|
}
|
|
1330
1528
|
if (char === "\r" || char === "\n") {
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
output.write("\n");
|
|
1529
|
+
if (applySelectedSuggestion()) {
|
|
1530
|
+
continue;
|
|
1334
1531
|
}
|
|
1532
|
+
cleanup();
|
|
1533
|
+
output.write("\n");
|
|
1335
1534
|
resolve(normalizeText(value));
|
|
1336
1535
|
return;
|
|
1337
1536
|
}
|
|
1338
1537
|
if (char === "\u007f" || char === "\b") {
|
|
1339
1538
|
value = value.slice(0, -1);
|
|
1340
|
-
|
|
1539
|
+
selectedSuggestionIndex = 0;
|
|
1540
|
+
renderPromptBlock();
|
|
1341
1541
|
continue;
|
|
1342
1542
|
}
|
|
1343
1543
|
if (char === "\t") {
|
|
1544
|
+
applySelectedSuggestion();
|
|
1344
1545
|
continue;
|
|
1345
1546
|
}
|
|
1346
1547
|
value += char;
|
|
1347
|
-
|
|
1548
|
+
renderPromptBlock();
|
|
1348
1549
|
}
|
|
1349
1550
|
};
|
|
1350
|
-
|
|
1551
|
+
output.on("resize", renderPromptBlock);
|
|
1552
|
+
renderPromptBlock();
|
|
1351
1553
|
input.on("data", onData);
|
|
1352
1554
|
});
|
|
1353
1555
|
}
|
|
@@ -1649,14 +1851,11 @@ async function followConsoleJob(rl, config, jobId) {
|
|
|
1649
1851
|
}
|
|
1650
1852
|
async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
1651
1853
|
let activeModel = "fast";
|
|
1652
|
-
|
|
1854
|
+
let sessionId = randomUUID();
|
|
1653
1855
|
const conversation = [];
|
|
1654
|
-
const ui =
|
|
1655
|
-
? new ConsoleScreenRenderer(activeModel, config.approvalMode)
|
|
1656
|
-
: null;
|
|
1657
|
-
ui?.activate();
|
|
1856
|
+
const ui = createConsoleScreenRenderer(activeModel, config.approvalMode, runtimeSession);
|
|
1658
1857
|
if (!ui) {
|
|
1659
|
-
printConsoleScreen(runtimeSession
|
|
1858
|
+
printConsoleScreen(runtimeSession);
|
|
1660
1859
|
}
|
|
1661
1860
|
const renderConversationState = () => {
|
|
1662
1861
|
ui?.setConversationMessages(conversation);
|
|
@@ -1669,12 +1868,13 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
1669
1868
|
text,
|
|
1670
1869
|
tone,
|
|
1671
1870
|
prefix: options?.prefix,
|
|
1871
|
+
prefixTone: options?.prefixTone,
|
|
1672
1872
|
continuationPrefix: options?.continuationPrefix,
|
|
1673
1873
|
});
|
|
1674
1874
|
return;
|
|
1675
1875
|
}
|
|
1676
1876
|
if (options?.prefix) {
|
|
1677
|
-
console.log(`${options.prefix}${text}`);
|
|
1877
|
+
console.log(`${styleTranscriptPrefix(options.prefix, options.prefixTone, supportsAnsi())}${styleTranscriptLine(text, tone, supportsAnsi())}`);
|
|
1678
1878
|
return;
|
|
1679
1879
|
}
|
|
1680
1880
|
if (tone === "warning") {
|
|
@@ -1698,6 +1898,14 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
1698
1898
|
const emitUserPrompt = (text) => {
|
|
1699
1899
|
emitConsoleEntry(text, "user", {
|
|
1700
1900
|
prefix: "> ",
|
|
1901
|
+
prefixTone: "user",
|
|
1902
|
+
continuationPrefix: " ",
|
|
1903
|
+
});
|
|
1904
|
+
};
|
|
1905
|
+
const emitAssistantPrompt = (text) => {
|
|
1906
|
+
emitConsoleEntry(text, "assistant", {
|
|
1907
|
+
prefix: "• ",
|
|
1908
|
+
prefixTone: "assistant",
|
|
1701
1909
|
continuationPrefix: " ",
|
|
1702
1910
|
});
|
|
1703
1911
|
};
|
|
@@ -1723,6 +1931,9 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
1723
1931
|
return await askConsoleInput(rl, {
|
|
1724
1932
|
ui,
|
|
1725
1933
|
onCycleApprovalMode: cycleApprovalMode,
|
|
1934
|
+
getConversationMessages: () => conversation,
|
|
1935
|
+
getModelMode: () => activeModel,
|
|
1936
|
+
getApprovalMode: () => config.approvalMode,
|
|
1726
1937
|
});
|
|
1727
1938
|
};
|
|
1728
1939
|
const askConsoleDecision = async (promptText, defaultValue = true) => {
|
|
@@ -1736,7 +1947,7 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
1736
1947
|
};
|
|
1737
1948
|
const printConsoleHelp = () => {
|
|
1738
1949
|
emitConsoleEntry("Console", "headline");
|
|
1739
|
-
emitConsoleEntry("Comandos: /help, /model [fast|thinking], /approval [preview|confirm|trusted], /workspace, /status, /clear, /exit", "muted");
|
|
1950
|
+
emitConsoleEntry("Comandos: /help, /model [fast|thinking], /approval [preview|confirm|trusted], /workspace, /status, /clear, /new, /exit", "muted");
|
|
1740
1951
|
emitConsoleEntry("Composer: Enter envia, Shift+Enter adiciona linha e Shift+Tab alterna o modo de aprovação.", "muted");
|
|
1741
1952
|
emitConsoleEntry("Workspace: /workspace list, /workspace attach <path>, /workspace use <id|n>, /workspace clear", "muted");
|
|
1742
1953
|
};
|
|
@@ -1886,7 +2097,12 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
1886
2097
|
: status === "failed"
|
|
1887
2098
|
? "Execução local falhou."
|
|
1888
2099
|
: "Execução local cancelada.");
|
|
1889
|
-
|
|
2100
|
+
if (status === "completed") {
|
|
2101
|
+
emitAssistantPrompt(summary);
|
|
2102
|
+
}
|
|
2103
|
+
else {
|
|
2104
|
+
emitConsoleEntry(summary, status === "failed" ? "error" : "warning");
|
|
2105
|
+
}
|
|
1890
2106
|
const rendered = renderStructuredOutcome(job);
|
|
1891
2107
|
if (rendered) {
|
|
1892
2108
|
emitConsoleEntry(rendered, "muted");
|
|
@@ -1910,11 +2126,22 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
1910
2126
|
ui?.clearTranscript();
|
|
1911
2127
|
renderConversationState();
|
|
1912
2128
|
if (!ui) {
|
|
1913
|
-
printConsoleScreen(runtimeSession
|
|
2129
|
+
printConsoleScreen(runtimeSession);
|
|
1914
2130
|
}
|
|
1915
2131
|
emitConsoleEntry("Contexto local do console limpo.", "muted");
|
|
1916
2132
|
return;
|
|
1917
2133
|
}
|
|
2134
|
+
if (normalizedPrompt === "/new") {
|
|
2135
|
+
sessionId = randomUUID();
|
|
2136
|
+
conversation.splice(0, conversation.length);
|
|
2137
|
+
ui?.clearTranscript();
|
|
2138
|
+
renderConversationState();
|
|
2139
|
+
if (!ui) {
|
|
2140
|
+
printConsoleScreen(runtimeSession);
|
|
2141
|
+
}
|
|
2142
|
+
emitConsoleEntry("Nova sessão iniciada. Contexto local reiniciado.", "muted");
|
|
2143
|
+
return;
|
|
2144
|
+
}
|
|
1918
2145
|
if (normalizedPrompt === "/model") {
|
|
1919
2146
|
emitConsoleEntry(`Modelo ativo: ${getCliModelLabel(activeModel)}.`, "muted");
|
|
1920
2147
|
emitConsoleEntry("Use /model fast ou /model thinking para trocar.", "muted");
|
|
@@ -1978,10 +2205,8 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
1978
2205
|
throw createCliExitError();
|
|
1979
2206
|
}
|
|
1980
2207
|
emitUserPrompt(normalizedPrompt);
|
|
2208
|
+
emitConsoleEntry("", "muted");
|
|
1981
2209
|
conversation.push({ role: "user", content: normalizedPrompt });
|
|
1982
|
-
while (conversation.length > 18) {
|
|
1983
|
-
conversation.shift();
|
|
1984
|
-
}
|
|
1985
2210
|
renderConversationState();
|
|
1986
2211
|
let streamedAssistant = "";
|
|
1987
2212
|
let assistantEntryId = null;
|
|
@@ -2041,8 +2266,17 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
2041
2266
|
}
|
|
2042
2267
|
if (!assistantPrefixPrinted) {
|
|
2043
2268
|
assistantEntryId = ui
|
|
2044
|
-
? ui.pushEntry({
|
|
2269
|
+
? ui.pushEntry({
|
|
2270
|
+
text: "",
|
|
2271
|
+
tone: "assistant",
|
|
2272
|
+
prefix: "• ",
|
|
2273
|
+
prefixTone: "assistant",
|
|
2274
|
+
continuationPrefix: " ",
|
|
2275
|
+
})
|
|
2045
2276
|
: null;
|
|
2277
|
+
if (!ui) {
|
|
2278
|
+
output.write(styleTranscriptPrefix("• ", "assistant", supportsAnsi()));
|
|
2279
|
+
}
|
|
2046
2280
|
assistantPrefixPrinted = true;
|
|
2047
2281
|
}
|
|
2048
2282
|
if (ui && assistantEntryId) {
|
|
@@ -2065,7 +2299,7 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
2065
2299
|
: null;
|
|
2066
2300
|
const jobId = normalizeText(job?.id);
|
|
2067
2301
|
if (bridgeSummary && !jobId) {
|
|
2068
|
-
|
|
2302
|
+
emitAssistantPrompt(bridgeSummary);
|
|
2069
2303
|
}
|
|
2070
2304
|
if (jobId) {
|
|
2071
2305
|
finalAssistantSummary = await followConsoleJobUi(jobId);
|
|
@@ -2076,9 +2310,6 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
2076
2310
|
}
|
|
2077
2311
|
if (finalAssistantSummary) {
|
|
2078
2312
|
conversation.push({ role: "assistant", content: finalAssistantSummary });
|
|
2079
|
-
while (conversation.length > 18) {
|
|
2080
|
-
conversation.shift();
|
|
2081
|
-
}
|
|
2082
2313
|
renderConversationState();
|
|
2083
2314
|
}
|
|
2084
2315
|
};
|
|
@@ -2134,52 +2365,112 @@ async function printHelpView(rl) {
|
|
|
2134
2365
|
{ text: "otto-bridge version | otto-bridge unpair", tone: "primary" },
|
|
2135
2366
|
{ text: "Mostra a versao instalada ou remove o pairing local.", tone: "muted" },
|
|
2136
2367
|
{ text: "", tone: "muted" },
|
|
2137
|
-
{ text: "Dentro do console: /help, /model fast|thinking, /approval preview|confirm|trusted, /status, /clear, /exit", tone: "muted" },
|
|
2368
|
+
{ text: "Dentro do console: /help, /model fast|thinking, /approval preview|confirm|trusted, /status, /clear, /new, /exit", tone: "muted" },
|
|
2138
2369
|
]));
|
|
2139
2370
|
await pauseForEnter(rl);
|
|
2140
2371
|
}
|
|
2141
|
-
|
|
2142
|
-
|
|
2372
|
+
function renderHomeOptionLine(label, selected) {
|
|
2373
|
+
if (!selected) {
|
|
2374
|
+
return ` ${label}`;
|
|
2375
|
+
}
|
|
2376
|
+
return `${style("▸", ANSI.brandBlue, supportsAnsi())} ${style(label, `${ANSI.bold}${ANSI.white}`, supportsAnsi())}`;
|
|
2377
|
+
}
|
|
2378
|
+
async function pickHomeChoice(rl, paired, renderBaseScreen) {
|
|
2143
2379
|
const options = paired
|
|
2144
2380
|
? [
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2381
|
+
{ value: "console", label: "Otto Console" },
|
|
2382
|
+
{ value: "terminal", label: "Terminal" },
|
|
2383
|
+
{ value: "setup", label: "Setup / parear novamente" },
|
|
2384
|
+
{ value: "status", label: "Status detalhado" },
|
|
2385
|
+
{ value: "extensions", label: "Extensões" },
|
|
2386
|
+
{ value: "help", label: "Ajuda" },
|
|
2387
|
+
{ value: "exit", label: "Sair" },
|
|
2152
2388
|
]
|
|
2153
2389
|
: [
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2390
|
+
{ value: "setup", label: "Pairing setup" },
|
|
2391
|
+
{ value: "terminal", label: "Terminal" },
|
|
2392
|
+
{ value: "help", label: "Ajuda" },
|
|
2393
|
+
{ value: "exit", label: "Sair" },
|
|
2158
2394
|
];
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
if (
|
|
2165
|
-
return
|
|
2166
|
-
|
|
2167
|
-
return "help";
|
|
2395
|
+
if (!supportsAnsi() || typeof input.setRawMode !== "function" || !input.isTTY) {
|
|
2396
|
+
printSection("Home");
|
|
2397
|
+
console.log(options.map((option, index) => `${style(`${index + 1}.`, ANSI.brandBlue, supportsAnsi())} ${option.label}`).join("\n"));
|
|
2398
|
+
const answer = await ask(rl, "Escolha");
|
|
2399
|
+
const numericIndex = Number(answer);
|
|
2400
|
+
if (Number.isInteger(numericIndex) && numericIndex >= 1 && numericIndex <= options.length) {
|
|
2401
|
+
return options[numericIndex - 1].value;
|
|
2402
|
+
}
|
|
2168
2403
|
return "exit";
|
|
2169
2404
|
}
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2405
|
+
rl.pause();
|
|
2406
|
+
input.setRawMode(true);
|
|
2407
|
+
input.resume();
|
|
2408
|
+
return await new Promise((resolve, reject) => {
|
|
2409
|
+
let selectedIndex = 0;
|
|
2410
|
+
let controlBuffer = "";
|
|
2411
|
+
const cleanup = () => {
|
|
2412
|
+
input.removeListener("data", onData);
|
|
2413
|
+
input.setRawMode(false);
|
|
2414
|
+
input.pause();
|
|
2415
|
+
rl.resume();
|
|
2416
|
+
};
|
|
2417
|
+
const render = () => {
|
|
2418
|
+
renderBaseScreen();
|
|
2419
|
+
printSection("Home");
|
|
2420
|
+
console.log(options.map((option, index) => renderHomeOptionLine(option.label, index === selectedIndex)).join("\n"));
|
|
2421
|
+
console.log("");
|
|
2422
|
+
printSoft(CONSOLE_MENU_HINT);
|
|
2423
|
+
};
|
|
2424
|
+
const consumeControlBuffer = () => {
|
|
2425
|
+
while (controlBuffer.length > 0) {
|
|
2426
|
+
const parsed = tryConsumeControlSequence(controlBuffer);
|
|
2427
|
+
if (!parsed) {
|
|
2428
|
+
controlBuffer = "";
|
|
2429
|
+
return;
|
|
2430
|
+
}
|
|
2431
|
+
if (parsed.action === "incomplete") {
|
|
2432
|
+
return;
|
|
2433
|
+
}
|
|
2434
|
+
controlBuffer = controlBuffer.slice(parsed.consumed);
|
|
2435
|
+
if (parsed.action === "move_up") {
|
|
2436
|
+
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : options.length - 1;
|
|
2437
|
+
render();
|
|
2438
|
+
continue;
|
|
2439
|
+
}
|
|
2440
|
+
if (parsed.action === "move_down") {
|
|
2441
|
+
selectedIndex = selectedIndex < options.length - 1 ? selectedIndex + 1 : 0;
|
|
2442
|
+
render();
|
|
2443
|
+
continue;
|
|
2444
|
+
}
|
|
2445
|
+
if (parsed.action === "ignore" || parsed.action === "cycle_approval" || parsed.action === "newline") {
|
|
2446
|
+
continue;
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
};
|
|
2450
|
+
const onData = (chunk) => {
|
|
2451
|
+
const text = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
|
|
2452
|
+
for (const char of Array.from(text)) {
|
|
2453
|
+
if (controlBuffer || char === "\u001b") {
|
|
2454
|
+
controlBuffer += char;
|
|
2455
|
+
consumeControlBuffer();
|
|
2456
|
+
continue;
|
|
2457
|
+
}
|
|
2458
|
+
if (char === "\u0003") {
|
|
2459
|
+
cleanup();
|
|
2460
|
+
reject(createCliExitError());
|
|
2461
|
+
return;
|
|
2462
|
+
}
|
|
2463
|
+
if (char === "\r" || char === "\n") {
|
|
2464
|
+
cleanup();
|
|
2465
|
+
output.write("\n");
|
|
2466
|
+
resolve(options[selectedIndex].value);
|
|
2467
|
+
return;
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
};
|
|
2471
|
+
render();
|
|
2472
|
+
input.on("data", onData);
|
|
2473
|
+
});
|
|
2183
2474
|
}
|
|
2184
2475
|
export async function launchInteractiveCli(options) {
|
|
2185
2476
|
let rl = await createPromptInterface();
|
|
@@ -2188,7 +2479,7 @@ export async function launchInteractiveCli(options) {
|
|
|
2188
2479
|
try {
|
|
2189
2480
|
let config = await loadBridgeConfig();
|
|
2190
2481
|
if (!config) {
|
|
2191
|
-
printHubScreen(null
|
|
2482
|
+
printHubScreen(null);
|
|
2192
2483
|
const setup = await runSetupWizard(rl, options);
|
|
2193
2484
|
config = setup.config;
|
|
2194
2485
|
if (config && setup.openConsole) {
|
|
@@ -2205,8 +2496,8 @@ export async function launchInteractiveCli(options) {
|
|
|
2205
2496
|
await runtimeSession.ensureStarted();
|
|
2206
2497
|
await runtimeSession.waitForReady();
|
|
2207
2498
|
for (;;) {
|
|
2208
|
-
printHubScreen(runtimeSession
|
|
2209
|
-
const choice = await pickHomeChoice(rl, true);
|
|
2499
|
+
printHubScreen(runtimeSession);
|
|
2500
|
+
const choice = await pickHomeChoice(rl, true, () => printHubScreen(runtimeSession));
|
|
2210
2501
|
if (choice === "exit") {
|
|
2211
2502
|
break;
|
|
2212
2503
|
}
|
|
@@ -2303,7 +2594,7 @@ export async function runSetupCommand(options) {
|
|
|
2303
2594
|
const rl = await createPromptInterface();
|
|
2304
2595
|
let runtimeSession = null;
|
|
2305
2596
|
try {
|
|
2306
|
-
printHubScreen(null
|
|
2597
|
+
printHubScreen(null);
|
|
2307
2598
|
const setup = await runSetupWizard(rl, options);
|
|
2308
2599
|
if (setup.config && setup.openConsole) {
|
|
2309
2600
|
runtimeSession = new CliRuntimeSession(setup.config);
|
|
@@ -2326,7 +2617,7 @@ export async function runConsoleCommand(initialPrompt) {
|
|
|
2326
2617
|
const rl = await createPromptInterface();
|
|
2327
2618
|
let runtimeSession = null;
|
|
2328
2619
|
try {
|
|
2329
|
-
printHubScreen(null
|
|
2620
|
+
printHubScreen(null);
|
|
2330
2621
|
let config = await loadBridgeConfig();
|
|
2331
2622
|
if (!config) {
|
|
2332
2623
|
const setup = await runSetupWizard(rl);
|
package/dist/main.js
CHANGED
package/dist/types.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const BRIDGE_CONFIG_VERSION = 1;
|
|
2
|
-
export const BRIDGE_VERSION = "1.1.
|
|
2
|
+
export const BRIDGE_VERSION = "1.1.3";
|
|
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.1.
|
|
27
|
+
console.log("\n[otto-bridge] Welcome to OTTOAI 1.1.3");
|
|
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"], {
|