@leg3ndy/otto-bridge 0.1.3 → 0.3.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 CHANGED
@@ -5,7 +5,7 @@ Companion local do Otto para:
5
5
  - reivindicar um codigo de pareamento gerado pela web
6
6
  - armazenar o `device_token` do dispositivo
7
7
  - manter um WebSocket persistente com o backend
8
- - executar jobs locais com `mock` ou `clawd-cursor`
8
+ - executar jobs locais com executor proprio do Otto no macOS, `mock` ou `clawd-cursor`
9
9
 
10
10
  ## Guia de uso
11
11
 
@@ -31,7 +31,7 @@ Enquanto o pacote nao estiver publicado, voce pode gerar um tarball local:
31
31
 
32
32
  ```bash
33
33
  npm pack
34
- npm install -g ./leg3ndy-otto-bridge-0.1.3.tgz
34
+ npm install -g ./leg3ndy-otto-bridge-0.3.0.tgz
35
35
  ```
36
36
 
37
37
  ## Publicacao
@@ -66,7 +66,7 @@ otto-bridge --help
66
66
  ### Parear o dispositivo
67
67
 
68
68
  ```bash
69
- otto-bridge pair --api http://localhost:8000 --code ABC123 --executor clawd-cursor
69
+ otto-bridge pair --api http://localhost:8000 --code ABC123
70
70
  ```
71
71
 
72
72
  Opcoes suportadas:
@@ -74,18 +74,32 @@ Opcoes suportadas:
74
74
  - `--name`: nome amigavel do dispositivo
75
75
  - `--timeout-seconds`: limite de espera pela aprovacao web
76
76
  - `--poll-interval-ms`: intervalo de polling do claim
77
- - `--executor`: `mock` ou `clawd-cursor`
77
+ - `--executor`: `native-macos`, `mock` ou `clawd-cursor`
78
78
  - `--clawd-url`: base URL da API local do `clawd-cursor`
79
79
  - `--clawd-poll-interval-ms`: polling do status/logs do `clawd-cursor`
80
80
 
81
+ No macOS, o caminho recomendado agora e o executor nativo do Otto Bridge. Se nenhum `--executor` for informado, o `pair` usa `native-macos` por padrao no Mac.
82
+
81
83
  ### Rodar o bridge
82
84
 
83
85
  ```bash
84
- otto-bridge run --executor clawd-cursor --clawd-url http://127.0.0.1:3847
86
+ otto-bridge run
85
87
  ```
86
88
 
87
89
  Se o executor estiver salvo no `config.json`, o `run` usa essa configuracao por padrao.
88
90
 
91
+ Para forcar o executor nativo no macOS sem reparar:
92
+
93
+ ```bash
94
+ otto-bridge run --executor native-macos
95
+ ```
96
+
97
+ O adapter `clawd-cursor` continua disponivel como override opcional:
98
+
99
+ ```bash
100
+ otto-bridge run --executor clawd-cursor --clawd-url http://127.0.0.1:3847
101
+ ```
102
+
89
103
  ### Ver estado local
90
104
 
91
105
  ```bash
@@ -136,7 +150,7 @@ otto-bridge unpair
136
150
 
137
151
  ## Payload esperado para jobs desktop
138
152
 
139
- O adapter do `clawd-cursor` procura a tarefa em uma destas chaves, nessa ordem:
153
+ Os executores locais do Otto Bridge procuram a tarefa em uma destas chaves, nessa ordem:
140
154
 
141
155
  - `task`
142
156
  - `prompt`
@@ -144,6 +158,20 @@ O adapter do `clawd-cursor` procura a tarefa em uma destas chaves, nessa ordem:
144
158
  - `instructions`
145
159
  - `message`
146
160
 
161
+ O executor nativo do macOS tambem aceita payload estruturado em `actions[]`, por exemplo:
162
+
163
+ ```json
164
+ {
165
+ "job_type": "desktop_actions",
166
+ "payload": {
167
+ "actions": [
168
+ { "type": "open_app", "app": "Safari" },
169
+ { "type": "open_url", "url": "https://www.youtube.com", "app": "Safari" }
170
+ ]
171
+ }
172
+ }
173
+ ```
174
+
147
175
  Exemplo:
148
176
 
149
177
  ```json
