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.
Files changed (2) hide show
  1. package/dist/index.js +204 -2
  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
- const VERSION = "0.1.3";
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.15",
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"