@leg3ndy/otto-bridge 0.2.0 → 0.4.0
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 +1 -1
- package/dist/executors/native_macos.js +344 -7
- package/dist/types.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { mkdir, readFile, readdir, stat } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
2
5
|
import process from "node:process";
|
|
3
6
|
import { JobCancelledError } from "./shared.js";
|
|
4
7
|
const KNOWN_APPS = [
|
|
5
8
|
{ canonical: "Safari", patterns: [/\bsafari\b/i] },
|
|
6
9
|
{ canonical: "Google Chrome", patterns: [/\bgoogle chrome\b/i, /\bchrome\b/i] },
|
|
7
10
|
{ canonical: "Firefox", patterns: [/\bfirefox\b/i] },
|
|
11
|
+
{ canonical: "Spotify", patterns: [/\bspotify\b/i, /\bspofity\b/i] },
|
|
8
12
|
{ canonical: "Finder", patterns: [/\bfinder\b/i] },
|
|
9
13
|
{ canonical: "Notes", patterns: [/\bnotes\b/i, /\bnotas\b/i] },
|
|
14
|
+
{ canonical: "TextEdit", patterns: [/\btextedit\b/i, /\bbloco de notas\b/i, /\beditor de texto\b/i] },
|
|
10
15
|
{ canonical: "Mail", patterns: [/\bmail\b/i] },
|
|
11
16
|
{ canonical: "Messages", patterns: [/\bmessages\b/i, /\bmensagens\b/i] },
|
|
12
17
|
{ canonical: "Calendar", patterns: [/\bcalendar\b/i, /\bcalend[áa]rio\b/i] },
|
|
@@ -15,6 +20,8 @@ const KNOWN_APPS = [
|
|
|
15
20
|
];
|
|
16
21
|
const KNOWN_SITES = [
|
|
17
22
|
{ url: "https://www.youtube.com", patterns: [/\byoutube\b/i, /\byou tube\b/i] },
|
|
23
|
+
{ url: "https://music.youtube.com", patterns: [/\byoutube music\b/i] },
|
|
24
|
+
{ url: "https://www.instagram.com", patterns: [/\binstagram\b/i, /\binstagram\.com\b/i] },
|
|
18
25
|
{ url: "https://mail.google.com", patterns: [/\bgmail\b/i] },
|
|
19
26
|
{ url: "https://www.google.com", patterns: [/\bgoogle\b/i] },
|
|
20
27
|
{ url: "https://github.com", patterns: [/\bgithub\b/i] },
|
|
@@ -65,6 +72,108 @@ function normalizeUrl(raw) {
|
|
|
65
72
|
}
|
|
66
73
|
return `https://${trimmed.replace(/^\/+/, "")}`;
|
|
67
74
|
}
|
|
75
|
+
function expandUserPath(value) {
|
|
76
|
+
const trimmed = value.trim();
|
|
77
|
+
if (!trimmed) {
|
|
78
|
+
return os.homedir();
|
|
79
|
+
}
|
|
80
|
+
if (trimmed === "~") {
|
|
81
|
+
return os.homedir();
|
|
82
|
+
}
|
|
83
|
+
if (trimmed.startsWith("~/")) {
|
|
84
|
+
return path.join(os.homedir(), trimmed.slice(2));
|
|
85
|
+
}
|
|
86
|
+
if (path.isAbsolute(trimmed)) {
|
|
87
|
+
return trimmed;
|
|
88
|
+
}
|
|
89
|
+
return path.resolve(process.cwd(), trimmed);
|
|
90
|
+
}
|
|
91
|
+
function clipText(value, maxLength) {
|
|
92
|
+
if (value.length <= maxLength) {
|
|
93
|
+
return value;
|
|
94
|
+
}
|
|
95
|
+
return `${value.slice(0, maxLength)}...`;
|
|
96
|
+
}
|
|
97
|
+
function isSafeShellCommand(command) {
|
|
98
|
+
const trimmed = command.trim();
|
|
99
|
+
if (!trimmed) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
const forbiddenPatterns = [
|
|
103
|
+
/(^|[;&|])\s*sudo\b/i,
|
|
104
|
+
/\brm\b/i,
|
|
105
|
+
/\bmv\b/i,
|
|
106
|
+
/\bcp\b/i,
|
|
107
|
+
/\bchmod\b/i,
|
|
108
|
+
/\bchown\b/i,
|
|
109
|
+
/\bshutdown\b/i,
|
|
110
|
+
/\breboot\b/i,
|
|
111
|
+
/\bmkfs\b/i,
|
|
112
|
+
/\bdd\b/i,
|
|
113
|
+
/\bkill(?:all)?\b/i,
|
|
114
|
+
/>/,
|
|
115
|
+
/>>/,
|
|
116
|
+
];
|
|
117
|
+
if (forbiddenPatterns.some((pattern) => pattern.test(trimmed))) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
const normalized = trimmed.replace(/\s+/g, " ");
|
|
121
|
+
const allowedPrefixes = [
|
|
122
|
+
"pwd",
|
|
123
|
+
"ls",
|
|
124
|
+
"cat ",
|
|
125
|
+
"cat",
|
|
126
|
+
"sed ",
|
|
127
|
+
"rg ",
|
|
128
|
+
"find ",
|
|
129
|
+
"git status",
|
|
130
|
+
"git log",
|
|
131
|
+
"git diff",
|
|
132
|
+
"head ",
|
|
133
|
+
"tail ",
|
|
134
|
+
"wc ",
|
|
135
|
+
"stat ",
|
|
136
|
+
"file ",
|
|
137
|
+
"mdls ",
|
|
138
|
+
"whoami",
|
|
139
|
+
"date",
|
|
140
|
+
"uname",
|
|
141
|
+
"python3 --version",
|
|
142
|
+
"node -v",
|
|
143
|
+
"npm -v",
|
|
144
|
+
];
|
|
145
|
+
return allowedPrefixes.some((prefix) => normalized === prefix || normalized.startsWith(`${prefix} `));
|
|
146
|
+
}
|
|
147
|
+
function extractConfirmationOptions(job, actions) {
|
|
148
|
+
const payload = asRecord(job.payload);
|
|
149
|
+
const requested = payload.requires_confirmation === true;
|
|
150
|
+
const longTyping = actions.some((action) => action.type === "type_text" && action.text.trim().length >= 180);
|
|
151
|
+
const confirmationMessage = asString(payload.confirmation_message)
|
|
152
|
+
|| `O Otto quer executar ${actions.length} ação${actions.length === 1 ? "" : "ões"} no seu Mac.`;
|
|
153
|
+
return {
|
|
154
|
+
required: requested || longTyping,
|
|
155
|
+
message: confirmationMessage,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function parseShortcut(shortcut) {
|
|
159
|
+
const parts = shortcut
|
|
160
|
+
.split("+")
|
|
161
|
+
.map((part) => part.trim().toLowerCase())
|
|
162
|
+
.filter(Boolean);
|
|
163
|
+
const key = parts.pop() || "";
|
|
164
|
+
const modifiers = parts.map((part) => {
|
|
165
|
+
if (part === "cmd" || part === "command")
|
|
166
|
+
return "command down";
|
|
167
|
+
if (part === "shift")
|
|
168
|
+
return "shift down";
|
|
169
|
+
if (part === "alt" || part === "option")
|
|
170
|
+
return "option down";
|
|
171
|
+
if (part === "ctrl" || part === "control")
|
|
172
|
+
return "control down";
|
|
173
|
+
return "";
|
|
174
|
+
}).filter(Boolean);
|
|
175
|
+
return { key, modifiers };
|
|
176
|
+
}
|
|
68
177
|
function detectKnownApp(task) {
|
|
69
178
|
for (const app of KNOWN_APPS) {
|
|
70
179
|
if (app.patterns.some((pattern) => pattern.test(task))) {
|
|
@@ -99,7 +208,7 @@ function parseStructuredActions(job) {
|
|
|
99
208
|
if (type === "open_app" || type === "focus_app") {
|
|
100
209
|
const app = asString(action.app) || asString(action.application) || asString(action.name);
|
|
101
210
|
if (app) {
|
|
102
|
-
actions.push({ type
|
|
211
|
+
actions.push({ type, app });
|
|
103
212
|
}
|
|
104
213
|
continue;
|
|
105
214
|
}
|
|
@@ -109,6 +218,47 @@ function parseStructuredActions(job) {
|
|
|
109
218
|
if (url) {
|
|
110
219
|
actions.push({ type: "open_url", url: normalizeUrl(url), app: app || undefined });
|
|
111
220
|
}
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (type === "type_text" || type === "write_text" || type === "keystroke") {
|
|
224
|
+
const text = asString(action.text) || asString(action.content);
|
|
225
|
+
if (text) {
|
|
226
|
+
actions.push({ type: "type_text", text });
|
|
227
|
+
}
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (type === "press_shortcut" || type === "shortcut") {
|
|
231
|
+
const shortcut = asString(action.shortcut) || asString(action.keys);
|
|
232
|
+
if (shortcut) {
|
|
233
|
+
actions.push({ type: "press_shortcut", shortcut });
|
|
234
|
+
}
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (type === "take_screenshot" || type === "screenshot" || type === "screen_capture") {
|
|
238
|
+
const savePath = asString(action.path) || asString(action.save_path);
|
|
239
|
+
actions.push({ type: "take_screenshot", path: savePath || undefined });
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (type === "read_file" || type === "read_local_file") {
|
|
243
|
+
const filePath = asString(action.path);
|
|
244
|
+
if (filePath) {
|
|
245
|
+
const maxChars = typeof action.max_chars === "number" ? Math.max(200, Math.min(12000, action.max_chars)) : undefined;
|
|
246
|
+
actions.push({ type: "read_file", path: filePath, max_chars: maxChars });
|
|
247
|
+
}
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
if (type === "list_files" || type === "ls") {
|
|
251
|
+
const filePath = asString(action.path) || "~";
|
|
252
|
+
const limit = typeof action.limit === "number" ? Math.max(1, Math.min(200, action.limit)) : undefined;
|
|
253
|
+
actions.push({ type: "list_files", path: filePath, limit });
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (type === "run_shell" || type === "shell" || type === "terminal") {
|
|
257
|
+
const command = asString(action.command) || asString(action.cmd);
|
|
258
|
+
const cwd = asString(action.cwd);
|
|
259
|
+
if (command) {
|
|
260
|
+
actions.push({ type: "run_shell", command, cwd: cwd || undefined });
|
|
261
|
+
}
|
|
112
262
|
}
|
|
113
263
|
}
|
|
114
264
|
return actions;
|
|
@@ -151,7 +301,18 @@ export class NativeMacOSJobExecutor {
|
|
|
151
301
|
throw new Error("Otto Bridge native-macos could not derive a supported local action from this request");
|
|
152
302
|
}
|
|
153
303
|
await reporter.accepted();
|
|
304
|
+
const confirmation = extractConfirmationOptions(job, actions);
|
|
305
|
+
if (confirmation.required) {
|
|
306
|
+
const decision = await reporter.confirmRequired(confirmation.message, {
|
|
307
|
+
actions,
|
|
308
|
+
executor: "native-macos",
|
|
309
|
+
});
|
|
310
|
+
if (decision.action !== "approve") {
|
|
311
|
+
throw new JobCancelledError(job.job_id);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
154
314
|
try {
|
|
315
|
+
const completionNotes = [];
|
|
155
316
|
for (let index = 0; index < actions.length; index += 1) {
|
|
156
317
|
this.assertNotCancelled(job.job_id);
|
|
157
318
|
const action = actions[index];
|
|
@@ -161,12 +322,53 @@ export class NativeMacOSJobExecutor {
|
|
|
161
322
|
await this.openApp(action.app);
|
|
162
323
|
continue;
|
|
163
324
|
}
|
|
325
|
+
if (action.type === "focus_app") {
|
|
326
|
+
await reporter.progress(progressPercent, `Trazendo ${action.app} para frente`);
|
|
327
|
+
await this.focusApp(action.app);
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
if (action.type === "press_shortcut") {
|
|
331
|
+
await reporter.progress(progressPercent, `Enviando atalho ${action.shortcut}`);
|
|
332
|
+
await this.pressShortcut(action.shortcut);
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
if (action.type === "type_text") {
|
|
336
|
+
await reporter.progress(progressPercent, "Digitando texto no app ativo");
|
|
337
|
+
await this.typeText(action.text);
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
if (action.type === "take_screenshot") {
|
|
341
|
+
await reporter.progress(progressPercent, "Capturando screenshot do Mac");
|
|
342
|
+
const screenshotPath = await this.takeScreenshot(action.path);
|
|
343
|
+
completionNotes.push(`Screenshot salvo em ${screenshotPath}`);
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (action.type === "read_file") {
|
|
347
|
+
await reporter.progress(progressPercent, `Lendo ${action.path}`);
|
|
348
|
+
const fileContent = await this.readLocalFile(action.path, action.max_chars);
|
|
349
|
+
completionNotes.push(`Conteudo de ${action.path}:\n${fileContent}`);
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
if (action.type === "list_files") {
|
|
353
|
+
await reporter.progress(progressPercent, `Listando arquivos em ${action.path}`);
|
|
354
|
+
const listing = await this.listLocalFiles(action.path, action.limit);
|
|
355
|
+
completionNotes.push(`Arquivos em ${action.path}:\n${listing}`);
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
if (action.type === "run_shell") {
|
|
359
|
+
await reporter.progress(progressPercent, `Rodando comando local: ${action.command}`);
|
|
360
|
+
const shellOutput = await this.runShellCommand(action.command, action.cwd);
|
|
361
|
+
completionNotes.push(`Saida de \`${action.command}\`:\n${shellOutput}`);
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
164
364
|
await reporter.progress(progressPercent, `Abrindo ${action.url}${action.app ? ` em ${action.app}` : ""}`);
|
|
165
365
|
await this.openUrl(action.url, action.app);
|
|
166
366
|
}
|
|
167
|
-
const summary =
|
|
168
|
-
?
|
|
169
|
-
:
|
|
367
|
+
const summary = completionNotes.length > 0
|
|
368
|
+
? completionNotes.join("\n\n")
|
|
369
|
+
: (actions.length === 1
|
|
370
|
+
? this.describeAction(actions[0])
|
|
371
|
+
: `${actions.length} ações executadas no macOS`);
|
|
170
372
|
await reporter.completed({
|
|
171
373
|
executor: "native-macos",
|
|
172
374
|
summary,
|
|
@@ -191,31 +393,162 @@ export class NativeMacOSJobExecutor {
|
|
|
191
393
|
}
|
|
192
394
|
async openApp(app) {
|
|
193
395
|
await this.runCommand("open", ["-a", app]);
|
|
194
|
-
await this.
|
|
396
|
+
await this.focusApp(app);
|
|
195
397
|
}
|
|
196
398
|
async openUrl(url, app) {
|
|
197
399
|
if (app) {
|
|
198
400
|
await this.runCommand("open", ["-a", app, url]);
|
|
199
|
-
await this.
|
|
401
|
+
await this.focusApp(app);
|
|
200
402
|
return;
|
|
201
403
|
}
|
|
202
404
|
await this.runCommand("open", [url]);
|
|
203
405
|
}
|
|
406
|
+
async focusApp(app) {
|
|
407
|
+
await this.runCommand("osascript", ["-e", `tell application "${escapeAppleScript(app)}" to activate`]);
|
|
408
|
+
}
|
|
409
|
+
async pressShortcut(shortcut) {
|
|
410
|
+
const { key, modifiers } = parseShortcut(shortcut);
|
|
411
|
+
if (!key) {
|
|
412
|
+
throw new Error(`Invalid shortcut: ${shortcut}`);
|
|
413
|
+
}
|
|
414
|
+
const namedKeyCodes = {
|
|
415
|
+
return: 36,
|
|
416
|
+
enter: 36,
|
|
417
|
+
tab: 48,
|
|
418
|
+
space: 49,
|
|
419
|
+
escape: 53,
|
|
420
|
+
esc: 53,
|
|
421
|
+
left: 123,
|
|
422
|
+
right: 124,
|
|
423
|
+
down: 125,
|
|
424
|
+
up: 126,
|
|
425
|
+
};
|
|
426
|
+
const usingClause = modifiers.length > 0 ? ` using {${modifiers.join(", ")}}` : "";
|
|
427
|
+
if (namedKeyCodes[key] !== undefined) {
|
|
428
|
+
await this.runCommand("osascript", [
|
|
429
|
+
"-e",
|
|
430
|
+
`tell application "System Events" to key code ${namedKeyCodes[key]}${usingClause}`,
|
|
431
|
+
]);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
await this.runCommand("osascript", [
|
|
435
|
+
"-e",
|
|
436
|
+
`tell application "System Events" to keystroke "${escapeAppleScript(key)}"${usingClause}`,
|
|
437
|
+
]);
|
|
438
|
+
}
|
|
439
|
+
async typeText(text) {
|
|
440
|
+
const previousClipboard = await this.readClipboardText();
|
|
441
|
+
try {
|
|
442
|
+
await this.writeClipboardText(text);
|
|
443
|
+
await this.pressShortcut("cmd+v");
|
|
444
|
+
}
|
|
445
|
+
finally {
|
|
446
|
+
if (previousClipboard !== null) {
|
|
447
|
+
await this.writeClipboardText(previousClipboard).catch(() => undefined);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
async takeScreenshot(targetPath) {
|
|
452
|
+
const artifactsDir = path.join(os.homedir(), ".otto-bridge", "artifacts");
|
|
453
|
+
await mkdir(artifactsDir, { recursive: true });
|
|
454
|
+
const screenshotPath = targetPath
|
|
455
|
+
? expandUserPath(targetPath)
|
|
456
|
+
: path.join(artifactsDir, `screenshot-${Date.now()}.png`);
|
|
457
|
+
await this.runCommand("screencapture", ["-x", screenshotPath]);
|
|
458
|
+
return screenshotPath;
|
|
459
|
+
}
|
|
460
|
+
async readLocalFile(filePath, maxChars = 4000) {
|
|
461
|
+
const resolved = expandUserPath(filePath);
|
|
462
|
+
const content = await readFile(resolved, "utf8");
|
|
463
|
+
return clipText(content.trim() || "(arquivo vazio)", maxChars);
|
|
464
|
+
}
|
|
465
|
+
async listLocalFiles(directoryPath, limit = 40) {
|
|
466
|
+
const resolved = expandUserPath(directoryPath);
|
|
467
|
+
const entries = await readdir(resolved, { withFileTypes: true });
|
|
468
|
+
const items = await Promise.all(entries.slice(0, limit).map(async (entry) => {
|
|
469
|
+
const entryPath = path.join(resolved, entry.name);
|
|
470
|
+
let suffix = "";
|
|
471
|
+
try {
|
|
472
|
+
const entryStat = await stat(entryPath);
|
|
473
|
+
suffix = entry.isDirectory() ? "/" : ` (${entryStat.size} bytes)`;
|
|
474
|
+
}
|
|
475
|
+
catch {
|
|
476
|
+
suffix = entry.isDirectory() ? "/" : "";
|
|
477
|
+
}
|
|
478
|
+
return `${entry.name}${suffix}`;
|
|
479
|
+
}));
|
|
480
|
+
return items.length > 0 ? items.join("\n") : "(pasta vazia)";
|
|
481
|
+
}
|
|
482
|
+
async runShellCommand(command, cwd) {
|
|
483
|
+
if (!isSafeShellCommand(command)) {
|
|
484
|
+
throw new Error("Otto Bridge permite apenas shell de consulta no momento. Use comandos de leitura como pwd, ls, cat, rg, find ou git status.");
|
|
485
|
+
}
|
|
486
|
+
const resolvedCwd = cwd ? expandUserPath(cwd) : process.cwd();
|
|
487
|
+
const { stdout, stderr } = await this.runCommandCapture("/bin/zsh", ["-lc", command], {
|
|
488
|
+
cwd: resolvedCwd,
|
|
489
|
+
});
|
|
490
|
+
const combined = [stdout.trim(), stderr.trim()].filter(Boolean).join("\n");
|
|
491
|
+
return clipText(combined || "(sem saida)", 4000);
|
|
492
|
+
}
|
|
493
|
+
async readClipboardText() {
|
|
494
|
+
try {
|
|
495
|
+
const { stdout } = await this.runCommandCapture("pbpaste", []);
|
|
496
|
+
return stdout;
|
|
497
|
+
}
|
|
498
|
+
catch {
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
async writeClipboardText(text) {
|
|
503
|
+
await this.runCommandCapture("pbcopy", [], { stdin: text });
|
|
504
|
+
}
|
|
204
505
|
describeAction(action) {
|
|
205
506
|
if (action.type === "open_app") {
|
|
206
507
|
return `${action.app} foi aberto no macOS`;
|
|
207
508
|
}
|
|
509
|
+
if (action.type === "focus_app") {
|
|
510
|
+
return `${action.app} ficou em foco no macOS`;
|
|
511
|
+
}
|
|
512
|
+
if (action.type === "press_shortcut") {
|
|
513
|
+
return `Atalho ${action.shortcut} executado no macOS`;
|
|
514
|
+
}
|
|
515
|
+
if (action.type === "type_text") {
|
|
516
|
+
return "Texto digitado no aplicativo ativo";
|
|
517
|
+
}
|
|
518
|
+
if (action.type === "take_screenshot") {
|
|
519
|
+
return "Screenshot capturado no macOS";
|
|
520
|
+
}
|
|
521
|
+
if (action.type === "read_file") {
|
|
522
|
+
return `${action.path} foi lido no macOS`;
|
|
523
|
+
}
|
|
524
|
+
if (action.type === "list_files") {
|
|
525
|
+
return `Arquivos listados em ${action.path}`;
|
|
526
|
+
}
|
|
527
|
+
if (action.type === "run_shell") {
|
|
528
|
+
return `Comando ${action.command} executado no macOS`;
|
|
529
|
+
}
|
|
208
530
|
return `${action.url} foi aberto${action.app ? ` em ${action.app}` : ""}`;
|
|
209
531
|
}
|
|
210
532
|
async runCommand(command, args) {
|
|
533
|
+
await this.runCommandCapture(command, args);
|
|
534
|
+
}
|
|
535
|
+
async runCommandCapture(command, args, options) {
|
|
211
536
|
const child = spawn(command, args, {
|
|
212
|
-
|
|
537
|
+
cwd: options?.cwd,
|
|
538
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
213
539
|
});
|
|
214
540
|
this.activeChild = child;
|
|
215
541
|
try {
|
|
216
542
|
const { stdout, stderr } = await new Promise((resolve, reject) => {
|
|
217
543
|
let stdout = "";
|
|
218
544
|
let stderr = "";
|
|
545
|
+
if (options?.stdin !== undefined) {
|
|
546
|
+
child.stdin.write(options.stdin);
|
|
547
|
+
child.stdin.end();
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
child.stdin.end();
|
|
551
|
+
}
|
|
219
552
|
child.stdout.on("data", (chunk) => {
|
|
220
553
|
stdout += String(chunk);
|
|
221
554
|
});
|
|
@@ -241,9 +574,13 @@ export class NativeMacOSJobExecutor {
|
|
|
241
574
|
if (stdoutText) {
|
|
242
575
|
console.log(`[otto-bridge] ${command} stdout=${stdoutText}`);
|
|
243
576
|
}
|
|
577
|
+
return { stdout, stderr };
|
|
244
578
|
}
|
|
245
579
|
catch (error) {
|
|
246
580
|
const detail = error instanceof Error ? error.message : String(error);
|
|
581
|
+
if (detail.includes("System Events") || detail.includes("(1002)")) {
|
|
582
|
+
throw new Error("macOS bloqueou o controle de teclado do Otto Bridge. Abra Ajustes do Sistema > Privacidade e Seguranca > Acessibilidade e autorize o terminal ou app que esta rodando o otto-bridge.");
|
|
583
|
+
}
|
|
247
584
|
throw new Error(detail);
|
|
248
585
|
}
|
|
249
586
|
finally {
|
package/dist/types.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const BRIDGE_CONFIG_VERSION = 1;
|
|
2
|
-
export const BRIDGE_VERSION = "0.
|
|
2
|
+
export const BRIDGE_VERSION = "0.4.0";
|
|
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;
|