lunel-cli 0.1.82 → 0.1.83
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/ai/codex.js +5 -2
- package/dist/ai/index.js +7 -2
- package/dist/ai/opencode.js +7 -4
- package/dist/index.js +68 -26
- package/package.json +1 -1
package/dist/ai/codex.js
CHANGED
|
@@ -6,6 +6,7 @@ import * as path from "path";
|
|
|
6
6
|
import { spawn } from "child_process";
|
|
7
7
|
import { createInterface } from "readline";
|
|
8
8
|
const THREAD_LIST_SOURCE_KINDS = ["cli", "vscode", "appServer", "exec", "unknown"];
|
|
9
|
+
const DEBUG_MODE = process.env.LUNEL_DEBUG === "1" || process.env.LUNEL_DEBUG_AI === "1";
|
|
9
10
|
export class CodexProvider {
|
|
10
11
|
proc = null;
|
|
11
12
|
shuttingDown = false;
|
|
@@ -19,7 +20,8 @@ export class CodexProvider {
|
|
|
19
20
|
assistantMessageIdByTurnId = new Map();
|
|
20
21
|
partTextById = new Map();
|
|
21
22
|
async init() {
|
|
22
|
-
|
|
23
|
+
if (DEBUG_MODE)
|
|
24
|
+
console.log("Starting Codex app-server...");
|
|
23
25
|
this.proc = spawn("codex", ["app-server"], {
|
|
24
26
|
stdio: ["pipe", "pipe", "inherit"],
|
|
25
27
|
env: process.env,
|
|
@@ -38,7 +40,8 @@ export class CodexProvider {
|
|
|
38
40
|
}
|
|
39
41
|
});
|
|
40
42
|
await this.call("initialize", { clientInfo: { name: "lunel", version: "1.0" } });
|
|
41
|
-
|
|
43
|
+
if (DEBUG_MODE)
|
|
44
|
+
console.log("Codex ready.\n");
|
|
42
45
|
}
|
|
43
46
|
async destroy() {
|
|
44
47
|
this.shuttingDown = true;
|
package/dist/ai/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// AI manager — runs both OpenCode and Codex simultaneously and routes calls
|
|
2
2
|
// by the `backend` field in each request. Backends that fail to init are
|
|
3
3
|
// skipped gracefully; the available list is exposed to the app.
|
|
4
|
+
const DEBUG_MODE = process.env.LUNEL_DEBUG === "1" || process.env.LUNEL_DEBUG_AI === "1";
|
|
4
5
|
export class AiManager {
|
|
5
6
|
_providers = {};
|
|
6
7
|
_available = [];
|
|
@@ -12,7 +13,9 @@ export class AiManager {
|
|
|
12
13
|
if (this._available.length === 0) {
|
|
13
14
|
throw new Error("No AI backend could be started. Ensure opencode or codex is installed.");
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
+
if (DEBUG_MODE) {
|
|
17
|
+
console.log(`[ai] Available backends: ${this._available.join(", ")}`);
|
|
18
|
+
}
|
|
16
19
|
}
|
|
17
20
|
async tryInit(backend) {
|
|
18
21
|
try {
|
|
@@ -31,7 +34,9 @@ export class AiManager {
|
|
|
31
34
|
this._available.push(backend);
|
|
32
35
|
}
|
|
33
36
|
catch (err) {
|
|
34
|
-
|
|
37
|
+
if (DEBUG_MODE) {
|
|
38
|
+
console.warn(`[ai] ${backend} backend unavailable: ${err.message}`);
|
|
39
|
+
}
|
|
35
40
|
}
|
|
36
41
|
}
|
|
37
42
|
availableBackends() {
|
package/dist/ai/opencode.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// All logic extracted verbatim from cli/src/index.ts AI handlers section.
|
|
3
3
|
import * as crypto from "crypto";
|
|
4
4
|
import { createOpencodeServer, createOpencodeClient } from "@opencode-ai/sdk";
|
|
5
|
-
const VERBOSE_AI_LOGS = process.env.LUNEL_DEBUG_AI === "1";
|
|
5
|
+
const VERBOSE_AI_LOGS = process.env.LUNEL_DEBUG === "1" || process.env.LUNEL_DEBUG_AI === "1";
|
|
6
6
|
const SSE_BACKOFF_INITIAL_MS = 500;
|
|
7
7
|
const SSE_BACKOFF_CAP_MS = 30_000;
|
|
8
8
|
const SSE_MAX_RETRIES = 20;
|
|
@@ -39,18 +39,21 @@ export class OpenCodeProvider {
|
|
|
39
39
|
process.env.OPENCODE_SERVER_USERNAME = opencodeUsername;
|
|
40
40
|
process.env.OPENCODE_SERVER_PASSWORD = opencodePassword;
|
|
41
41
|
this.authHeader = authHeader;
|
|
42
|
-
|
|
42
|
+
if (VERBOSE_AI_LOGS)
|
|
43
|
+
console.log("Starting OpenCode...");
|
|
43
44
|
this.server = await createOpencodeServer({
|
|
44
45
|
hostname: "127.0.0.1",
|
|
45
46
|
port: 0,
|
|
46
47
|
timeout: 15000,
|
|
47
48
|
});
|
|
48
|
-
|
|
49
|
+
if (VERBOSE_AI_LOGS)
|
|
50
|
+
console.log(`OpenCode server listening on ${this.server.url}`);
|
|
49
51
|
this.client = createOpencodeClient({
|
|
50
52
|
baseUrl: this.server.url,
|
|
51
53
|
headers: { Authorization: authHeader },
|
|
52
54
|
});
|
|
53
|
-
|
|
55
|
+
if (VERBOSE_AI_LOGS)
|
|
56
|
+
console.log("OpenCode ready.\n");
|
|
54
57
|
}
|
|
55
58
|
async destroy() {
|
|
56
59
|
this.shuttingDown = true;
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,15 @@ import { createInterface } from "readline";
|
|
|
15
15
|
const DEFAULT_PROXY_URL = normalizeGatewayUrl(process.env.LUNEL_PROXY_URL || "https://gateway.lunel.dev");
|
|
16
16
|
const MANAGER_URL = normalizeGatewayUrl(process.env.LUNEL_MANAGER_URL || "https://manager.lunel.dev");
|
|
17
17
|
const CLI_ARGS = process.argv.slice(2);
|
|
18
|
+
function hasAnyFlag(args, ...flags) {
|
|
19
|
+
return flags.some((flag) => args.includes(flag));
|
|
20
|
+
}
|
|
21
|
+
const SHOW_HELP = hasAnyFlag(CLI_ARGS, "--help", "-h");
|
|
22
|
+
const DEBUG_MODE = hasAnyFlag(CLI_ARGS, "--debug", "-d");
|
|
23
|
+
if (DEBUG_MODE) {
|
|
24
|
+
process.env.LUNEL_DEBUG = "1";
|
|
25
|
+
process.env.LUNEL_DEBUG_AI = "1";
|
|
26
|
+
}
|
|
18
27
|
const APPLE_REVIEW_CODE = "abcd";
|
|
19
28
|
import { createRequire } from "module";
|
|
20
29
|
const __require = createRequire(import.meta.url);
|
|
@@ -62,10 +71,35 @@ let shuttingDown = false;
|
|
|
62
71
|
let activeControlWs = null;
|
|
63
72
|
let activeDataWs = null;
|
|
64
73
|
function logWithTimestamp(scope, message, fields) {
|
|
74
|
+
if (!DEBUG_MODE)
|
|
75
|
+
return;
|
|
65
76
|
const timestamp = new Date().toISOString();
|
|
66
77
|
const suffix = fields ? ` ${JSON.stringify(fields)}` : "";
|
|
67
78
|
console.log(`[${timestamp}] [${scope}] ${message}${suffix}`);
|
|
68
79
|
}
|
|
80
|
+
function debugLog(message, ...args) {
|
|
81
|
+
if (!DEBUG_MODE)
|
|
82
|
+
return;
|
|
83
|
+
console.log(message, ...args);
|
|
84
|
+
}
|
|
85
|
+
function debugWarn(message, ...args) {
|
|
86
|
+
if (!DEBUG_MODE)
|
|
87
|
+
return;
|
|
88
|
+
console.warn(message, ...args);
|
|
89
|
+
}
|
|
90
|
+
function printHelp() {
|
|
91
|
+
console.log(`Lunel CLI v${VERSION}
|
|
92
|
+
|
|
93
|
+
Usage:
|
|
94
|
+
npx lunel-cli [options]
|
|
95
|
+
|
|
96
|
+
Options:
|
|
97
|
+
-h, --help Show help
|
|
98
|
+
-n, --new Create a new session code
|
|
99
|
+
-d, --debug Show verbose debug logs
|
|
100
|
+
--extra-ports Extra local ports to expose, comma-separated (e.g. 3000,8080)
|
|
101
|
+
`);
|
|
102
|
+
}
|
|
69
103
|
const activeTunnels = new Map();
|
|
70
104
|
const PORT_SYNC_INTERVAL_MS = 30_000;
|
|
71
105
|
const CLI_LOCAL_TCP_CONNECT_TIMEOUT_MS = 2_500;
|
|
@@ -211,12 +245,9 @@ function parseExtraPortsFromArgs(args) {
|
|
|
211
245
|
}
|
|
212
246
|
return Array.from(parsed).sort((a, b) => a - b);
|
|
213
247
|
}
|
|
214
|
-
function hasFlag(args, flag) {
|
|
215
|
-
return args.includes(flag);
|
|
216
|
-
}
|
|
217
248
|
const EXTRA_PORTS = parseExtraPortsFromArgs(CLI_ARGS);
|
|
218
|
-
const USE_APPLE_REVIEW_CODE =
|
|
219
|
-
const FORCE_NEW_CODE =
|
|
249
|
+
const USE_APPLE_REVIEW_CODE = hasAnyFlag(CLI_ARGS, "--abcd-code");
|
|
250
|
+
const FORCE_NEW_CODE = hasAnyFlag(CLI_ARGS, "--new", "-n");
|
|
220
251
|
const SCAN_PORTS = Array.from(new Set([...DEV_PORTS, ...EXTRA_PORTS])).sort((a, b) => a - b);
|
|
221
252
|
function samePortSet(a, b) {
|
|
222
253
|
if (a.length !== b.length)
|
|
@@ -953,14 +984,14 @@ function e2eeHandlePeerHello(peerPubkeyB64, dataWs) {
|
|
|
953
984
|
e2eeSentReady = true;
|
|
954
985
|
if (e2eeGotReady) {
|
|
955
986
|
e2eeActive = true;
|
|
956
|
-
|
|
987
|
+
debugLog("[e2ee] encryption active");
|
|
957
988
|
}
|
|
958
989
|
}
|
|
959
990
|
function e2eeHandlePeerReady() {
|
|
960
991
|
e2eeGotReady = true;
|
|
961
992
|
if (e2eeSentReady) {
|
|
962
993
|
e2eeActive = true;
|
|
963
|
-
|
|
994
|
+
debugLog("[e2ee] encryption active");
|
|
964
995
|
}
|
|
965
996
|
}
|
|
966
997
|
function e2eeEncrypt(payload) {
|
|
@@ -1055,7 +1086,7 @@ function resumeDataChannel() {
|
|
|
1055
1086
|
lastSentSeq = entry.seq;
|
|
1056
1087
|
}
|
|
1057
1088
|
}
|
|
1058
|
-
|
|
1089
|
+
debugLog(`[backpressure] data channel resumed, flushed ${toFlush.length} buffered events`);
|
|
1059
1090
|
if (activeControlWs?.readyState === WebSocket.OPEN) {
|
|
1060
1091
|
const buffered = dataChannel?.bufferedAmount ?? 0;
|
|
1061
1092
|
activeControlWs.send(JSON.stringify({ type: "data_channel_resumed", bufferedBytes: buffered }));
|
|
@@ -1067,7 +1098,7 @@ function checkDataChannelBackpressure() {
|
|
|
1067
1098
|
const buffered = dataChannel.bufferedAmount ?? 0;
|
|
1068
1099
|
if (!dataChannelPaused && buffered > DATA_CHANNEL_HIGH_WATER_BYTES) {
|
|
1069
1100
|
dataChannelPaused = true;
|
|
1070
|
-
|
|
1101
|
+
debugWarn(`[backpressure] data channel paused (${buffered} bytes buffered)`);
|
|
1071
1102
|
if (activeControlWs?.readyState === WebSocket.OPEN) {
|
|
1072
1103
|
activeControlWs.send(JSON.stringify({ type: "data_channel_paused", bufferedBytes: buffered }));
|
|
1073
1104
|
}
|
|
@@ -1240,7 +1271,7 @@ async function ensurePtyProcess() {
|
|
|
1240
1271
|
console.error("[pty]", data.toString().trim());
|
|
1241
1272
|
});
|
|
1242
1273
|
ptyProcess.on("exit", (code) => {
|
|
1243
|
-
|
|
1274
|
+
debugLog(`[pty] PTY process exited with code ${code}`);
|
|
1244
1275
|
ptyProcess = null;
|
|
1245
1276
|
// Reject all pending spawns
|
|
1246
1277
|
for (const [id, pending] of ptyPendingSpawns) {
|
|
@@ -1941,7 +1972,7 @@ async function publishDiscoveredPorts(ws, force = false) {
|
|
|
1941
1972
|
action: "ports_discovered",
|
|
1942
1973
|
payload: { ports: openPorts },
|
|
1943
1974
|
}));
|
|
1944
|
-
|
|
1975
|
+
debugLog(`[proxy] ports updated (${openPorts.length}): ${openPorts.join(", ") || "-"}`);
|
|
1945
1976
|
}
|
|
1946
1977
|
catch (err) {
|
|
1947
1978
|
console.error("Port scan failed:", err);
|
|
@@ -2236,7 +2267,7 @@ async function processMessage(message) {
|
|
|
2236
2267
|
}
|
|
2237
2268
|
// Validate required fields
|
|
2238
2269
|
if (!ns || !action) {
|
|
2239
|
-
|
|
2270
|
+
debugWarn("[router] Ignoring message with missing ns/action:", redactSensitive(JSON.stringify(message).substring(0, 300)));
|
|
2240
2271
|
return {
|
|
2241
2272
|
v: 1,
|
|
2242
2273
|
id,
|
|
@@ -2519,7 +2550,9 @@ async function processMessage(message) {
|
|
|
2519
2550
|
}
|
|
2520
2551
|
catch (error) {
|
|
2521
2552
|
const err = error;
|
|
2522
|
-
|
|
2553
|
+
if (DEBUG_MODE) {
|
|
2554
|
+
console.error(`[router] ${ns}.${action} error:`, err.code || "ERROR", err.message);
|
|
2555
|
+
}
|
|
2523
2556
|
logWithTimestamp("router", "request failed", {
|
|
2524
2557
|
id,
|
|
2525
2558
|
ns,
|
|
@@ -2659,7 +2692,7 @@ async function getAssignedProxyUrl(password) {
|
|
|
2659
2692
|
}
|
|
2660
2693
|
return normalizeGatewayUrl(payload.proxyUrl);
|
|
2661
2694
|
}
|
|
2662
|
-
async function revokePassword(password, reason = "revoked by cli --new
|
|
2695
|
+
async function revokePassword(password, reason = "revoked by cli --new") {
|
|
2663
2696
|
const response = await fetch(new URL("/v2/revoke", MANAGER_URL), {
|
|
2664
2697
|
method: "POST",
|
|
2665
2698
|
headers: { "Content-Type": "application/json" },
|
|
@@ -2819,10 +2852,11 @@ async function connectWebSocket() {
|
|
|
2819
2852
|
sendResponseOnData(response, dataWs);
|
|
2820
2853
|
return;
|
|
2821
2854
|
}
|
|
2822
|
-
|
|
2855
|
+
debugWarn("[router] Ignoring non-request control frame");
|
|
2823
2856
|
}
|
|
2824
2857
|
catch (error) {
|
|
2825
|
-
|
|
2858
|
+
if (DEBUG_MODE)
|
|
2859
|
+
console.error("Error processing control message:", error);
|
|
2826
2860
|
}
|
|
2827
2861
|
});
|
|
2828
2862
|
controlWs.on("close", (code, reason) => {
|
|
@@ -2837,7 +2871,8 @@ async function connectWebSocket() {
|
|
|
2837
2871
|
failConnection(`control ws error: ${error.message}`);
|
|
2838
2872
|
return;
|
|
2839
2873
|
}
|
|
2840
|
-
|
|
2874
|
+
if (DEBUG_MODE)
|
|
2875
|
+
console.error("Control WebSocket error:", error.message);
|
|
2841
2876
|
});
|
|
2842
2877
|
dataWs.on("open", () => {
|
|
2843
2878
|
dataConnected = true;
|
|
@@ -2862,7 +2897,7 @@ async function connectWebSocket() {
|
|
|
2862
2897
|
e2eeReset();
|
|
2863
2898
|
const lastSeq = Number(raw.payload?.lastSeq ?? 0);
|
|
2864
2899
|
const toReplay = replayBuffer.filter((e) => e.seq > lastSeq);
|
|
2865
|
-
|
|
2900
|
+
debugLog(`[replay] replaying ${toReplay.length} messages after seq ${lastSeq}`);
|
|
2866
2901
|
// Replay without encryption — E2EE handshake hasn't completed yet
|
|
2867
2902
|
for (const entry of toReplay)
|
|
2868
2903
|
dataWs.send(JSON.stringify(entry.msg));
|
|
@@ -2889,10 +2924,11 @@ async function connectWebSocket() {
|
|
|
2889
2924
|
sendResponseOnData(response, dataWs);
|
|
2890
2925
|
return;
|
|
2891
2926
|
}
|
|
2892
|
-
|
|
2927
|
+
debugWarn("[router] Ignoring non-request data frame");
|
|
2893
2928
|
}
|
|
2894
2929
|
catch (error) {
|
|
2895
|
-
|
|
2930
|
+
if (DEBUG_MODE)
|
|
2931
|
+
console.error("Error processing data message:", error);
|
|
2896
2932
|
}
|
|
2897
2933
|
});
|
|
2898
2934
|
dataWs.on("close", (code, reason) => {
|
|
@@ -2913,7 +2949,8 @@ async function connectWebSocket() {
|
|
|
2913
2949
|
failConnection(`data ws error: ${error.message}`);
|
|
2914
2950
|
return;
|
|
2915
2951
|
}
|
|
2916
|
-
|
|
2952
|
+
if (DEBUG_MODE)
|
|
2953
|
+
console.error("Data WebSocket error:", error.message);
|
|
2917
2954
|
});
|
|
2918
2955
|
setTimeout(() => {
|
|
2919
2956
|
if (!settled) {
|
|
@@ -2939,16 +2976,21 @@ async function handleConnectionDrop(reason) {
|
|
|
2939
2976
|
try {
|
|
2940
2977
|
currentPrimaryGateway = await getAssignedProxyUrl(currentSessionPassword);
|
|
2941
2978
|
await connectWebSocket();
|
|
2942
|
-
|
|
2979
|
+
debugLog(`[reconnect] connected via ${activeGatewayUrl}`);
|
|
2943
2980
|
return;
|
|
2944
2981
|
}
|
|
2945
2982
|
catch (err) {
|
|
2946
|
-
|
|
2983
|
+
if (DEBUG_MODE)
|
|
2984
|
+
console.error(`[reconnect] attempt ${attempt} failed: ${err.message}`);
|
|
2947
2985
|
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
2948
2986
|
}
|
|
2949
2987
|
}
|
|
2950
2988
|
}
|
|
2951
2989
|
async function main() {
|
|
2990
|
+
if (SHOW_HELP) {
|
|
2991
|
+
printHelp();
|
|
2992
|
+
return;
|
|
2993
|
+
}
|
|
2952
2994
|
console.log("Lunel CLI v" + VERSION);
|
|
2953
2995
|
console.log("=".repeat(20) + "\n");
|
|
2954
2996
|
if (EXTRA_PORTS.length > 0) {
|
|
@@ -2958,9 +3000,9 @@ async function main() {
|
|
|
2958
3000
|
try {
|
|
2959
3001
|
const cliConfig = await getCliConfig();
|
|
2960
3002
|
const savedSession = getSavedSessionForRoot(cliConfig, ROOT_DIR);
|
|
2961
|
-
|
|
3003
|
+
debugLog("Checking PTY runtime...");
|
|
2962
3004
|
await ensurePtyBinaryReady();
|
|
2963
|
-
|
|
3005
|
+
debugLog("PTY runtime ready.\n");
|
|
2964
3006
|
// Start both AI backends (OpenCode + Codex). Unavailable ones are skipped.
|
|
2965
3007
|
aiManager = await createAiManager();
|
|
2966
3008
|
// Wire provider events → mobile app data channel, tagged with backend name.
|
|
@@ -3020,7 +3062,7 @@ async function main() {
|
|
|
3020
3062
|
}
|
|
3021
3063
|
if (error instanceof Error) {
|
|
3022
3064
|
console.error(`Error: ${error.message}`);
|
|
3023
|
-
if (error.stack)
|
|
3065
|
+
if (DEBUG_MODE && error.stack)
|
|
3024
3066
|
console.error(error.stack);
|
|
3025
3067
|
}
|
|
3026
3068
|
else {
|