lunel-cli 0.1.15 → 0.1.16
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 +204 -2
- package/package.json +2 -1
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";
|
|
@@ -9,7 +10,9 @@ import * as os from "os";
|
|
|
9
10
|
import { spawn, execSync } from "child_process";
|
|
10
11
|
import { createServer, createConnection } from "net";
|
|
11
12
|
const PROXY_URL = process.env.LUNEL_PROXY_URL || "https://gateway.lunel.dev";
|
|
12
|
-
|
|
13
|
+
import { createRequire } from "module";
|
|
14
|
+
const __require = createRequire(import.meta.url);
|
|
15
|
+
const VERSION = __require("../package.json").version;
|
|
13
16
|
// Root directory - sandbox all file operations to this
|
|
14
17
|
const ROOT_DIR = process.cwd();
|
|
15
18
|
// Terminal sessions
|
|
@@ -18,6 +21,8 @@ const processes = new Map();
|
|
|
18
21
|
const processOutputBuffers = new Map();
|
|
19
22
|
// CPU usage tracking
|
|
20
23
|
let lastCpuInfo = null;
|
|
24
|
+
// OpenCode client
|
|
25
|
+
let opencodeClient = null;
|
|
21
26
|
// Proxy tunnel management
|
|
22
27
|
let currentSessionCode = null;
|
|
23
28
|
const activeTunnels = new Map();
|
|
@@ -621,7 +626,7 @@ function handleTerminalKill(payload) {
|
|
|
621
626
|
function handleSystemCapabilities() {
|
|
622
627
|
return {
|
|
623
628
|
version: VERSION,
|
|
624
|
-
namespaces: ["fs", "git", "terminal", "processes", "ports", "monitor", "http", "proxy"],
|
|
629
|
+
namespaces: ["fs", "git", "terminal", "processes", "ports", "monitor", "http", "ai", "proxy"],
|
|
625
630
|
platform: os.platform(),
|
|
626
631
|
rootDir: ROOT_DIR,
|
|
627
632
|
hostname: os.hostname(),
|
|
@@ -1085,6 +1090,145 @@ async function handleHttpRequest(payload) {
|
|
|
1085
1090
|
}
|
|
1086
1091
|
}
|
|
1087
1092
|
// ============================================================================
|
|
1093
|
+
// AI Handlers (OpenCode SDK)
|
|
1094
|
+
// ============================================================================
|
|
1095
|
+
async function handleAiCreateSession(payload) {
|
|
1096
|
+
const title = payload.title || undefined;
|
|
1097
|
+
const response = await opencodeClient.session.create({ body: { title } });
|
|
1098
|
+
return { session: response.data };
|
|
1099
|
+
}
|
|
1100
|
+
async function handleAiListSessions() {
|
|
1101
|
+
const response = await opencodeClient.session.list();
|
|
1102
|
+
return { sessions: response.data };
|
|
1103
|
+
}
|
|
1104
|
+
async function handleAiGetSession(payload) {
|
|
1105
|
+
const id = payload.id;
|
|
1106
|
+
const response = await opencodeClient.session.get({ path: { id } });
|
|
1107
|
+
return { session: response.data };
|
|
1108
|
+
}
|
|
1109
|
+
async function handleAiDeleteSession(payload) {
|
|
1110
|
+
const id = payload.id;
|
|
1111
|
+
await opencodeClient.session.delete({ path: { id } });
|
|
1112
|
+
return {};
|
|
1113
|
+
}
|
|
1114
|
+
async function handleAiGetMessages(payload) {
|
|
1115
|
+
const id = payload.id;
|
|
1116
|
+
const response = await opencodeClient.session.messages({ path: { id } });
|
|
1117
|
+
return { messages: response.data };
|
|
1118
|
+
}
|
|
1119
|
+
async function handleAiPrompt(payload) {
|
|
1120
|
+
const sessionId = payload.sessionId;
|
|
1121
|
+
const text = payload.text;
|
|
1122
|
+
const model = payload.model;
|
|
1123
|
+
// Fire and forget — results stream via SSE events forwarded on data channel
|
|
1124
|
+
opencodeClient.session.prompt({
|
|
1125
|
+
path: { id: sessionId },
|
|
1126
|
+
body: {
|
|
1127
|
+
parts: [{ type: "text", text }],
|
|
1128
|
+
...(model ? { model } : {}),
|
|
1129
|
+
},
|
|
1130
|
+
}).catch((err) => {
|
|
1131
|
+
if (dataChannel && dataChannel.readyState === WebSocket.OPEN) {
|
|
1132
|
+
dataChannel.send(JSON.stringify({
|
|
1133
|
+
v: 1,
|
|
1134
|
+
id: `evt-${Date.now()}`,
|
|
1135
|
+
ns: "ai",
|
|
1136
|
+
action: "event",
|
|
1137
|
+
payload: {
|
|
1138
|
+
type: "prompt_error",
|
|
1139
|
+
properties: { sessionId, error: err.message },
|
|
1140
|
+
},
|
|
1141
|
+
}));
|
|
1142
|
+
}
|
|
1143
|
+
});
|
|
1144
|
+
return { ack: true };
|
|
1145
|
+
}
|
|
1146
|
+
async function handleAiAbort(payload) {
|
|
1147
|
+
const id = payload.sessionId;
|
|
1148
|
+
await opencodeClient.session.abort({ path: { id } });
|
|
1149
|
+
return {};
|
|
1150
|
+
}
|
|
1151
|
+
async function handleAiAgents() {
|
|
1152
|
+
const response = await opencodeClient.app.agents();
|
|
1153
|
+
return { agents: response.data };
|
|
1154
|
+
}
|
|
1155
|
+
async function handleAiProviders() {
|
|
1156
|
+
const response = await opencodeClient.config.providers();
|
|
1157
|
+
return { providers: response.data };
|
|
1158
|
+
}
|
|
1159
|
+
async function handleAiSetAuth(payload) {
|
|
1160
|
+
const providerId = payload.providerId;
|
|
1161
|
+
const key = payload.key;
|
|
1162
|
+
await opencodeClient.auth.set({
|
|
1163
|
+
path: { id: providerId },
|
|
1164
|
+
body: { type: "api", key },
|
|
1165
|
+
});
|
|
1166
|
+
return {};
|
|
1167
|
+
}
|
|
1168
|
+
async function handleAiCommand(payload) {
|
|
1169
|
+
const sessionId = payload.sessionId;
|
|
1170
|
+
const command = payload.command;
|
|
1171
|
+
const args = payload.arguments || "";
|
|
1172
|
+
const response = await opencodeClient.session.command({
|
|
1173
|
+
path: { id: sessionId },
|
|
1174
|
+
body: { command, arguments: args },
|
|
1175
|
+
});
|
|
1176
|
+
return { result: response.data };
|
|
1177
|
+
}
|
|
1178
|
+
async function handleAiRevert(payload) {
|
|
1179
|
+
const sessionId = payload.sessionId;
|
|
1180
|
+
const messageId = payload.messageId;
|
|
1181
|
+
await opencodeClient.session.revert({
|
|
1182
|
+
path: { id: sessionId },
|
|
1183
|
+
body: { messageID: messageId },
|
|
1184
|
+
});
|
|
1185
|
+
return {};
|
|
1186
|
+
}
|
|
1187
|
+
async function handleAiUnrevert(payload) {
|
|
1188
|
+
const sessionId = payload.sessionId;
|
|
1189
|
+
await opencodeClient.session.unrevert({ path: { id: sessionId } });
|
|
1190
|
+
return {};
|
|
1191
|
+
}
|
|
1192
|
+
async function handleAiShare(payload) {
|
|
1193
|
+
const sessionId = payload.sessionId;
|
|
1194
|
+
const response = await opencodeClient.session.share({ path: { id: sessionId } });
|
|
1195
|
+
return { share: response.data };
|
|
1196
|
+
}
|
|
1197
|
+
async function handleAiPermissionReply(payload) {
|
|
1198
|
+
const permissionId = payload.permissionId;
|
|
1199
|
+
const sessionId = payload.sessionId;
|
|
1200
|
+
const approved = payload.approved;
|
|
1201
|
+
await opencodeClient.postSessionIdPermissionsPermissionId({
|
|
1202
|
+
path: { id: sessionId, permissionID: permissionId },
|
|
1203
|
+
body: { response: approved ? "once" : "reject" },
|
|
1204
|
+
});
|
|
1205
|
+
return {};
|
|
1206
|
+
}
|
|
1207
|
+
// SSE event forwarding from OpenCode to mobile app
|
|
1208
|
+
async function subscribeToOpenCodeEvents(client) {
|
|
1209
|
+
try {
|
|
1210
|
+
const events = await client.event.subscribe();
|
|
1211
|
+
for await (const event of events.stream) {
|
|
1212
|
+
if (dataChannel && dataChannel.readyState === WebSocket.OPEN) {
|
|
1213
|
+
const msg = {
|
|
1214
|
+
v: 1,
|
|
1215
|
+
id: `evt-${Date.now()}-${Math.random().toString(36).substring(2, 6)}`,
|
|
1216
|
+
ns: "ai",
|
|
1217
|
+
action: "event",
|
|
1218
|
+
payload: {
|
|
1219
|
+
type: event.type,
|
|
1220
|
+
properties: event.properties,
|
|
1221
|
+
},
|
|
1222
|
+
};
|
|
1223
|
+
dataChannel.send(JSON.stringify(msg));
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
catch (err) {
|
|
1228
|
+
console.error("OpenCode event stream error:", err);
|
|
1229
|
+
setTimeout(() => subscribeToOpenCodeEvents(client), 3000);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1088
1232
|
// Proxy Handlers
|
|
1089
1233
|
// ============================================================================
|
|
1090
1234
|
async function scanDevPorts() {
|
|
@@ -1394,6 +1538,57 @@ async function processMessage(message) {
|
|
|
1394
1538
|
throw Object.assign(new Error(`Unknown action: ${ns}.${action}`), { code: "EINVAL" });
|
|
1395
1539
|
}
|
|
1396
1540
|
break;
|
|
1541
|
+
case "ai":
|
|
1542
|
+
switch (action) {
|
|
1543
|
+
case "prompt":
|
|
1544
|
+
result = await handleAiPrompt(payload);
|
|
1545
|
+
break;
|
|
1546
|
+
case "createSession":
|
|
1547
|
+
result = await handleAiCreateSession(payload);
|
|
1548
|
+
break;
|
|
1549
|
+
case "listSessions":
|
|
1550
|
+
result = await handleAiListSessions();
|
|
1551
|
+
break;
|
|
1552
|
+
case "getSession":
|
|
1553
|
+
result = await handleAiGetSession(payload);
|
|
1554
|
+
break;
|
|
1555
|
+
case "deleteSession":
|
|
1556
|
+
result = await handleAiDeleteSession(payload);
|
|
1557
|
+
break;
|
|
1558
|
+
case "getMessages":
|
|
1559
|
+
result = await handleAiGetMessages(payload);
|
|
1560
|
+
break;
|
|
1561
|
+
case "abort":
|
|
1562
|
+
result = await handleAiAbort(payload);
|
|
1563
|
+
break;
|
|
1564
|
+
case "agents":
|
|
1565
|
+
result = await handleAiAgents();
|
|
1566
|
+
break;
|
|
1567
|
+
case "providers":
|
|
1568
|
+
result = await handleAiProviders();
|
|
1569
|
+
break;
|
|
1570
|
+
case "setAuth":
|
|
1571
|
+
result = await handleAiSetAuth(payload);
|
|
1572
|
+
break;
|
|
1573
|
+
case "command":
|
|
1574
|
+
result = await handleAiCommand(payload);
|
|
1575
|
+
break;
|
|
1576
|
+
case "revert":
|
|
1577
|
+
result = await handleAiRevert(payload);
|
|
1578
|
+
break;
|
|
1579
|
+
case "unrevert":
|
|
1580
|
+
result = await handleAiUnrevert(payload);
|
|
1581
|
+
break;
|
|
1582
|
+
case "share":
|
|
1583
|
+
result = await handleAiShare(payload);
|
|
1584
|
+
break;
|
|
1585
|
+
case "permission":
|
|
1586
|
+
result = await handleAiPermissionReply(payload);
|
|
1587
|
+
break;
|
|
1588
|
+
default:
|
|
1589
|
+
throw Object.assign(new Error(`Unknown action: ${ns}.${action}`), { code: "EINVAL" });
|
|
1590
|
+
}
|
|
1591
|
+
break;
|
|
1397
1592
|
case "proxy":
|
|
1398
1593
|
switch (action) {
|
|
1399
1594
|
case "connect":
|
|
@@ -1578,6 +1773,13 @@ async function main() {
|
|
|
1578
1773
|
console.log("Lunel CLI v" + VERSION);
|
|
1579
1774
|
console.log("=".repeat(20) + "\n");
|
|
1580
1775
|
try {
|
|
1776
|
+
// Start OpenCode server + client
|
|
1777
|
+
console.log("Starting OpenCode...");
|
|
1778
|
+
const { client } = await createOpencode();
|
|
1779
|
+
opencodeClient = client;
|
|
1780
|
+
console.log("OpenCode ready.\n");
|
|
1781
|
+
// Subscribe to OpenCode events
|
|
1782
|
+
subscribeToOpenCodeEvents(client);
|
|
1581
1783
|
const code = await createSession();
|
|
1582
1784
|
displayQR(code);
|
|
1583
1785
|
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.16",
|
|
4
4
|
"author": [
|
|
5
5
|
{
|
|
6
6
|
"name": "Soham Bharambe",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"prepublishOnly": "npm run build"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
+
"@opencode-ai/sdk": "^1.1.56",
|
|
28
29
|
"ignore": "^6.0.2",
|
|
29
30
|
"qrcode-terminal": "^0.12.0",
|
|
30
31
|
"ws": "^8.18.0"
|