package/dist/config.js CHANGED
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
3
3
  import { homedir, hostname, platform, arch } from "node:os";
4
4
  import path from "node:path";
5
- import { BRIDGE_CONFIG_VERSION, BRIDGE_VERSION, DEFAULT_CLAWD_CURSOR_BASE_URL, DEFAULT_CLAWD_CURSOR_POLL_INTERVAL_MS, DEFAULT_API_BASE_URL, DEFAULT_EXECUTOR_TYPE, } from "./types.js";
5
+ import { BRIDGE_CONFIG_VERSION, BRIDGE_VERSION, DEFAULT_CLAWD_CURSOR_BASE_URL, DEFAULT_CLAWD_CURSOR_POLL_INTERVAL_MS, DEFAULT_API_BASE_URL, } from "./types.js";
6
6
  function sanitizeApiBaseUrl(value) {
7
7
  const raw = String(value || DEFAULT_API_BASE_URL).trim();
8
8
  if (!raw) {
@@ -10,8 +10,14 @@ function sanitizeApiBaseUrl(value) {
10
10
  }
11
11
  return raw.replace(/\/+$/, "");
12
12
  }
13
+ function resolveDefaultExecutorType() {
14
+ return platform() === "darwin" ? "native-macos" : "mock";
15
+ }
13
16
  function sanitizeExecutorType(value) {
14
- return value === "clawd-cursor" ? "clawd-cursor" : DEFAULT_EXECUTOR_TYPE;
17
+ if (value === "clawd-cursor" || value === "native-macos" || value === "mock") {
18
+ return value;
19
+ }
20
+ return resolveDefaultExecutorType();
15
21
  }
16
22
  function sanitizeClawdCursorBaseUrl(value) {
17
23
  const raw = String(value || DEFAULT_CLAWD_CURSOR_BASE_URL).trim();
@@ -27,6 +33,16 @@ function sanitizePollIntervalMs(value, fallback = DEFAULT_CLAWD_CURSOR_POLL_INTE
27
33
  }
28
34
  return Math.max(250, Math.floor(parsed));
29
35
  }
36
+ function migrateLegacyExecutor(current) {
37
+ if (platform() === "darwin"
38
+ && current?.type === "clawd-cursor"
39
+ && sanitizeClawdCursorBaseUrl(current.baseUrl) === DEFAULT_CLAWD_CURSOR_BASE_URL) {
40
+ // Old macOS pairings defaulted to clawd-cursor while the bridge was still bootstrapping.
41
+ // Newer builds should run Otto's native executor by default without forcing users to re-pair.
42
+ return { type: "native-macos" };
43
+ }
44
+ return current || null;
45
+ }
30
46
  export function getBridgeHomeDir() {
31
47
  const custom = String(process.env.OTTO_BRIDGE_HOME || "").trim();
32
48
  return custom || path.join(homedir(), ".otto-bridge");
@@ -51,7 +67,7 @@ export async function loadBridgeConfig() {
51
67
  ...parsed,
52
68
  apiBaseUrl: sanitizeApiBaseUrl(parsed.apiBaseUrl),
53
69
  wsUrl: buildWebSocketUrl(parsed.apiBaseUrl),
54
- executor: resolveExecutorConfig(undefined, parsed.executor),
70
+ executor: resolveExecutorConfig(undefined, migrateLegacyExecutor(parsed.executor)),
55
71
  };
56
72
  }
57
73
  catch {
@@ -83,6 +99,9 @@ export function resolveExecutorConfig(overrides, current) {
83
99
  ?? process.env.OTTO_CLAWD_POLL_INTERVAL_MS),
84
100
  };
85
101
  }
