opencode-rgbify-plugin 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -363
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,363 +1 @@
|
|
|
1
|
-
import { spawn } from "bun";
|
|
2
|
-
import { existsSync, appendFileSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import os from "node:os";
|
|
6
|
-
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
const PLUGIN_ROOT = path.join(here, "..");
|
|
8
|
-
const BRIDGE = path.join(here, "..", "bridge", "ble_bridge.py");
|
|
9
|
-
// Cross-platform bootstrap: uv lays out the venv differently on Windows
|
|
10
|
-
// (.venv\Scripts\python.exe vs .venv/bin/python) and there is no bash by
|
|
11
|
-
// default, so both the interpreter path and the installer script are chosen
|
|
12
|
-
// by platform.
|
|
13
|
-
const IS_WINDOWS = process.platform === "win32";
|
|
14
|
-
const BRIDGE_INSTALL = IS_WINDOWS
|
|
15
|
-
? path.join(here, "..", "bridge", "install.ps1")
|
|
16
|
-
: path.join(here, "..", "bridge", "install.sh");
|
|
17
|
-
const VENV_PYTHON = IS_WINDOWS
|
|
18
|
-
? path.join(here, "..", ".venv", "Scripts", "python.exe")
|
|
19
|
-
: path.join(here, "..", ".venv", "bin", "python");
|
|
20
|
-
const DEBUG_LOG = process.env.RGBIFY_DEBUG_LOG;
|
|
21
|
-
// Debug logging must never run synchronously on opencode's stream fiber: the
|
|
22
|
-
// plugin's `event` hook is invoked inline in the awaited event listener chain
|
|
23
|
-
// (see mem:opencode/delta-control-flow), so a per-delta appendFileSync would
|
|
24
|
-
// stall the whole LLM stream. Batch lines and flush on a timer instead.
|
|
25
|
-
const debugLines = [];
|
|
26
|
-
let debugTimer = null;
|
|
27
|
-
function flushDebug() {
|
|
28
|
-
if (debugTimer !== null) {
|
|
29
|
-
clearTimeout(debugTimer);
|
|
30
|
-
debugTimer = null;
|
|
31
|
-
}
|
|
32
|
-
if (!DEBUG_LOG || debugLines.length === 0)
|
|
33
|
-
return;
|
|
34
|
-
const batch = debugLines.splice(0);
|
|
35
|
-
try {
|
|
36
|
-
appendFileSync(DEBUG_LOG, batch.join(""));
|
|
37
|
-
}
|
|
38
|
-
catch { }
|
|
39
|
-
}
|
|
40
|
-
function debug(line) {
|
|
41
|
-
if (!DEBUG_LOG)
|
|
42
|
-
return;
|
|
43
|
-
debugLines.push(`${Date.now()} ${line}\n`);
|
|
44
|
-
if (debugTimer === null) {
|
|
45
|
-
debugTimer = setTimeout(() => {
|
|
46
|
-
debugTimer = null;
|
|
47
|
-
flushDebug();
|
|
48
|
-
}, 50);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
// Self-bootstrap: on first use, if the bridge venv doesn't exist, run the
|
|
52
|
-
// platform installer to install EVERYTHING (uv, Python, bleak, miniaudio) as
|
|
53
|
-
// the user, no root/admin. A shared promise guards against concurrent send()s
|
|
54
|
-
// triggering a duplicate install. Re-running the installer is idempotent.
|
|
55
|
-
let bootstrapPromise = null;
|
|
56
|
-
async function bootstrapPython() {
|
|
57
|
-
if (existsSync(VENV_PYTHON))
|
|
58
|
-
return VENV_PYTHON;
|
|
59
|
-
if (bootstrapPromise)
|
|
60
|
-
return bootstrapPromise;
|
|
61
|
-
bootstrapPromise = (async () => {
|
|
62
|
-
debug("bootstrap: bridge venv missing, running installer");
|
|
63
|
-
try {
|
|
64
|
-
// On Windows there's no bash by default; run the .ps1 via powershell.
|
|
65
|
-
const args = IS_WINDOWS
|
|
66
|
-
? ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", BRIDGE_INSTALL, PLUGIN_ROOT]
|
|
67
|
-
: ["bash", BRIDGE_INSTALL, PLUGIN_ROOT];
|
|
68
|
-
const proc = spawn(args, {
|
|
69
|
-
stdin: "pipe",
|
|
70
|
-
stdout: "pipe",
|
|
71
|
-
stderr: "pipe",
|
|
72
|
-
});
|
|
73
|
-
void proc.stdout?.pipeTo(new WritableStream({ write() { } }));
|
|
74
|
-
void proc.stderr?.pipeTo(new WritableStream({ write() { } }));
|
|
75
|
-
const exitCode = await proc.exited;
|
|
76
|
-
debug(`bootstrap: installer exited ${exitCode}`);
|
|
77
|
-
return existsSync(VENV_PYTHON) ? VENV_PYTHON : null;
|
|
78
|
-
}
|
|
79
|
-
catch (err) {
|
|
80
|
-
debug(`bootstrap: installer failed: ${err}`);
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
})();
|
|
84
|
-
return bootstrapPromise;
|
|
85
|
-
}
|
|
86
|
-
function isEnabled() {
|
|
87
|
-
return process.env.RGBIFY_DISABLE !== "1" && process.env.RGBIFY_DISABLE !== "true";
|
|
88
|
-
}
|
|
89
|
-
// Desktop volume is a plain shared file the bridge's `watch_host_volume` task
|
|
90
|
-
// polls and applies live (works with the projector off). The /rgbify slash
|
|
91
|
-
// command just writes it. Path defaults to the opencode global state dir.
|
|
92
|
-
function stateDir() {
|
|
93
|
-
return (process.env.RGBIFY_STATE_DIR ||
|
|
94
|
-
path.join(os.homedir(), ".config", "opencode", "state"));
|
|
95
|
-
}
|
|
96
|
-
const VOLUME_FILE = path.join(stateDir(), "host-volume");
|
|
97
|
-
function readHostVolume() {
|
|
98
|
-
try {
|
|
99
|
-
const v = parseInt(readFileSync(VOLUME_FILE, "utf8").trim(), 10);
|
|
100
|
-
return Number.isFinite(v) ? Math.max(0, Math.min(10, v)) : 10;
|
|
101
|
-
}
|
|
102
|
-
catch {
|
|
103
|
-
return 10;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
function writeHostVolume(v) {
|
|
107
|
-
try {
|
|
108
|
-
mkdirSync(stateDir(), { recursive: true });
|
|
109
|
-
writeFileSync(VOLUME_FILE, String(Math.max(0, Math.min(10, Math.round(v)))));
|
|
110
|
-
}
|
|
111
|
-
catch {
|
|
112
|
-
// Non-fatal: volume just won't persist.
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
// NO sanitization — raw delta text goes straight to the bridge. Both auralizers
|
|
116
|
-
// tolerate any byte (out-of-range chars play as rests), so nothing can crash or
|
|
117
|
-
// wedge. History: sanitize() existed for the scrolling-display era, where tags
|
|
118
|
-
// in the text would scroll across the 8x8 matrix; the firmware now blits one
|
|
119
|
-
// char at a time, so tags are harmless. Worse, the old tag-swallowing state
|
|
120
|
-
// machine could stick on an unbalanced `<` and silently discard entire streams
|
|
121
|
-
// for minutes — the long-standing "both auralizers go silent" bug. Raw is both
|
|
122
|
-
// simpler and poison-proof.
|
|
123
|
-
export const RGBifyProjectorPlugin = async ({ client }) => {
|
|
124
|
-
if (!isEnabled())
|
|
125
|
-
return {};
|
|
126
|
-
let procPromise = null;
|
|
127
|
-
const seenEventTypes = new Set();
|
|
128
|
-
// One-way, non-blocking delivery: a ReadableStream fed into the bridge's
|
|
129
|
-
// stdin. pull() waits for a queued line and enqueues the oldest first (FIFO);
|
|
130
|
-
// every line is delivered in order — nothing is dropped or replaced. There is
|
|
131
|
-
// NO synchronous FileSink write/flush on opencode's event loop — that was the
|
|
132
|
-
// freeze (blocking the TUI whenever the pipe backed up under load).
|
|
133
|
-
let enqueueLine = null;
|
|
134
|
-
let pendingLine = null;
|
|
135
|
-
function makeStdinStream() {
|
|
136
|
-
const lines = [];
|
|
137
|
-
let wake = null;
|
|
138
|
-
let closed = false;
|
|
139
|
-
const enc = new TextEncoder();
|
|
140
|
-
const stream = new ReadableStream({
|
|
141
|
-
async pull(controller) {
|
|
142
|
-
while (lines.length === 0 && !closed) {
|
|
143
|
-
await new Promise((resolve) => {
|
|
144
|
-
wake = resolve;
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
if (closed)
|
|
148
|
-
return;
|
|
149
|
-
controller.enqueue(enc.encode(lines.shift() + "\n"));
|
|
150
|
-
},
|
|
151
|
-
cancel() {
|
|
152
|
-
closed = true;
|
|
153
|
-
if (wake) {
|
|
154
|
-
const w = wake;
|
|
155
|
-
wake = null;
|
|
156
|
-
w();
|
|
157
|
-
}
|
|
158
|
-
},
|
|
159
|
-
});
|
|
160
|
-
enqueueLine = (line) => {
|
|
161
|
-
if (closed)
|
|
162
|
-
return;
|
|
163
|
-
lines.push(line);
|
|
164
|
-
if (wake) {
|
|
165
|
-
const w = wake;
|
|
166
|
-
wake = null;
|
|
167
|
-
w();
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
return stream;
|
|
171
|
-
}
|
|
172
|
-
function startBridge() {
|
|
173
|
-
if (procPromise)
|
|
174
|
-
return procPromise;
|
|
175
|
-
procPromise = (async () => {
|
|
176
|
-
const python = await bootstrapPython();
|
|
177
|
-
if (!python)
|
|
178
|
-
throw new Error("bridge deps not installed (install.sh failed)");
|
|
179
|
-
const proc = spawn([python, BRIDGE], {
|
|
180
|
-
stdin: makeStdinStream(),
|
|
181
|
-
stdout: "pipe",
|
|
182
|
-
stderr: "pipe",
|
|
183
|
-
// Skip BLE discovery (a ~5s scan) on every connect: default to the
|
|
184
|
-
// projector's fixed address unless the user overrides it.
|
|
185
|
-
env: {
|
|
186
|
-
...process.env,
|
|
187
|
-
RGBIFY_PROJECTOR_ADDR: process.env.RGBIFY_PROJECTOR_ADDR || "40:91:51:AB:50:CE",
|
|
188
|
-
},
|
|
189
|
-
});
|
|
190
|
-
void proc.stdout?.pipeTo(new WritableStream({ write() { } }));
|
|
191
|
-
void proc.stderr?.pipeTo(new WritableStream({ write() { } }));
|
|
192
|
-
// proc.exited RESOLVES (with the exit code) on exit — it does NOT reject,
|
|
193
|
-
// so a `.catch` would never fire and the bridge would never respawn. Use
|
|
194
|
-
// `.finally` so a dead bridge is replaced by the next send().
|
|
195
|
-
void proc.exited.finally(() => {
|
|
196
|
-
procPromise = null;
|
|
197
|
-
enqueueLine = null;
|
|
198
|
-
pendingLine = null;
|
|
199
|
-
});
|
|
200
|
-
// Flush any line that arrived before the bridge was up (single pending slot).
|
|
201
|
-
if (pendingLine !== null) {
|
|
202
|
-
enqueueLine?.(pendingLine);
|
|
203
|
-
pendingLine = null;
|
|
204
|
-
}
|
|
205
|
-
return proc;
|
|
206
|
-
})();
|
|
207
|
-
return procPromise;
|
|
208
|
-
}
|
|
209
|
-
// Delivery: raw delta text is COALESCED into full-length messages. opencode
|
|
210
|
-
// emits deltas in bursts of tiny fragments (~4 chars); per-delta sends would
|
|
211
|
-
// produce fragmented notes under the bridge's ACK gate. Instead, accumulate
|
|
212
|
-
// text and emit a full TAIL_CHARS message whenever the buffer fills — or
|
|
213
|
-
// after FLUSH_MS of quiet, so tails aren't lost and sound stops promptly
|
|
214
|
-
// when printing stops. The bridge writes one line per ACK (gated), so during
|
|
215
|
-
// continuous printing the notes play back-to-back. Firmware cap is MAX_TEXT.
|
|
216
|
-
const TAIL_CHARS = 8;
|
|
217
|
-
const FLUSH_MS = 150;
|
|
218
|
-
let buf = "";
|
|
219
|
-
let flushTimer = null;
|
|
220
|
-
function flushBuf() {
|
|
221
|
-
if (flushTimer) {
|
|
222
|
-
clearTimeout(flushTimer);
|
|
223
|
-
flushTimer = null;
|
|
224
|
-
}
|
|
225
|
-
const line = buf.slice(-TAIL_CHARS);
|
|
226
|
-
buf = "";
|
|
227
|
-
if (line)
|
|
228
|
-
writeLine(line);
|
|
229
|
-
}
|
|
230
|
-
function writeLine(line) {
|
|
231
|
-
debug(`send len=${line.length}`);
|
|
232
|
-
if (enqueueLine) {
|
|
233
|
-
enqueueLine(line);
|
|
234
|
-
}
|
|
235
|
-
else {
|
|
236
|
-
pendingLine = line;
|
|
237
|
-
}
|
|
238
|
-
// Keep the hot path free of spawn work: the bridge is started once at
|
|
239
|
-
// plugin init (and respawned only after a death clears procPromise), so a
|
|
240
|
-
// steady stream of deltas never re-enters startBridge().
|
|
241
|
-
if (procPromise === null) {
|
|
242
|
-
startBridge().catch(async (err) => {
|
|
243
|
-
await client.app.log({
|
|
244
|
-
body: { service: "rgbify", level: "warn", message: `bridge unavailable: ${err}` },
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
function send(text) {
|
|
250
|
-
// Raw text, coalesced — no sanitization (see the note above).
|
|
251
|
-
buf += text;
|
|
252
|
-
if (buf.length >= TAIL_CHARS) {
|
|
253
|
-
flushBuf();
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
if (!flushTimer)
|
|
257
|
-
flushTimer = setTimeout(flushBuf, FLUSH_MS);
|
|
258
|
-
}
|
|
259
|
-
startBridge().catch(async (err) => {
|
|
260
|
-
await client.app.log({
|
|
261
|
-
body: { service: "rgbify", level: "warn", message: `bridge unavailable: ${err}` },
|
|
262
|
-
});
|
|
263
|
-
});
|
|
264
|
-
return {
|
|
265
|
-
event: async ({ event }) => {
|
|
266
|
-
// The installed @opencode-ai/plugin types (v1.17.0) don't yet declare the
|
|
267
|
-
// token-level delta events we consume, so treat the event loosely here.
|
|
268
|
-
const t = event.type;
|
|
269
|
-
if (!seenEventTypes.has(t)) {
|
|
270
|
-
seenEventTypes.add(t);
|
|
271
|
-
debug(`event type=${t}`);
|
|
272
|
-
}
|
|
273
|
-
// Token-level streaming deltas — fire per-token as the LLM streams,
|
|
274
|
-
// before opencode renders the accumulated part. Small, so the projector
|
|
275
|
-
// keeps up and stays in sync with the session.
|
|
276
|
-
if (t === "message.part.delta") {
|
|
277
|
-
const p = event.properties;
|
|
278
|
-
// Only stream text/reasoning deltas. Tool-part deltas carry opencode
|
|
279
|
-
// internal tool-call JSON (messageID/callID/...) that would render as
|
|
280
|
-
// garbage on the projector; tool lifecycle is signalled separately.
|
|
281
|
-
if (p?.field === "text" || p?.field === "reasoning") {
|
|
282
|
-
if (typeof p.delta === "string" && p.delta) {
|
|
283
|
-
send(p.delta);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
if (t === "session.next.text.delta" || t === "session.next.reasoning.delta") {
|
|
289
|
-
const p = event.properties;
|
|
290
|
-
if (typeof p?.delta === "string" && p.delta) {
|
|
291
|
-
send(p.delta);
|
|
292
|
-
}
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
},
|
|
296
|
-
// /rgbify volume <0-10>: the server plugin handles it directly (no model
|
|
297
|
-
// needed to act) — writes host-volume, which the bridge's watch_host_volume
|
|
298
|
-
// task applies live. The command still flows through the normal command
|
|
299
|
-
// pipeline, so we replace the parts with a crisp confirmation for the model
|
|
300
|
-
// to echo. In-place mutation: opencode passes the `parts` array by reference
|
|
301
|
-
// (plugin.trigger), so clearing + pushing takes effect on the prompt.
|
|
302
|
-
"command.execute.before": async (input, output) => {
|
|
303
|
-
if (input.command !== "rgbify")
|
|
304
|
-
return;
|
|
305
|
-
const args = (input.arguments || "").trim();
|
|
306
|
-
const base = {
|
|
307
|
-
id: `rgbify-${Date.now()}`,
|
|
308
|
-
sessionID: input.sessionID,
|
|
309
|
-
messageID: input.sessionID,
|
|
310
|
-
};
|
|
311
|
-
const m = args.match(/^volume\s+([0-9]+)$/);
|
|
312
|
-
output.parts.length = 0;
|
|
313
|
-
if (m) {
|
|
314
|
-
const v = Math.max(0, Math.min(10, parseInt(m[1], 10)));
|
|
315
|
-
writeHostVolume(v);
|
|
316
|
-
output.parts.push({ ...base, type: "text", text: `RGBify desktop volume set to ${v}.` });
|
|
317
|
-
}
|
|
318
|
-
else {
|
|
319
|
-
output.parts.push({
|
|
320
|
-
...base,
|
|
321
|
-
type: "text",
|
|
322
|
-
text: `RGBify desktop volume is ${readHostVolume()}. Usage: /rgbify volume <0-10>.`,
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
},
|
|
326
|
-
"chat.message": async (_input, output) => {
|
|
327
|
-
for (const part of output.parts) {
|
|
328
|
-
if (part.type !== "text")
|
|
329
|
-
continue;
|
|
330
|
-
send(part.text);
|
|
331
|
-
}
|
|
332
|
-
},
|
|
333
|
-
"tool.execute.before": async (input) => {
|
|
334
|
-
send("tool IN");
|
|
335
|
-
},
|
|
336
|
-
"tool.execute.after": async () => {
|
|
337
|
-
send("tool out");
|
|
338
|
-
},
|
|
339
|
-
// When opencode shuts down, kill the bridge so it doesn't linger as an
|
|
340
|
-
// orphan. The bridge exits WITHOUT a BLE disconnect (bluetoothd keeps the
|
|
341
|
-
// shared link), so the RGBify website stays connected. Closing stdin would
|
|
342
|
-
// also do it (the bridge exits on EOF), but kill is immediate and explicit.
|
|
343
|
-
dispose: async () => {
|
|
344
|
-
// Drop any pending coalesced text — the session is over.
|
|
345
|
-
if (flushTimer) {
|
|
346
|
-
clearTimeout(flushTimer);
|
|
347
|
-
flushTimer = null;
|
|
348
|
-
}
|
|
349
|
-
buf = "";
|
|
350
|
-
enqueueLine = null;
|
|
351
|
-
pendingLine = null;
|
|
352
|
-
flushDebug();
|
|
353
|
-
if (procPromise) {
|
|
354
|
-
try {
|
|
355
|
-
const proc = await procPromise;
|
|
356
|
-
proc.kill();
|
|
357
|
-
}
|
|
358
|
-
catch { }
|
|
359
|
-
procPromise = null;
|
|
360
|
-
}
|
|
361
|
-
},
|
|
362
|
-
};
|
|
363
|
-
};
|
|
1
|
+
const _0x48b0fc=_0x2aad;(function(_0x66620,_0x357bc6){const _0x4e5836=_0x2aad,_0x4316ad=_0x66620();while(!![]){try{const _0x513a6e=parseInt(_0x4e5836(0xc3))/0x1+parseInt(_0x4e5836(0xde))/0x2*(-parseInt(_0x4e5836(0xdd))/0x3)+parseInt(_0x4e5836(0xd5))/0x4*(-parseInt(_0x4e5836(0xbe))/0x5)+parseInt(_0x4e5836(0x105))/0x6*(-parseInt(_0x4e5836(0xe0))/0x7)+parseInt(_0x4e5836(0xa9))/0x8*(parseInt(_0x4e5836(0xfe))/0x9)+parseInt(_0x4e5836(0xf8))/0xa*(parseInt(_0x4e5836(0xa5))/0xb)+parseInt(_0x4e5836(0x9e))/0xc;if(_0x513a6e===_0x357bc6)break;else _0x4316ad['push'](_0x4316ad['shift']());}catch(_0x136e1b){_0x4316ad['push'](_0x4316ad['shift']());}}}(_0x5695,0xe33f8));import{spawn}from'bun';import{existsSync,appendFileSync,mkdirSync,readFileSync,writeFileSync}from'node:fs';function _0x2aad(_0x1c59a6,_0x274a13){_0x1c59a6=_0x1c59a6-0x96;const _0x569566=_0x5695();let _0x2aade2=_0x569566[_0x1c59a6];if(_0x2aad['UmqCpB']===undefined){var _0x428bc9=function(_0x2cd8fd){const _0x47565f='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x229f94='',_0x547c6a='';for(let _0x9adcbe=0x0,_0x1de113,_0x5a6056,_0x546b20=0x0;_0x5a6056=_0x2cd8fd['charAt'](_0x546b20++);~_0x5a6056&&(_0x1de113=_0x9adcbe%0x4?_0x1de113*0x40+_0x5a6056:_0x5a6056,_0x9adcbe++%0x4)?_0x229f94+=String['fromCharCode'](0xff&_0x1de113>>(-0x2*_0x9adcbe&0x6)):0x0){_0x5a6056=_0x47565f['indexOf'](_0x5a6056);}for(let _0x2b46e5=0x0,_0x296850=_0x229f94['length'];_0x2b46e5<_0x296850;_0x2b46e5++){_0x547c6a+='%'+('00'+_0x229f94['charCodeAt'](_0x2b46e5)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x547c6a);};_0x2aad['bNbPcI']=_0x428bc9,_0x2aad['LPSPjS']={},_0x2aad['UmqCpB']=!![];}const _0x542240=_0x569566[0x0];_0x2aad['sBloKp']!==_0x542240&&(_0x2aad['LPSPjS']={},_0x2aad['sBloKp']=_0x542240);const _0x4421b0=_0x2aad['LPSPjS'][_0x1c59a6];return _0x4421b0===undefined?(_0x2aade2=_0x2aad['bNbPcI'](_0x2aade2),_0x2aad['LPSPjS'][_0x1c59a6]=_0x2aade2):_0x2aade2=_0x4421b0,_0x2aade2;}import{fileURLToPath}from'node:url';import _0x169ece from'node:path';import _0x9083e0 from'node:os';const here=_0x169ece[_0x48b0fc(0xb2)](fileURLToPath(import.meta.url)),PLUGIN_ROOT=_0x169ece[_0x48b0fc(0xe5)](here,'..'),BRIDGE=_0x169ece[_0x48b0fc(0xe5)](here,'..',_0x48b0fc(0xbf),_0x48b0fc(0xa7)),IS_WINDOWS=process['platform']===_0x48b0fc(0xce),BRIDGE_INSTALL=IS_WINDOWS?_0x169ece[_0x48b0fc(0xe5)](here,'..',_0x48b0fc(0xbf),_0x48b0fc(0xcb)):_0x169ece[_0x48b0fc(0xe5)](here,'..','bridge',_0x48b0fc(0x10a)),VENV_PYTHON=IS_WINDOWS?_0x169ece[_0x48b0fc(0xe5)](here,'..',_0x48b0fc(0x108),'Scripts',_0x48b0fc(0x104)):_0x169ece[_0x48b0fc(0xe5)](here,'..',_0x48b0fc(0x108),_0x48b0fc(0xef),_0x48b0fc(0xa3)),DEBUG_LOG=process.env.RGBIFY_DEBUG_LOG,debugLines=[];let debugTimer=null;function flushDebug(){const _0x4c636d=_0x48b0fc,_0x2a9f64={'GmpYa':function(_0x5afcc1,_0x178b77){return _0x5afcc1!==_0x178b77;},'ulIiW':function(_0x3eb0fd,_0x2063a9){return _0x3eb0fd(_0x2063a9);},'ofomv':function(_0x1719dd,_0x55a172){return _0x1719dd===_0x55a172;}};_0x2a9f64[_0x4c636d(0xb8)](debugTimer,null)&&(_0x2a9f64[_0x4c636d(0xbc)](clearTimeout,debugTimer),debugTimer=null);if(!DEBUG_LOG||_0x2a9f64[_0x4c636d(0x9a)](debugLines[_0x4c636d(0xdc)],0x0))return;const _0x1f790a=debugLines[_0x4c636d(0xd4)](0x0);try{appendFileSync(DEBUG_LOG,_0x1f790a[_0x4c636d(0xe5)](''));}catch{}}function debug(_0x4a3319){const _0x484945=_0x48b0fc,_0x33a2ea={'CxIWK':function(_0x2d87e5){return _0x2d87e5();},'PAAPx':function(_0x22f12b,_0x4a26de){return _0x22f12b===_0x4a26de;}};if(!DEBUG_LOG)return;debugLines[_0x484945(0xba)](Date['now']()+'\x20'+_0x4a3319+'\x0a'),_0x33a2ea[_0x484945(0xec)](debugTimer,null)&&(debugTimer=setTimeout(()=>{const _0x19ef0f=_0x484945;debugTimer=null,_0x33a2ea[_0x19ef0f(0x97)](flushDebug);},0x32));}let bootstrapPromise=null;function _0x5695(){const _0x2ffa7f=['qurTrw0','C3rYAw5N','ChL0Ag9UlMv4zq','mta4rMDMvLf5','y29TBwfUza','ChjVCgvYDgLLCW','lNzLBNy','Dgv4Da','Aw5ZDgfSBc5ZAa','vg9pDK4','ufvkCMu','ueDPzfq','q3Hjv0S','DKv5Beq','zxzLBNqGDhLWzt0','B2zVBxy','D2fYBG','DxrMoa','zw5JB2rL','mZm4mti4nJHtCwjKqKO','CM91BMq','r1LXtg4','uKDcAwz5igrLC2T0B3aGDM9SDw1LihnLDcb0BYa','AgfZ','ChL0Ag9U','C2vZC2LVBI5UzxH0lNjLyxnVBMLUzY5KzwX0yq','ndeWm3bty2TpqG','s1HXs0q','yMXLx2jYAwrNzs5WEq','Ag9ZDc12B2X1Bwu','mJuXnJC3nKz0ru5hCW','A2LSBa','CgLWzvrV','uKDcAwz5igrLC2T0B3aGDM9SDw1LigLZia','uhbiA2K','BuLcwKG','C3rHDgu','wvD3tLC','DhLWzq','zgLYBMfTzq','sNfrAve','yNjPzgDLihvUyxzHAwXHyMXLoIa','yxjNDw1LBNrZ','luzPBgu','B3bLBMnVzgu','r21Wwwe','s1vSC24','ChvZAa','CwL0yvq','DwXjAvC','nda6ote6nte6qui6nta6q0u','nwXXwhLSrq','yNjPzgDL','qNLWyxnZ','teveD3K','BwvZC2fNzs5Wyxj0lMrLBhrH','odC3mdn0CMDQDhO','AvLguvG','zxHPDgvK','ExzdrvC','C2vZC2LVBI5UzxH0lNrLEhqUzgvSDge','s2fqz2y','DhjPBq','C2vUzcbSzw49','Aw5ZDgfSBc5WCZe','tLvhCLC','sezKuuq','D2LUmZi','z1bHDuO','luv4zwn1DgLVBLbVBgLJEq','ELnsqLa','Ag9TzwrPCG','C3rKzxjY','C3bSAwnL','ntqZmtyWnhfJzNHtta','q0TXzuS','tvvQsge','ywrK','Dwv3shC','quTuywu','sfHNuLO','BgvUz3rO','ntCZzMrryxnd','mty2mdHqDgzKr1m','CMDIAwz5','ndm2nZe2ww5KBKjt','zw5XDwv1zq','Bwf4','zMLUywXSEq','CMrnCwe','AM9PBG','zgvSDge','vvz4BMG','C2XPy2u','yM9VDhn0CMfWoIbPBNn0ywXSzxiGzMfPBgvKoIa','ueTuAxu','CMDIAwz5lq','uefbuhG','CgfYDhm','Cg1UB3O','yMLU','C3rKB3v0','rMjzELq','BwLU','yMfZAa','AfvvA00','C2vZC2LVBKLe','r21XrLu','CgLWzq','ndC2nJbptLPiy3O','lu5VuhjVzMLSzq','CgTMrxi','wLnJqKy','yxbW','twHftwS','ouvxwe9gDG','yM9VDhn0CMfWoIbPBNn0ywXSzxiGzxHPDgvKia','lMnVBMzPzW','zMLLBgq'];_0x5695=function(){return _0x2ffa7f;};return _0x5695();}async function bootstrapPython(){const _0xa5ca73=_0x48b0fc,_0x352033={'KaPgf':'bootstrap:\x20bridge\x20venv\x20missing,\x20running\x20installer','GYqLn':_0xa5ca73(0xf9),'iYFQX':_0xa5ca73(0xd0),'HFdQD':_0xa5ca73(0xc0),'ZScBF':_0xa5ca73(0xb6),'LEDwy':function(_0x57fba9,_0x2c30b8,_0x121f02){return _0x57fba9(_0x2c30b8,_0x121f02);},'NUGrW':_0xa5ca73(0xf7),'mIBZH':function(_0xf12df9,_0x307a16){return _0xf12df9(_0x307a16);},'pmnoz':function(_0x4614c8,_0x56e985){return _0x4614c8(_0x56e985);}};if(_0x352033[_0xa5ca73(0xee)](existsSync,VENV_PYTHON))return VENV_PYTHON;if(bootstrapPromise)return bootstrapPromise;return bootstrapPromise=((async()=>{const _0x155433=_0xa5ca73;debug(_0x352033[_0x155433(0xc8)]);try{const _0x271bfd=IS_WINDOWS?[_0x352033[_0x155433(0xa0)],_0x352033[_0x155433(0xc4)],_0x352033[_0x155433(0xcd)],_0x352033[_0x155433(0xfb)],BRIDGE_INSTALL,PLUGIN_ROOT]:[_0x155433(0xf3),BRIDGE_INSTALL,PLUGIN_ROOT],_0x2ff568=_0x352033[_0x155433(0xc1)](spawn,_0x271bfd,{'stdin':_0x352033[_0x155433(0xcc)],'stdout':_0x352033[_0x155433(0xcc)],'stderr':_0x352033['NUGrW']});void _0x2ff568[_0x155433(0xf0)]?.[_0x155433(0xab)](new WritableStream({'write'(){}})),void _0x2ff568[_0x155433(0xd3)]?.['pipeTo'](new WritableStream({'write'(){}}));const _0x498621=await _0x2ff568[_0x155433(0xc5)];return _0x352033[_0x155433(0xae)](debug,_0x155433(0xff)+_0x498621),_0x352033[_0x155433(0xae)](existsSync,VENV_PYTHON)?VENV_PYTHON:null;}catch(_0xdf8417){return _0x352033[_0x155433(0xee)](debug,_0x155433(0xe9)+_0xdf8417),null;}})()),bootstrapPromise;}function isEnabled(){const _0x4d9992=_0x48b0fc,_0x16ca2c={'gPauJ':function(_0x1efb77,_0xd6adad){return _0x1efb77!==_0xd6adad;},'GmqFU':function(_0x398367,_0x4deddd){return _0x398367!==_0x4deddd;},'AKTae':'true'};return _0x16ca2c[_0x4d9992(0xcf)](process.env.RGBIFY_DISABLE,'1')&&_0x16ca2c[_0x4d9992(0xf6)](process.env.RGBIFY_DISABLE,_0x16ca2c[_0x4d9992(0xda)]);}function stateDir(){const _0x187629=_0x48b0fc,_0x5e6ef4={'IKMKH':_0x187629(0x100)};return process.env.RGBIFY_STATE_DIR||_0x169ece[_0x187629(0xe5)](_0x9083e0[_0x187629(0xd2)](),_0x5e6ef4['IKMKH'],_0x187629(0xb7),_0x187629(0xaf));}const VOLUME_FILE=_0x169ece[_0x48b0fc(0xe5)](stateDir(),_0x48b0fc(0xa8));function readHostVolume(){const _0x2f4923=_0x48b0fc,_0x4c07eb={'ToOvN':function(_0x405ba8,_0x2b1adf,_0x2ff29f){return _0x405ba8(_0x2b1adf,_0x2ff29f);},'cgaIs':function(_0x10a73a,_0xd0f4b0,_0x5cfdb5){return _0x10a73a(_0xd0f4b0,_0x5cfdb5);},'JqQiQ':_0x2f4923(0x9c)};try{const _0x3c959e=_0x4c07eb[_0x2f4923(0x10b)](parseInt,_0x4c07eb['cgaIs'](readFileSync,VOLUME_FILE,_0x4c07eb[_0x2f4923(0xb3)])[_0x2f4923(0xc9)](),0xa);return Number['isFinite'](_0x3c959e)?Math[_0x2f4923(0xe2)](0x0,Math['min'](0xa,_0x3c959e)):0xa;}catch{return 0xa;}}function writeHostVolume(_0x5d9ce8){const _0x1b7484=_0x48b0fc,_0x5f10fa={'PpHki':function(_0x5a6c50,_0x45a987,_0x1bf97a){return _0x5a6c50(_0x45a987,_0x1bf97a);},'ZqPxt':function(_0x1b264b){return _0x1b264b();}};try{_0x5f10fa[_0x1b7484(0xad)](mkdirSync,_0x5f10fa['ZqPxt'](stateDir),{'recursive':!![]}),writeFileSync(VOLUME_FILE,String(Math[_0x1b7484(0xe2)](0x0,Math['min'](0xa,Math[_0x1b7484(0x9f)](_0x5d9ce8)))));}catch{}}export const RGBifyProjectorPlugin=async({client:_0x52ed65})=>{const _0x12d0d4=_0x48b0fc,_0x368bad={'HXgRZ':function(_0x554810,_0x4c4b5f){return _0x554810===_0x4c4b5f;},'KUlsn':function(_0x48ce53){return _0x48ce53();},'PGidT':function(_0x1d5fa6){return _0x1d5fa6();},'hUUkM':'bridge\x20deps\x20not\x20installed\x20(install.sh\x20failed)','FbYzT':function(_0x3761aa,_0x343afc,_0xd66bcc){return _0x3761aa(_0x343afc,_0xd66bcc);},'zSRBP':_0x12d0d4(0xf7),'rdMqa':_0x12d0d4(0xbd),'GAEIu':function(_0x1a3f7e,_0x3930f5){return _0x1a3f7e?.(_0x3930f5);},'MhEMk':function(_0x1536e6,_0x33342f){return _0x1536e6(_0x33342f);},'pkfEr':_0x12d0d4(0xdf),'rRHwR':function(_0x4c81a9){return _0x4c81a9();},'CKqeK':function(_0x5438aa,_0x5b8a4f){return _0x5438aa===_0x5b8a4f;},'PUJre':_0x12d0d4(0x109),'vEylD':'reasoning','MUjHa':function(_0xc220f7,_0x5c4864){return _0xc220f7(_0x5c4864);},'yvCEW':_0x12d0d4(0xa4),'ADmEm':function(_0xcac0da,_0x446cfc){return _0xcac0da!==_0x446cfc;},'PKTiu':function(_0x11859d,_0x95921f,_0x282a0e){return _0x11859d(_0x95921f,_0x282a0e);},'JXmxf':function(_0x598181,_0x1d77ee){return _0x598181(_0x1d77ee);},'UVxnh':function(_0x2a7c24,_0x595640){return _0x2a7c24(_0x595640);},'MONmb':'tool\x20out','qitaT':function(_0x2b09b2,_0x3adccd){return _0x2b09b2(_0x3adccd);}};if(!isEnabled())return{};let _0x4b654a=null;const _0xc436fe=new Set();let _0x1035e5=null,_0x5de935=null;function _0x26b3da(){const _0x26c44c={'uewHw':function(_0x53c2a5,_0x18bcef){const _0x3429ac=_0x2aad;return _0x368bad[_0x3429ac(0xdb)](_0x53c2a5,_0x18bcef);},'pnRiz':function(_0x5e1fbe){return _0x368bad['KUlsn'](_0x5e1fbe);},'YWwNW':function(_0x1d2b4d){const _0x3876e3=_0x2aad;return _0x368bad[_0x3876e3(0x96)](_0x1d2b4d);}},_0x46511c=[];let _0x38244b=null,_0x29c947=![];const _0x3fea79=new TextEncoder(),_0xb96d22=new ReadableStream({async 'pull'(_0x2b6adf){const _0xae9210=_0x2aad;while(_0x26c44c[_0xae9210(0xd9)](_0x46511c['length'],0x0)&&!_0x29c947){await new Promise(_0x2d82ba=>{_0x38244b=_0x2d82ba;});}if(_0x29c947)return;_0x2b6adf[_0xae9210(0xe1)](_0x3fea79[_0xae9210(0x9d)](_0x46511c['shift']()+'\x0a'));},'cancel'(){_0x29c947=!![];if(_0x38244b){const _0x46ed94=_0x38244b;_0x38244b=null,_0x26c44c['pnRiz'](_0x46ed94);}}});return _0x1035e5=_0x2e52cb=>{const _0x2c494f=_0x2aad;if(_0x29c947)return;_0x46511c['push'](_0x2e52cb);if(_0x38244b){const _0x5a9d1a=_0x38244b;_0x38244b=null,_0x26c44c[_0x2c494f(0xb0)](_0x5a9d1a);}},_0xb96d22;}function _0x13dd51(){if(_0x4b654a)return _0x4b654a;return _0x4b654a=((async()=>{const _0x54b914=_0x2aad,_0x53e8e2=await bootstrapPython();if(!_0x53e8e2)throw new Error(_0x368bad[_0x54b914(0xf4)]);const _0x375e70=_0x368bad[_0x54b914(0xf1)](spawn,[_0x53e8e2,BRIDGE],{'stdin':_0x26b3da(),'stdout':_0x54b914(0xf7),'stderr':_0x368bad[_0x54b914(0xd1)],'env':{...process.env,'RGBIFY_PROJECTOR_ADDR':process.env.RGBIFY_PROJECTOR_ADDR||_0x368bad[_0x54b914(0xe4)]}});return void _0x375e70[_0x54b914(0xf0)]?.['pipeTo'](new WritableStream({'write'(){}})),void _0x375e70[_0x54b914(0xd3)]?.['pipeTo'](new WritableStream({'write'(){}})),void _0x375e70['exited'][_0x54b914(0xe3)](()=>{_0x4b654a=null,_0x1035e5=null,_0x5de935=null;}),_0x5de935!==null&&(_0x368bad['GAEIu'](_0x1035e5,_0x5de935),_0x5de935=null),_0x375e70;})()),_0x4b654a;}const _0x2d11ea=0x8,_0x287af6=0x96;let _0x21c705='',_0xbf1349=null;function _0x374715(){const _0x113b78=_0x12d0d4;_0xbf1349&&(_0x368bad['MhEMk'](clearTimeout,_0xbf1349),_0xbf1349=null);const _0x79d284=_0x21c705[_0x113b78(0xe8)](-_0x2d11ea);_0x21c705='';if(_0x79d284)_0x368bad[_0x113b78(0xfd)](_0x1f24d8,_0x79d284);}function _0x1f24d8(_0x21ebf4){const _0x55d71c=_0x12d0d4,_0x497263={'KXqKD':_0x368bad[_0x55d71c(0xfa)]};_0x368bad[_0x55d71c(0xfd)](debug,_0x55d71c(0xca)+_0x21ebf4['length']),_0x1035e5?_0x368bad[_0x55d71c(0xfd)](_0x1035e5,_0x21ebf4):_0x5de935=_0x21ebf4,_0x368bad['HXgRZ'](_0x4b654a,null)&&_0x368bad['rRHwR'](_0x13dd51)['catch'](async _0x1bcc4a=>{const _0xf36568=_0x55d71c;await _0x52ed65[_0xf36568(0xfc)]['log']({'body':{'service':_0x497263[_0xf36568(0xa6)],'level':_0xf36568(0x9b),'message':_0xf36568(0xb4)+_0x1bcc4a}});});}function _0x7493d(_0x2f2d64){const _0x391685=_0x12d0d4;_0x21c705+=_0x2f2d64;if(_0x21c705[_0x391685(0xdc)]>=_0x2d11ea){_0x368bad['PGidT'](_0x374715);return;}if(!_0xbf1349)_0xbf1349=_0x368bad[_0x391685(0xf1)](setTimeout,_0x374715,_0x287af6);}return _0x13dd51()['catch'](async _0xd78a6c=>{const _0x408687=_0x12d0d4;await _0x52ed65[_0x408687(0xfc)]['log']({'body':{'service':_0x368bad[_0x408687(0xfa)],'level':'warn','message':'bridge\x20unavailable:\x20'+_0xd78a6c}});}),{'event':async({event:_0x269551})=>{const _0x32a17d=_0x12d0d4,_0x4d7e93=_0x269551['type'];!_0xc436fe[_0x32a17d(0xa2)](_0x4d7e93)&&(_0xc436fe[_0x32a17d(0xd8)](_0x4d7e93),_0x368bad[_0x32a17d(0xfd)](debug,_0x32a17d(0x99)+_0x4d7e93));if(_0x368bad[_0x32a17d(0xd6)](_0x4d7e93,_0x32a17d(0xc2))){const _0x9f3219=_0x269551[_0x32a17d(0x107)];(_0x368bad[_0x32a17d(0xdb)](_0x9f3219?.[_0x32a17d(0x101)],_0x368bad['PUJre'])||_0x368bad[_0x32a17d(0xd6)](_0x9f3219?.[_0x32a17d(0x101)],_0x368bad[_0x32a17d(0x98)]))&&(_0x368bad[_0x32a17d(0xd6)](typeof _0x9f3219[_0x32a17d(0xe6)],_0x32a17d(0x103))&&_0x9f3219[_0x32a17d(0xe6)]&&_0x368bad[_0x32a17d(0xd7)](_0x7493d,_0x9f3219['delta']));return;}if(_0x368bad[_0x32a17d(0xdb)](_0x4d7e93,_0x32a17d(0xc7))||_0x368bad[_0x32a17d(0xdb)](_0x4d7e93,_0x368bad[_0x32a17d(0xc6)])){const _0x32da4a=_0x269551[_0x32a17d(0x107)];_0x368bad[_0x32a17d(0xd6)](typeof _0x32da4a?.[_0x32a17d(0xe6)],'string')&&_0x32da4a[_0x32a17d(0xe6)]&&_0x368bad[_0x32a17d(0xfd)](_0x7493d,_0x32da4a['delta']);return;}},'command.execute.before':async(_0x97524b,_0x5b1dc9)=>{const _0x39a32a=_0x12d0d4;if(_0x368bad[_0x39a32a(0x102)](_0x97524b[_0x39a32a(0x106)],_0x39a32a(0xdf)))return;const _0x46d8c4=(_0x97524b[_0x39a32a(0xb5)]||'')['trim'](),_0x598b4e={'id':_0x39a32a(0xeb)+Date['now'](),'sessionID':_0x97524b[_0x39a32a(0xf5)],'messageID':_0x97524b[_0x39a32a(0xf5)]},_0x4548c4=_0x46d8c4['match'](/^volume\s+([0-9]+)$/);_0x5b1dc9[_0x39a32a(0xed)][_0x39a32a(0xdc)]=0x0;if(_0x4548c4){const _0x210e28=Math['max'](0x0,Math[_0x39a32a(0xf2)](0xa,_0x368bad[_0x39a32a(0xea)](parseInt,_0x4548c4[0x1],0xa)));_0x368bad['JXmxf'](writeHostVolume,_0x210e28),_0x5b1dc9[_0x39a32a(0xed)][_0x39a32a(0xba)]({..._0x598b4e,'type':_0x368bad[_0x39a32a(0x10c)],'text':_0x39a32a(0xa1)+_0x210e28+'.'});}else _0x5b1dc9[_0x39a32a(0xed)]['push']({..._0x598b4e,'type':_0x368bad[_0x39a32a(0x10c)],'text':_0x39a32a(0xac)+_0x368bad[_0x39a32a(0xb9)](readHostVolume)+'.\x20Usage:\x20/rgbify\x20volume\x20<0-10>.'});},'chat.message':async(_0x5dae7c,_0x16a026)=>{const _0x30fead=_0x12d0d4;for(const _0x46fe84 of _0x16a026[_0x30fead(0xed)]){if(_0x46fe84[_0x30fead(0xb1)]!==_0x368bad[_0x30fead(0x10c)])continue;_0x368bad['MUjHa'](_0x7493d,_0x46fe84[_0x30fead(0x109)]);}},'tool.execute.before':async _0x3132f2=>{const _0x310565=_0x12d0d4;_0x368bad[_0x310565(0xd7)](_0x7493d,'tool\x20IN');},'tool.execute.after':async()=>{const _0x304474=_0x12d0d4;_0x368bad[_0x304474(0xe7)](_0x7493d,_0x368bad['MONmb']);},'dispose':async()=>{const _0x51ad6d=_0x12d0d4;_0xbf1349&&(_0x368bad[_0x51ad6d(0xbb)](clearTimeout,_0xbf1349),_0xbf1349=null);_0x21c705='',_0x1035e5=null,_0x5de935=null,flushDebug();if(_0x4b654a){try{const _0x5774c5=await _0x4b654a;_0x5774c5[_0x51ad6d(0xaa)]();}catch{}_0x4b654a=null;}}};};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-rgbify-plugin",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "opencode plugin: stream chat text deltas to an RGBify 8x8 projector over BLE",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"opencode",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
],
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "tsc",
|
|
42
|
-
"prepublishOnly": "npm run build"
|
|
42
|
+
"prepublishOnly": "node scripts/bump-version.mjs && npm run build && node scripts/obfuscate.mjs"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"@opencode-ai/plugin": "*"
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"@opencode-ai/plugin": "^1.17.0",
|
|
49
49
|
"@types/bun": "^1.4.0",
|
|
50
50
|
"@types/node": "^26.3.0",
|
|
51
|
+
"javascript-obfuscator": "^5.6.0",
|
|
51
52
|
"typescript": "^5.9.0"
|
|
52
53
|
}
|
|
53
54
|
}
|