lunel-cli 0.1.15 → 0.1.17
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 +320 -57
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { WebSocket } from "ws";
|
|
3
3
|
import qrcode from "qrcode-terminal";
|
|
4
|
+
import { createOpencode } from "@opencode-ai/sdk";
|
|
4
5
|
import Ignore from "ignore";
|
|
5
6
|
const ignore = Ignore.default;
|
|
6
7
|
import * as fs from "fs/promises";
|
|
@@ -8,16 +9,24 @@ import * as path from "path";
|
|
|
8
9
|
import * as os from "os";
|
|
9
10
|
import { spawn, execSync } from "child_process";
|
|
10
11
|
import { createServer, createConnection } from "net";
|
|
12
|
+
import { createInterface } from "readline";
|
|
11
13
|
const PROXY_URL = process.env.LUNEL_PROXY_URL || "https://gateway.lunel.dev";
|
|
12
|
-
|
|
14
|
+
import { createRequire } from "module";
|
|
15
|
+
const __require = createRequire(import.meta.url);
|
|
16
|
+
const VERSION = __require("../package.json").version;
|
|
13
17
|
// Root directory - sandbox all file operations to this
|
|
14
18
|
const ROOT_DIR = process.cwd();
|
|
15
|
-
// Terminal sessions
|
|
16
|
-
const terminals = new
|
|
19
|
+
// Terminal sessions (managed by Rust PTY binary)
|
|
20
|
+
const terminals = new Set();
|
|
21
|
+
// PTY binary process
|
|
22
|
+
let ptyProcess = null;
|
|
23
|
+
const ptyPendingSpawns = new Map();
|
|
17
24
|
const processes = new Map();
|
|
18
25
|
const processOutputBuffers = new Map();
|
|
19
26
|
// CPU usage tracking
|
|
20
27
|
let lastCpuInfo = null;
|
|
28
|
+
// OpenCode client
|
|
29
|
+
let opencodeClient = null;
|
|
21
30
|
// Proxy tunnel management
|
|
22
31
|
let currentSessionCode = null;
|
|
23
32
|
const activeTunnels = new Map();
|
|
@@ -529,53 +538,113 @@ async function handleGitDiscard(payload) {
|
|
|
529
538
|
return {};
|
|
530
539
|
}
|
|
531
540
|
// ============================================================================
|
|
532
|
-
// Terminal Handlers
|
|
541
|
+
// Terminal Handlers (delegates to Rust PTY binary)
|
|
533
542
|
// ============================================================================
|
|
534
543
|
let dataChannel = null;
|
|
535
|
-
function
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
544
|
+
function getPtyBinaryPath() {
|
|
545
|
+
// TODO: Switch to GitHub releases download for production
|
|
546
|
+
return path.join(os.homedir(), "lunel-pty");
|
|
547
|
+
}
|
|
548
|
+
function ensurePtyProcess() {
|
|
549
|
+
if (ptyProcess && ptyProcess.exitCode === null)
|
|
550
|
+
return;
|
|
551
|
+
const binPath = getPtyBinaryPath();
|
|
552
|
+
ptyProcess = spawn(binPath, [], {
|
|
541
553
|
cwd: ROOT_DIR,
|
|
542
|
-
env: {
|
|
543
|
-
...process.env,
|
|
544
|
-
TERM: "xterm-256color",
|
|
545
|
-
COLUMNS: cols.toString(),
|
|
546
|
-
LINES: rows.toString(),
|
|
547
|
-
},
|
|
548
554
|
stdio: ["pipe", "pipe", "pipe"],
|
|
549
555
|
});
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
payload: { terminalId, data: data.toString() },
|
|
560
|
-
};
|
|
561
|
-
dataChannel.send(JSON.stringify(msg));
|
|
556
|
+
ptyProcess.stderr?.on("data", (data) => {
|
|
557
|
+
console.error("[pty]", data.toString().trim());
|
|
558
|
+
});
|
|
559
|
+
ptyProcess.on("exit", (code) => {
|
|
560
|
+
console.log(`[pty] PTY process exited with code ${code}`);
|
|
561
|
+
ptyProcess = null;
|
|
562
|
+
// Reject all pending spawns
|
|
563
|
+
for (const [id, pending] of ptyPendingSpawns) {
|
|
564
|
+
pending.reject(new Error("PTY process exited"));
|
|
562
565
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
566
|
+
ptyPendingSpawns.clear();
|
|
567
|
+
});
|
|
568
|
+
// Parse stdout line by line for events from the Rust binary
|
|
569
|
+
const rl = createInterface({ input: ptyProcess.stdout });
|
|
570
|
+
rl.on("line", (line) => {
|
|
571
|
+
let event;
|
|
572
|
+
try {
|
|
573
|
+
event = JSON.parse(line);
|
|
574
|
+
}
|
|
575
|
+
catch {
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
if (event.event === "spawned") {
|
|
579
|
+
const pending = ptyPendingSpawns.get(event.id);
|
|
580
|
+
if (pending) {
|
|
581
|
+
pending.resolve();
|
|
582
|
+
ptyPendingSpawns.delete(event.id);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
else if (event.event === "state") {
|
|
586
|
+
// Forward screen state to app via data channel
|
|
587
|
+
if (dataChannel && dataChannel.readyState === WebSocket.OPEN) {
|
|
588
|
+
const msg = {
|
|
589
|
+
v: 1,
|
|
590
|
+
id: `evt-${Date.now()}`,
|
|
591
|
+
ns: "terminal",
|
|
592
|
+
action: "state",
|
|
593
|
+
payload: {
|
|
594
|
+
terminalId: event.id,
|
|
595
|
+
buffer: event.buffer,
|
|
596
|
+
cursorX: event.cursorX,
|
|
597
|
+
cursorY: event.cursorY,
|
|
598
|
+
cols: event.cols,
|
|
599
|
+
rows: event.rows,
|
|
600
|
+
},
|
|
601
|
+
};
|
|
602
|
+
dataChannel.send(JSON.stringify(msg));
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
else if (event.event === "exit") {
|
|
606
|
+
terminals.delete(event.id);
|
|
607
|
+
if (dataChannel && dataChannel.readyState === WebSocket.OPEN) {
|
|
608
|
+
const msg = {
|
|
609
|
+
v: 1,
|
|
610
|
+
id: `evt-${Date.now()}`,
|
|
611
|
+
ns: "terminal",
|
|
612
|
+
action: "exit",
|
|
613
|
+
payload: { terminalId: event.id, code: event.code },
|
|
614
|
+
};
|
|
615
|
+
dataChannel.send(JSON.stringify(msg));
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
else if (event.event === "error") {
|
|
619
|
+
console.error(`[pty] Error for ${event.id}: ${event.message}`);
|
|
577
620
|
}
|
|
578
621
|
});
|
|
622
|
+
}
|
|
623
|
+
function sendToPty(cmd) {
|
|
624
|
+
if (!ptyProcess || !ptyProcess.stdin) {
|
|
625
|
+
throw Object.assign(new Error("PTY process not running"), { code: "ENOPTY" });
|
|
626
|
+
}
|
|
627
|
+
ptyProcess.stdin.write(JSON.stringify(cmd) + "\n");
|
|
628
|
+
}
|
|
629
|
+
async function handleTerminalSpawn(payload) {
|
|
630
|
+
ensurePtyProcess();
|
|
631
|
+
const shell = payload.shell || process.env.SHELL || "/bin/sh";
|
|
632
|
+
const cols = payload.cols || 80;
|
|
633
|
+
const rows = payload.rows || 24;
|
|
634
|
+
const terminalId = `term-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
635
|
+
// Wait for the Rust binary to confirm spawn
|
|
636
|
+
const spawnPromise = new Promise((resolve, reject) => {
|
|
637
|
+
ptyPendingSpawns.set(terminalId, { resolve, reject });
|
|
638
|
+
setTimeout(() => {
|
|
639
|
+
if (ptyPendingSpawns.has(terminalId)) {
|
|
640
|
+
ptyPendingSpawns.delete(terminalId);
|
|
641
|
+
reject(new Error("Spawn timed out"));
|
|
642
|
+
}
|
|
643
|
+
}, 10000);
|
|
644
|
+
});
|
|
645
|
+
sendToPty({ cmd: "spawn", id: terminalId, shell, cols, rows });
|
|
646
|
+
await spawnPromise;
|
|
647
|
+
terminals.add(terminalId);
|
|
579
648
|
return { terminalId };
|
|
580
649
|
}
|
|
581
650
|
function handleTerminalWrite(payload) {
|
|
@@ -585,10 +654,9 @@ function handleTerminalWrite(payload) {
|
|
|
585
654
|
throw Object.assign(new Error("terminalId is required"), { code: "EINVAL" });
|
|
586
655
|
if (typeof data !== "string")
|
|
587
656
|
throw Object.assign(new Error("data is required"), { code: "EINVAL" });
|
|
588
|
-
|
|
589
|
-
if (!proc)
|
|
657
|
+
if (!terminals.has(terminalId))
|
|
590
658
|
throw Object.assign(new Error("Terminal not found"), { code: "ENOTERM" });
|
|
591
|
-
|
|
659
|
+
sendToPty({ cmd: "write", id: terminalId, data });
|
|
592
660
|
return {};
|
|
593
661
|
}
|
|
594
662
|
function handleTerminalResize(payload) {
|
|
@@ -597,21 +665,18 @@ function handleTerminalResize(payload) {
|
|
|
597
665
|
const rows = payload.rows;
|
|
598
666
|
if (!terminalId)
|
|
599
667
|
throw Object.assign(new Error("terminalId is required"), { code: "EINVAL" });
|
|
600
|
-
|
|
601
|
-
if (!proc)
|
|
668
|
+
if (!terminals.has(terminalId))
|
|
602
669
|
throw Object.assign(new Error("Terminal not found"), { code: "ENOTERM" });
|
|
603
|
-
|
|
604
|
-
// This is a simplified version
|
|
670
|
+
sendToPty({ cmd: "resize", id: terminalId, cols, rows });
|
|
605
671
|
return {};
|
|
606
672
|
}
|
|
607
673
|
function handleTerminalKill(payload) {
|
|
608
674
|
const terminalId = payload.terminalId;
|
|
609
675
|
if (!terminalId)
|
|
610
676
|
throw Object.assign(new Error("terminalId is required"), { code: "EINVAL" });
|
|
611
|
-
|
|
612
|
-
if (!proc)
|
|
677
|
+
if (!terminals.has(terminalId))
|
|
613
678
|
throw Object.assign(new Error("Terminal not found"), { code: "ENOTERM" });
|
|
614
|
-
|
|
679
|
+
sendToPty({ cmd: "kill", id: terminalId });
|
|
615
680
|
terminals.delete(terminalId);
|
|
616
681
|
return {};
|
|
617
682
|
}
|
|
@@ -621,7 +686,7 @@ function handleTerminalKill(payload) {
|
|
|
621
686
|
function handleSystemCapabilities() {
|
|
622
687
|
return {
|
|
623
688
|
version: VERSION,
|
|
624
|
-
namespaces: ["fs", "git", "terminal", "processes", "ports", "monitor", "http", "proxy"],
|
|
689
|
+
namespaces: ["fs", "git", "terminal", "processes", "ports", "monitor", "http", "ai", "proxy"],
|
|
625
690
|
platform: os.platform(),
|
|
626
691
|
rootDir: ROOT_DIR,
|
|
627
692
|
hostname: os.hostname(),
|
|
@@ -1085,6 +1150,145 @@ async function handleHttpRequest(payload) {
|
|
|
1085
1150
|
}
|
|
1086
1151
|
}
|
|
1087
1152
|
// ============================================================================
|
|
1153
|
+
// AI Handlers (OpenCode SDK)
|
|
1154
|
+
// ============================================================================
|
|
1155
|
+
async function handleAiCreateSession(payload) {
|
|
1156
|
+
const title = payload.title || undefined;
|
|
1157
|
+
const response = await opencodeClient.session.create({ body: { title } });
|
|
1158
|
+
return { session: response.data };
|
|
1159
|
+
}
|
|
1160
|
+
async function handleAiListSessions() {
|
|
1161
|
+
const response = await opencodeClient.session.list();
|
|
1162
|
+
return { sessions: response.data };
|
|
1163
|
+
}
|
|
1164
|
+
async function handleAiGetSession(payload) {
|
|
1165
|
+
const id = payload.id;
|
|
1166
|
+
const response = await opencodeClient.session.get({ path: { id } });
|
|
1167
|
+
return { session: response.data };
|
|
1168
|
+
}
|
|
1169
|
+
async function handleAiDeleteSession(payload) {
|
|
1170
|
+
const id = payload.id;
|
|
1171
|
+
await opencodeClient.session.delete({ path: { id } });
|
|
1172
|
+
return {};
|
|
1173
|
+
}
|
|
1174
|
+
async function handleAiGetMessages(payload) {
|
|
1175
|
+
const id = payload.id;
|
|
1176
|
+
const response = await opencodeClient.session.messages({ path: { id } });
|
|
1177
|
+
return { messages: response.data };
|
|
1178
|
+
}
|
|
1179
|
+
async function handleAiPrompt(payload) {
|
|
1180
|
+
const sessionId = payload.sessionId;
|
|
1181
|
+
const text = payload.text;
|
|
1182
|
+
const model = payload.model;
|
|
1183
|
+
// Fire and forget — results stream via SSE events forwarded on data channel
|
|
1184
|
+
opencodeClient.session.prompt({
|
|
1185
|
+
path: { id: sessionId },
|
|
1186
|
+
body: {
|
|
1187
|
+
parts: [{ type: "text", text }],
|
|
1188
|
+
...(model ? { model } : {}),
|
|
1189
|
+
},
|
|
1190
|
+
}).catch((err) => {
|
|
1191
|
+
if (dataChannel && dataChannel.readyState === WebSocket.OPEN) {
|
|
1192
|
+
dataChannel.send(JSON.stringify({
|
|
1193
|
+
v: 1,
|
|
1194
|
+
id: `evt-${Date.now()}`,
|
|
1195
|
+
ns: "ai",
|
|
1196
|
+
action: "event",
|
|
1197
|
+
payload: {
|
|
1198
|
+
type: "prompt_error",
|
|
1199
|
+
properties: { sessionId, error: err.message },
|
|
1200
|
+
},
|
|
1201
|
+
}));
|
|
1202
|
+
}
|
|
1203
|
+
});
|
|
1204
|
+
return { ack: true };
|
|
1205
|
+
}
|
|
1206
|
+
async function handleAiAbort(payload) {
|
|
1207
|
+
const id = payload.sessionId;
|
|
1208
|
+
await opencodeClient.session.abort({ path: { id } });
|
|
1209
|
+
return {};
|
|
1210
|
+
}
|
|
1211
|
+
async function handleAiAgents() {
|
|
1212
|
+
const response = await opencodeClient.app.agents();
|
|
1213
|
+
return { agents: response.data };
|
|
1214
|
+
}
|
|
1215
|
+
async function handleAiProviders() {
|
|
1216
|
+
const response = await opencodeClient.config.providers();
|
|
1217
|
+
return { providers: response.data };
|
|
1218
|
+
}
|
|
1219
|
+
async function handleAiSetAuth(payload) {
|
|
1220
|
+
const providerId = payload.providerId;
|
|
1221
|
+
const key = payload.key;
|
|
1222
|
+
await opencodeClient.auth.set({
|
|
1223
|
+
path: { id: providerId },
|
|
1224
|
+
body: { type: "api", key },
|
|
1225
|
+
});
|
|
1226
|
+
return {};
|
|
1227
|
+
}
|
|
1228
|
+
async function handleAiCommand(payload) {
|
|
1229
|
+
const sessionId = payload.sessionId;
|
|
1230
|
+
const command = payload.command;
|
|
1231
|
+
const args = payload.arguments || "";
|
|
1232
|
+
const response = await opencodeClient.session.command({
|
|
1233
|
+
path: { id: sessionId },
|
|
1234
|
+
body: { command, arguments: args },
|
|
1235
|
+
});
|
|
1236
|
+
return { result: response.data };
|
|
1237
|
+
}
|
|
1238
|
+
async function handleAiRevert(payload) {
|
|
1239
|
+
const sessionId = payload.sessionId;
|
|
1240
|
+
const messageId = payload.messageId;
|
|
1241
|
+
await opencodeClient.session.revert({
|
|
1242
|
+
path: { id: sessionId },
|
|
1243
|
+
body: { messageID: messageId },
|
|
1244
|
+
});
|
|
1245
|
+
return {};
|
|
1246
|
+
}
|
|
1247
|
+
async function handleAiUnrevert(payload) {
|
|
1248
|
+
const sessionId = payload.sessionId;
|
|
1249
|
+
await opencodeClient.session.unrevert({ path: { id: sessionId } });
|
|
1250
|
+
return {};
|
|
1251
|
+
}
|
|
1252
|
+
async function handleAiShare(payload) {
|
|
1253
|
+
const sessionId = payload.sessionId;
|
|
1254
|
+
const response = await opencodeClient.session.share({ path: { id: sessionId } });
|
|
1255
|
+
return { share: response.data };
|
|
1256
|
+
}
|
|
1257
|
+
async function handleAiPermissionReply(payload) {
|
|
1258
|
+
const permissionId = payload.permissionId;
|
|
1259
|
+
const sessionId = payload.sessionId;
|
|
1260
|
+
const approved = payload.approved;
|
|
1261
|
+
await opencodeClient.postSessionIdPermissionsPermissionId({
|
|
1262
|
+
path: { id: sessionId, permissionID: permissionId },
|
|
1263
|
+
body: { response: approved ? "once" : "reject" },
|
|
1264
|
+
});
|
|
1265
|
+
return {};
|
|
1266
|
+
}
|
|
1267
|
+
// SSE event forwarding from OpenCode to mobile app
|
|
1268
|
+
async function subscribeToOpenCodeEvents(client) {
|
|
1269
|
+
try {
|
|
1270
|
+
const events = await client.event.subscribe();
|
|
1271
|
+
for await (const event of events.stream) {
|
|
1272
|
+
if (dataChannel && dataChannel.readyState === WebSocket.OPEN) {
|
|
1273
|
+
const msg = {
|
|
1274
|
+
v: 1,
|
|
1275
|
+
id: `evt-${Date.now()}-${Math.random().toString(36).substring(2, 6)}`,
|
|
1276
|
+
ns: "ai",
|
|
1277
|
+
action: "event",
|
|
1278
|
+
payload: {
|
|
1279
|
+
type: event.type,
|
|
1280
|
+
properties: event.properties,
|
|
1281
|
+
},
|
|
1282
|
+
};
|
|
1283
|
+
dataChannel.send(JSON.stringify(msg));
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
catch (err) {
|
|
1288
|
+
console.error("OpenCode event stream error:", err);
|
|
1289
|
+
setTimeout(() => subscribeToOpenCodeEvents(client), 3000);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1088
1292
|
// Proxy Handlers
|
|
1089
1293
|
// ============================================================================
|
|
1090
1294
|
async function scanDevPorts() {
|
|
@@ -1313,7 +1517,7 @@ async function processMessage(message) {
|
|
|
1313
1517
|
case "terminal":
|
|
1314
1518
|
switch (action) {
|
|
1315
1519
|
case "spawn":
|
|
1316
|
-
result = handleTerminalSpawn(payload);
|
|
1520
|
+
result = await handleTerminalSpawn(payload);
|
|
1317
1521
|
break;
|
|
1318
1522
|
case "write":
|
|
1319
1523
|
result = handleTerminalWrite(payload);
|
|
@@ -1394,6 +1598,57 @@ async function processMessage(message) {
|
|
|
1394
1598
|
throw Object.assign(new Error(`Unknown action: ${ns}.${action}`), { code: "EINVAL" });
|
|
1395
1599
|
}
|
|
1396
1600
|
break;
|
|
1601
|
+
case "ai":
|
|
1602
|
+
switch (action) {
|
|
1603
|
+
case "prompt":
|
|
1604
|
+
result = await handleAiPrompt(payload);
|
|
1605
|
+
break;
|
|
1606
|
+
case "createSession":
|
|
1607
|
+
result = await handleAiCreateSession(payload);
|
|
1608
|
+
break;
|
|
1609
|
+
case "listSessions":
|
|
1610
|
+
result = await handleAiListSessions();
|
|
1611
|
+
break;
|
|
1612
|
+
case "getSession":
|
|
1613
|
+
result = await handleAiGetSession(payload);
|
|
1614
|
+
break;
|
|
1615
|
+
case "deleteSession":
|
|
1616
|
+
result = await handleAiDeleteSession(payload);
|
|
1617
|
+
break;
|
|
1618
|
+
case "getMessages":
|
|
1619
|
+
result = await handleAiGetMessages(payload);
|
|
1620
|
+
break;
|
|
1621
|
+
case "abort":
|
|
1622
|
+
result = await handleAiAbort(payload);
|
|
1623
|
+
break;
|
|
1624
|
+
case "agents":
|
|
1625
|
+
result = await handleAiAgents();
|
|
1626
|
+
break;
|
|
1627
|
+
case "providers":
|
|
1628
|
+
result = await handleAiProviders();
|
|
1629
|
+
break;
|
|
1630
|
+
case "setAuth":
|
|
1631
|
+
result = await handleAiSetAuth(payload);
|
|
1632
|
+
break;
|
|
1633
|
+
case "command":
|
|
1634
|
+
result = await handleAiCommand(payload);
|
|
1635
|
+
break;
|
|
1636
|
+
case "revert":
|
|
1637
|
+
result = await handleAiRevert(payload);
|
|
1638
|
+
break;
|
|
1639
|
+
case "unrevert":
|
|
1640
|
+
result = await handleAiUnrevert(payload);
|
|
1641
|
+
break;
|
|
1642
|
+
case "share":
|
|
1643
|
+
result = await handleAiShare(payload);
|
|
1644
|
+
break;
|
|
1645
|
+
case "permission":
|
|
1646
|
+
result = await handleAiPermissionReply(payload);
|
|
1647
|
+
break;
|
|
1648
|
+
default:
|
|
1649
|
+
throw Object.assign(new Error(`Unknown action: ${ns}.${action}`), { code: "EINVAL" });
|
|
1650
|
+
}
|
|
1651
|
+
break;
|
|
1397
1652
|
case "proxy":
|
|
1398
1653
|
switch (action) {
|
|
1399
1654
|
case "connect":
|
|
@@ -1557,9 +1812,10 @@ function connectWebSocket(code) {
|
|
|
1557
1812
|
// Handle graceful shutdown
|
|
1558
1813
|
process.on("SIGINT", () => {
|
|
1559
1814
|
console.log("\nShutting down...");
|
|
1560
|
-
// Kill all terminals
|
|
1561
|
-
|
|
1562
|
-
|
|
1815
|
+
// Kill PTY process (kills all terminals)
|
|
1816
|
+
if (ptyProcess) {
|
|
1817
|
+
ptyProcess.kill();
|
|
1818
|
+
ptyProcess = null;
|
|
1563
1819
|
}
|
|
1564
1820
|
terminals.clear();
|
|
1565
1821
|
// Kill all managed processes
|
|
@@ -1578,6 +1834,13 @@ async function main() {
|
|
|
1578
1834
|
console.log("Lunel CLI v" + VERSION);
|
|
1579
1835
|
console.log("=".repeat(20) + "\n");
|
|
1580
1836
|
try {
|
|
1837
|
+
// Start OpenCode server + client
|
|
1838
|
+
console.log("Starting OpenCode...");
|
|
1839
|
+
const { client } = await createOpencode();
|
|
1840
|
+
opencodeClient = client;
|
|
1841
|
+
console.log("OpenCode ready.\n");
|
|
1842
|
+
// Subscribe to OpenCode events
|
|
1843
|
+
subscribeToOpenCodeEvents(client);
|
|
1581
1844
|
const code = await createSession();
|
|
1582
1845
|
displayQR(code);
|
|
1583
1846
|
connectWebSocket(code);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lunel-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
4
4
|
"author": [
|
|
5
5
|
{
|
|
6
6
|
"name": "Soham Bharambe",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"lunel-cli": "./dist/index.js"
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
|
-
"dist"
|
|
20
|
+
"dist",
|
|
21
|
+
"bin"
|
|
21
22
|
],
|
|
22
23
|
"scripts": {
|
|
23
24
|
"build": "tsc",
|
|
@@ -25,6 +26,7 @@
|
|
|
25
26
|
"prepublishOnly": "npm run build"
|
|
26
27
|
},
|
|
27
28
|
"dependencies": {
|
|
29
|
+
"@opencode-ai/sdk": "^1.1.56",
|
|
28
30
|
"ignore": "^6.0.2",
|
|
29
31
|
"qrcode-terminal": "^0.12.0",
|
|
30
32
|
"ws": "^8.18.0"
|