102
+ if (type === "native-macos") {
103
+ return { type: "native-macos" };
104
+ }
86
105
  return { type: "mock" };
87
106
  }
88
107
  export function buildWebSocketUrl(apiBaseUrl) {
@@ -0,0 +1,400 @@
1
+ import { spawn } from "node:child_process";
2
+ import process from "node:process";
3
+ import { JobCancelledError } from "./shared.js";
4
+ const KNOWN_APPS = [
5
+ { canonical: "Safari", patterns: [/\bsafari\b/i] },
6
+ { canonical: "Google Chrome", patterns: [/\bgoogle chrome\b/i, /\bchrome\b/i] },
7
+ { canonical: "Firefox", patterns: [/\bfirefox\b/i] },
8
+ { canonical: "Spotify", patterns: [/\bspotify\b/i, /\bspofity\b/i] },
9
+ { canonical: "Finder", patterns: [/\bfinder\b/i] },
10
+ { canonical: "Notes", patterns: [/\bnotes\b/i, /\bnotas\b/i] },
11
+ { canonical: "TextEdit", patterns: [/\btextedit\b/i, /\bbloco de notas\b/i, /\beditor de texto\b/i] },
12
+ { canonical: "Mail", patterns: [/\bmail\b/i] },
13
+ { canonical: "Messages", patterns: [/\bmessages\b/i, /\bmensagens\b/i] },
14
+ { canonical: "Calendar", patterns: [/\bcalendar\b/i, /\bcalend[áa]rio\b/i] },
15
+ { canonical: "Terminal", patterns: [/\bterminal\b/i] },
16
+ { canonical: "System Settings", patterns: [/\bsystem settings\b/i, /\bajustes do sistema\b/i, /\bconfigura[cç][õo]es do sistema\b/i] },
17
+ ];
18
+ const KNOWN_SITES = [
19
+ { url: "https://www.youtube.com", patterns: [/\byoutube\b/i, /\byou tube\b/i] },
20
+ { url: "https://www.instagram.com", patterns: [/\binstagram\b/i, /\binstagram\.com\b/i] },
21
+ { url: "https://mail.google.com", patterns: [/\bgmail\b/i] },
22
+ { url: "https://www.google.com", patterns: [/\bgoogle\b/i] },
23
+ { url: "https://github.com", patterns: [/\bgithub\b/i] },
24
+ { url: "https://chat.openai.com", patterns: [/\bchatgpt\b/i] },
25
+ { url: "https://web.whatsapp.com", patterns: [/\bwhatsapp\b/i] },
26
+ { url: "https://x.com", patterns: [/\bx\.com\b/i, /\btwitter\b/i, /\bxis\b/i] },
27
+ ];
28
+ function asRecord(value) {
29
+ return value && typeof value === "object" ? value : {};
30
+ }
31
+ function asString(value) {
32
+ if (typeof value !== "string") {
33
+ return null;
34
+ }
35
+ const trimmed = value.trim();
36
+ return trimmed || null;
37
+ }
38
+ function normalizeText(value) {
39
+ return value
40
+ .normalize("NFD")
41
+ .replace(/\p{Diacritic}/gu, "")
42
+ .toLowerCase();
43
+ }
44
+ function escapeAppleScript(value) {
45
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
46
+ }
47
+ function extractTaskText(job) {
48
+ const payload = asRecord(job.payload);
49
+ const candidates = [
50
+ payload.task,
51
+ payload.prompt,
52
+ payload.instruction,
53
+ payload.instructions,
54
+ payload.message,
55
+ ];
56
+ for (const candidate of candidates) {
57
+ const text = asString(candidate);
58
+ if (text) {
59
+ return text;
60
+ }
61
+ }
62
+ return `Execute Otto job ${job.job_type}`;
63
+ }
64
+ function normalizeUrl(raw) {
65
+ const trimmed = raw.trim();
66
+ if (/^https?:\/\//i.test(trimmed)) {
67
+ return trimmed;
68
+ }
69
+ return `https://${trimmed.replace(/^\/+/, "")}`;
70
+ }
71
+ function splitTextForTyping(value) {
72
+ const parts = [];
73
+ for (const line of value.split("\n")) {
74
+ if (line.length <= 220) {
75
+ parts.push(line);
76
+ continue;
77
+ }
78
+ for (let index = 0; index < line.length; index += 220) {
79
+ parts.push(line.slice(index, index + 220));
80
+ }
81
+ }
82
+ return parts;
83
+ }
84
+ function extractConfirmationOptions(job, actions) {
85
+ const payload = asRecord(job.payload);
86
+ const requested = payload.requires_confirmation === true;
87
+ const longTyping = actions.some((action) => action.type === "type_text" && action.text.trim().length >= 180);
88
+ const confirmationMessage = asString(payload.confirmation_message)
89
+ || `O Otto quer executar ${actions.length} ação${actions.length === 1 ? "" : "ões"} no seu Mac.`;
90
+ return {
91
+ required: requested || longTyping,
92
+ message: confirmationMessage,
93
+ };
94
+ }
95
+ function parseShortcut(shortcut) {
96
+ const parts = shortcut
97
+ .split("+")
98
+ .map((part) => part.trim().toLowerCase())
99
+ .filter(Boolean);
100
+ const key = parts.pop() || "";
101
+ const modifiers = parts.map((part) => {
102
+ if (part === "cmd" || part === "command")
103
+ return "command down";
104
+ if (part === "shift")
105
+ return "shift down";
106
+ if (part === "alt" || part === "option")
107
+ return "option down";
108
+ if (part === "ctrl" || part === "control")
109
+ return "control down";
110
+ return "";
111
+ }).filter(Boolean);
112
+ return { key, modifiers };
113
+ }
114
+ function detectKnownApp(task) {
115
+ for (const app of KNOWN_APPS) {
116
+ if (app.patterns.some((pattern) => pattern.test(task))) {
117
+ return app.canonical;
118
+ }
119
+ }
120
+ return null;
121
+ }
122
+ function detectUrl(task) {
123
+ const explicitMatch = task.match(/https?:\/\/[^\s]+/i);
124
+ if (explicitMatch?.[0]) {
125
+ return normalizeUrl(explicitMatch[0]);
126
+ }
127
+ const normalized = normalizeText(task);
128
+ for (const site of KNOWN_SITES) {
129
+ if (site.patterns.some((pattern) => pattern.test(normalized))) {
130
+ return site.url;
131
+ }
132
+ }
133
+ return null;
134
+ }
135
+ function parseStructuredActions(job) {
136
+ const payload = asRecord(job.payload);
137
+ const rawActions = Array.isArray(payload.actions) ? payload.actions : [];
138
+ const actions = [];
139
+ for (const rawAction of rawActions) {
140
+ const action = asRecord(rawAction);
141
+ const type = asString(action.type);
142
+ if (!type) {
143
+ continue;
144
+ }
145
+ if (type === "open_app" || type === "focus_app") {
146
+ const app = asString(action.app) || asString(action.application) || asString(action.name);
147
+ if (app) {
148
+ actions.push({ type, app });
149
+ }
150
+ continue;
151
+ }
152
+ if (type === "open_url") {
153
+ const url = asString(action.url) || asString(action.href);
154
+ const app = asString(action.app) || asString(action.application);
155
+ if (url) {
156
+ actions.push({ type: "open_url", url: normalizeUrl(url), app: app || undefined });
157
+ }
158
+ continue;
159
+ }
160
+ if (type === "type_text" || type === "write_text" || type === "keystroke") {
161
+ const text = asString(action.text) || asString(action.content);
162
+ if (text) {
163
+ actions.push({ type: "type_text", text });
164
+ }
165
+ continue;
166
+ }
167
+ if (type === "press_shortcut" || type === "shortcut") {
168
+ const shortcut = asString(action.shortcut) || asString(action.keys);
169
+ if (shortcut) {
170
+ actions.push({ type: "press_shortcut", shortcut });
171
+ }
172
+ }
173
+ }
174
+ return actions;
175
+ }
176
+ function deriveActionsFromText(job) {
177
+ const task = extractTaskText(job);
178
+ const detectedApp = detectKnownApp(task);
179
+ const detectedUrl = detectUrl(task);
180
+ if (detectedUrl) {
181
+ return [{
182
+ type: "open_url",
183
+ url: detectedUrl,
184
+ app: detectedApp || undefined,
185
+ }];
186
+ }
187
+ if (detectedApp) {
188
+ return [{
189
+ type: "open_app",
190
+ app: detectedApp,
191
+ }];
192
+ }
193
+ return [];
194
+ }
195
+ function extractActions(job) {
196
+ const structured = parseStructuredActions(job);
197
+ if (structured.length > 0) {
198
+ return structured;
199
+ }
200
+ return deriveActionsFromText(job);
201
+ }
202
+ export class NativeMacOSJobExecutor {
203
+ cancelledJobs = new Set();
204
+ activeChild = null;
205
+ async run(job, reporter) {
206
+ if (process.platform !== "darwin") {
207
+ throw new Error("The native-macos executor only runs on macOS");
208
+ }
209
+ const actions = extractActions(job);
210
+ if (actions.length === 0) {
211
+ throw new Error("Otto Bridge native-macos could not derive a supported local action from this request");
212
+ }
213
+ await reporter.accepted();
214
+ const confirmation = extractConfirmationOptions(job, actions);
215
+ if (confirmation.required) {
216
+ const decision = await reporter.confirmRequired(confirmation.message, {
217
+ actions,
218
+ executor: "native-macos",
219
+ });
220
+ if (decision.action !== "approve") {
221
+ throw new JobCancelledError(job.job_id);
222
+ }
223
+ }
224
+ try {
225
+ for (let index = 0; index < actions.length; index += 1) {
226
+ this.assertNotCancelled(job.job_id);
227
+ const action = actions[index];
228
+ const progressPercent = Math.max(10, Math.round(((index + 1) / actions.length) * 100));
229
+ if (action.type === "open_app") {
230
+ await reporter.progress(progressPercent, `Abrindo ${action.app} no macOS`);
231
+ await this.openApp(action.app);
232
+ continue;
233
+ }
234
+ if (action.type === "focus_app") {
235
+ await reporter.progress(progressPercent, `Trazendo ${action.app} para frente`);
236
+ await this.focusApp(action.app);
237
+ continue;
238
+ }
239
+ if (action.type === "press_shortcut") {
240
+ await reporter.progress(progressPercent, `Enviando atalho ${action.shortcut}`);
241
+ await this.pressShortcut(action.shortcut);
242
+ continue;
243
+ }
244
+ if (action.type === "type_text") {
245
+ await reporter.progress(progressPercent, "Digitando texto no app ativo");
246
+ await this.typeText(action.text);
247
+ continue;
248
+ }
249
+ await reporter.progress(progressPercent, `Abrindo ${action.url}${action.app ? ` em ${action.app}` : ""}`);
250
+ await this.openUrl(action.url, action.app);
251
+ }
252
+ const summary = actions.length === 1
253
+ ? this.describeAction(actions[0])
254
+ : `${actions.length} ações executadas no macOS`;
255
+ await reporter.completed({
256
+ executor: "native-macos",
257
+ summary,
258
+ actions,
259
+ });
260
+ }
261
+ finally {
262
+ this.cancelledJobs.delete(job.job_id);
263
+ }
264
+ }
265
+ async cancel(jobId) {
266
+ this.cancelledJobs.add(jobId);
267
+ if (this.activeChild) {
268
+ this.activeChild.kill("SIGTERM");
269
+ this.activeChild = null;
270
+ }
271
+ }
272
+ assertNotCancelled(jobId) {
273
+ if (this.cancelledJobs.has(jobId)) {
274
+ throw new JobCancelledError(jobId);
275
+ }
276
+ }
277
+ async openApp(app) {
278
+ await this.runCommand("open", ["-a", app]);
279
+ await this.focusApp(app);
280
+ }
281
+ async openUrl(url, app) {
282
+ if (app) {
283
+ await this.runCommand("open", ["-a", app, url]);
284
+ await this.focusApp(app);
285
+ return;
286
+ }
287
+ await this.runCommand("open", [url]);
288
+ }
289
+ async focusApp(app) {
290
+ await this.runCommand("osascript", ["-e", `tell application "${escapeAppleScript(app)}" to activate`]);
291
+ }
292
+ async pressShortcut(shortcut) {
293
+ const { key, modifiers } = parseShortcut(shortcut);
294
+ if (!key) {
295
+ throw new Error(`Invalid shortcut: ${shortcut}`);
296
+ }
297
+ const namedKeyCodes = {
298
+ return: 36,
299
+ enter: 36,
300
+ tab: 48,
301
+ space: 49,
302
+ escape: 53,
303
+ esc: 53,
304
+ left: 123,
305
+ right: 124,
306
+ down: 125,
307
+ up: 126,
308
+ };
309
+ const usingClause = modifiers.length > 0 ? ` using {${modifiers.join(", ")}}` : "";
310
+ if (namedKeyCodes[key] !== undefined) {
311
+ await this.runCommand("osascript", [
312
+ "-e",
313
+ `tell application "System Events" to key code ${namedKeyCodes[key]}${usingClause}`,
314
+ ]);
315
+ return;
316
+ }
317
+ await this.runCommand("osascript", [
318
+ "-e",
319
+ `tell application "System Events" to keystroke "${escapeAppleScript(key)}"${usingClause}`,
320
+ ]);
321
+ }
322
+ async typeText(text) {
323
+ const chunks = splitTextForTyping(text);
324
+ for (let index = 0; index < chunks.length; index += 1) {
325
+ const chunk = chunks[index];
326
+ if (chunk) {
327
+ await this.runCommand("osascript", [
328
+ "-e",
329
+ `tell application "System Events" to keystroke "${escapeAppleScript(chunk)}"`,
330
+ ]);
331
+ }
332
+ if (index < chunks.length - 1) {
333
+ await this.runCommand("osascript", [
334
+ "-e",
335
+ 'tell application "System Events" to key code 36',
336
+ ]);
337
+ }
338
+ }
339
+ }
340
+ describeAction(action) {
341
+ if (action.type === "open_app") {
342
+ return `${action.app} foi aberto no macOS`;
343
+ }
344
+ if (action.type === "focus_app") {
345
+ return `${action.app} ficou em foco no macOS`;
346
+ }
347
+ if (action.type === "press_shortcut") {
348
+ return `Atalho ${action.shortcut} executado no macOS`;
349
+ }
350
+ if (action.type === "type_text") {
351
+ return "Texto digitado no aplicativo ativo";
352
+ }
353
+ return `${action.url} foi aberto${action.app ? ` em ${action.app}` : ""}`;
354
+ }
355
+ async runCommand(command, args) {
356
+ const child = spawn(command, args, {
357
+ stdio: ["ignore", "pipe", "pipe"],
358
+ });
359
+ this.activeChild = child;
360
+ try {
361
+ const { stdout, stderr } = await new Promise((resolve, reject) => {
362
+ let stdout = "";
363
+ let stderr = "";
364
+ child.stdout.on("data", (chunk) => {
365
+ stdout += String(chunk);
366
+ });
367
+ child.stderr.on("data", (chunk) => {
368
+ stderr += String(chunk);
369
+ });
370
+ child.on("error", (error) => {
371
+ reject(error);
372
+ });
373
+ child.on("close", (code) => {
374
+ if (code === 0) {
375
+ resolve({ stdout, stderr });
376
+ return;
377
+ }
378
+ reject(new Error(`${command} ${args.join(" ")} failed: ${stderr.trim() || stdout.trim() || `exit code ${code ?? "unknown"}`}`));
379
+ });
380
+ });
381
+ const stderrText = stderr.trim();
382
+ if (stderrText) {
383
+ console.warn(`[otto-bridge] ${command} stderr=${stderrText}`);
384
+ }
385
+ const stdoutText = stdout.trim();
386
+ if (stdoutText) {
387
+ console.log(`[otto-bridge] ${command} stdout=${stdoutText}`);
388
+ }
389
+ }
390
+ catch (error) {
391
+ const detail = error instanceof Error ? error.message : String(error);
392
+ throw new Error(detail);
393
+ }
394
+ finally {
395
+ if (this.activeChild === child) {
396
+ this.activeChild = null;
397
+ }
398
+ }
399
+ }
400
+ }
package/dist/main.js CHANGED
@@ -53,15 +53,15 @@ function resolveExecutorOverrides(args, current) {
53
53
  }
54
54
  function printUsage() {
55
55
  console.log(`Usage:
56
- otto-bridge pair --api http://localhost:8000 --code ABC123 [--name "Meu PC"] [--executor mock|clawd-cursor]
57
- otto-bridge run [--executor mock|clawd-cursor] [--clawd-url http://127.0.0.1:3847]
56
+ otto-bridge pair --api http://localhost:8000 --code ABC123 [--name "Meu PC"] [--executor native-macos|mock|clawd-cursor]
57
+ otto-bridge run [--executor native-macos|mock|clawd-cursor] [--clawd-url http://127.0.0.1:3847]
58
58
  otto-bridge status
59
59
  otto-bridge version
60
60
  otto-bridge update [--tag latest|next] [--dry-run]
61
61
  otto-bridge unpair
62
62
 
63
63
  Examples:
64
- otto-bridge pair --api https://api.leg3ndy.com.br --code ABC123 --executor clawd-cursor --clawd-url http://127.0.0.1:3847
64
+ otto-bridge pair --api https://api.leg3ndy.com.br --code ABC123
65
65
  otto-bridge run
66
66
  otto-bridge version
67
67
  otto-bridge update
package/dist/runtime.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { DEFAULT_HEARTBEAT_INTERVAL_MS, DEFAULT_RECONNECT_BASE_DELAY_MS, DEFAULT_RECONNECT_MAX_DELAY_MS, } from "./types.js";
2
2
  import { ClawdCursorJobExecutor } from "./executors/clawd_cursor.js";
3
3
  import { MockJobExecutor } from "./executors/mock.js";
4
+ import { NativeMacOSJobExecutor } from "./executors/native_macos.js";
4
5
  import { JobCancelledError } from "./executors/shared.js";
5
6
  function delay(ms) {
6
7
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -278,6 +279,9 @@ export class BridgeRuntime {
278
279
  if (config.executor.type === "clawd-cursor") {
279
280
  return new ClawdCursorJobExecutor(config.executor);
280
281
  }
282
+ if (config.executor.type === "native-macos") {
283
+ return new NativeMacOSJobExecutor();
284
+ }
281
285
  return new MockJobExecutor();
282
286
  }
283
287
  }
package/dist/types.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export const BRIDGE_CONFIG_VERSION = 1;
2
- export const BRIDGE_VERSION = "0.1.3";
2
+ export const BRIDGE_VERSION = "0.3.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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leg3ndy/otto-bridge",
3
- "version": "0.1.3",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Local companion for Otto Bridge device pairing and WebSocket runtime.",