lunel-cli 0.1.86 → 0.1.88

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 CHANGED
@@ -931,7 +931,13 @@ async function handleGitDiscard(payload) {
931
931
  }
932
932
  function emitAppEvent(msg) {
933
933
  if (activeV2Transport) {
934
- void activeV2Transport.sendMessage(msg).catch((error) => {
934
+ if (!activeV2Transport.isSecure()) {
935
+ if (DEBUG_MODE) {
936
+ console.error("[transport:v2] dropped event before secure session:", `${msg.ns}.${msg.action}`);
937
+ }
938
+ return;
939
+ }
940
+ void activeV2Transport.sendEvent(msg).catch((error) => {
935
941
  if (DEBUG_MODE)
936
942
  console.error("[transport:v2] failed to send event:", error instanceof Error ? error.message : String(error));
937
943
  });
@@ -2551,11 +2557,15 @@ async function connectWebSocketV2() {
2551
2557
  if (!currentSessionPassword) {
2552
2558
  throw new Error("missing password for websocket connect");
2553
2559
  }
2560
+ if (!currentSessionCode) {
2561
+ throw new Error("missing session code for secure transport");
2562
+ }
2554
2563
  console.log(`Connecting to gateway ${gatewayUrl}...`);
2555
2564
  activeGatewayUrl = gatewayUrl;
2556
2565
  const transport = new V2SessionTransport({
2557
2566
  gatewayUrl,
2558
2567
  password: currentSessionPassword,
2568
+ sessionCode: currentSessionCode,
2559
2569
  role: "cli",
2560
2570
  debugLog: DEBUG_MODE ? debugLog : undefined,
2561
2571
  handlers: {
@@ -2564,7 +2574,6 @@ async function connectWebSocketV2() {
2564
2574
  return;
2565
2575
  if (message.type === "peer_connected") {
2566
2576
  console.log("App connected!\n");
2567
- startPortSync();
2568
2577
  return;
2569
2578
  }
2570
2579
  if (message.type === "peer_disconnected") {
@@ -2593,6 +2602,9 @@ async function connectWebSocketV2() {
2593
2602
  onProtocolResponse: async () => {
2594
2603
  // CLI does not currently await app responses outside request/reply routing.
2595
2604
  },
2605
+ onProtocolEvent: async (message) => {
2606
+ await processMessage(message);
2607
+ },
2596
2608
  onClose: (reason) => {
2597
2609
  if (shuttingDown)
2598
2610
  return;
@@ -2609,6 +2621,7 @@ async function connectWebSocketV2() {
2609
2621
  });
2610
2622
  activeV2Transport = transport;
2611
2623
  await transport.connect();
2624
+ startPortSync();
2612
2625
  console.log("Connected to gateway (single secure session).\n");
2613
2626
  }
2614
2627
  async function handleConnectionDrop(reason) {
@@ -17,12 +17,18 @@ export interface Response {
17
17
  message: string;
18
18
  };
19
19
  }
20
+ export interface EventMessage {
21
+ v: 1;
22
+ id: string;
23
+ ns: string;
24
+ action: string;
25
+ payload: Record<string, unknown>;
26
+ }
20
27
  export interface SystemMessage {
21
- type: "connected" | "peer_connected" | "peer_disconnected" | "error" | "app_disconnected" | "close_connection" | "e2ee_hello" | "e2ee_secure_ready";
28
+ type: "connected" | "peer_connected" | "peer_disconnected" | "error" | "app_disconnected" | "close_connection";
22
29
  role?: string;
23
30
  channel?: string;
24
31
  peer?: string;
25
- pubkey?: string;
26
32
  reconnectDeadline?: number;
27
33
  reason?: string;
28
34
  payload?: Record<string, unknown>;
@@ -35,11 +41,26 @@ export type V2HandshakeFrame = {
35
41
  t: "lunel_v2";
36
42
  kind: "server_hello";
37
43
  pubkey: string;
38
- header: string;
39
44
  } | {
40
45
  t: "lunel_v2";
41
- kind: "client_ready";
42
- header: string;
46
+ kind: "client_key";
47
+ nonce: string;
48
+ box: string;
49
+ auth: string;
50
+ } | {
51
+ t: "lunel_v2";
52
+ kind: "server_ready";
53
+ auth: string;
54
+ };
55
+ export type EncryptedProtocolEnvelope = {
56
+ kind: "request";
57
+ message: Message;
58
+ } | {
59
+ kind: "response";
60
+ message: Response;
61
+ } | {
62
+ kind: "event";
63
+ message: EventMessage;
43
64
  };
44
65
  export declare const V2_BINARY_MAGIC_0 = 76;
45
66
  export declare const V2_BINARY_MAGIC_1 = 50;
@@ -47,6 +68,7 @@ export declare const V2_FRAME_ENCRYPTED_MESSAGE = 1;
47
68
  export declare function isProtocolRequest(value: unknown): value is Message;
48
69
  export declare function isProtocolResponse(value: unknown): value is Response;
49
70
  export declare function isV2HandshakeFrame(value: unknown): value is V2HandshakeFrame;
71
+ export declare function isEncryptedProtocolEnvelope(value: unknown): value is EncryptedProtocolEnvelope;
50
72
  export declare function encodeV2EncryptedFrame(payload: Uint8Array): Uint8Array;
51
73
  export declare function decodeV2BinaryFrame(data: Uint8Array): {
52
74
  type: number;
@@ -28,9 +28,24 @@ export function isV2HandshakeFrame(value) {
28
28
  if (frame.kind === "client_hello")
29
29
  return typeof frame.pubkey === "string";
30
30
  if (frame.kind === "server_hello")
31
- return typeof frame.pubkey === "string" && typeof frame.header === "string";
32
- if (frame.kind === "client_ready")
33
- return typeof frame.header === "string";
31
+ return typeof frame.pubkey === "string";
32
+ if (frame.kind === "client_key") {
33
+ return typeof frame.nonce === "string" && typeof frame.box === "string" && typeof frame.auth === "string";
34
+ }
35
+ if (frame.kind === "server_ready")
36
+ return typeof frame.auth === "string";
37
+ return false;
38
+ }
39
+ export function isEncryptedProtocolEnvelope(value) {
40
+ if (!value || typeof value !== "object")
41
+ return false;
42
+ const envelope = value;
43
+ if (envelope.kind === "request")
44
+ return isProtocolRequest(envelope.message);
45
+ if (envelope.kind === "response")
46
+ return isProtocolResponse(envelope.message);
47
+ if (envelope.kind === "event")
48
+ return isProtocolRequest(envelope.message);
34
49
  return false;
35
50
  }
36
51
  export function encodeV2EncryptedFrame(payload) {
@@ -1,13 +1,15 @@
1
- import type { Message, Response, SystemMessage } from "./protocol.js";
1
+ import type { EventMessage, Message, Response, SystemMessage } from "./protocol.js";
2
2
  export interface V2TransportHandlers {
3
3
  onSystemMessage: (message: SystemMessage) => Promise<void> | void;
4
4
  onProtocolRequest: (message: Message) => Promise<Response>;
5
5
  onProtocolResponse?: (message: Response) => Promise<void> | void;
6
+ onProtocolEvent?: (message: EventMessage) => Promise<void> | void;
6
7
  onClose: (reason: string) => void;
7
8
  }
8
9
  export interface V2TransportOptions {
9
10
  gatewayUrl: string;
10
11
  password: string;
12
+ sessionCode: string;
11
13
  role: "cli" | "app";
12
14
  handlers: V2TransportHandlers;
13
15
  debugLog?: (message: string, ...args: unknown[]) => void;
@@ -18,9 +20,8 @@ export declare class V2SessionTransport {
18
20
  private closed;
19
21
  private state;
20
22
  private keyPair;
23
+ private remotePublicKey;
21
24
  private sessionKeys;
22
- private pushState;
23
- private pullState;
24
25
  private secureReadyResolve;
25
26
  private secureReadyReject;
26
27
  private secureReadyPromise;
@@ -28,12 +29,15 @@ export declare class V2SessionTransport {
28
29
  connect(): Promise<void>;
29
30
  sendMessage(message: Message): Promise<void>;
30
31
  sendResponse(response: Response): Promise<void>;
32
+ sendEvent(message: EventMessage): Promise<void>;
31
33
  close(): void;
34
+ isSecure(): boolean;
32
35
  private handleMessage;
33
36
  private maybeStartHandshake;
34
37
  private handleHandshakeFrame;
35
- private encryptMessage;
38
+ private encryptEnvelope;
36
39
  private ensureKeyPair;
40
+ private computeHandshakeAuth;
37
41
  private sendJsonFrame;
38
42
  private sendBinaryFrame;
39
43
  private markSecure;
@@ -1,6 +1,8 @@
1
1
  import { WebSocket } from "ws";
2
2
  import { createRequire } from "module";
3
- import { V2_FRAME_ENCRYPTED_MESSAGE, buildSessionV2WsUrl, decodeV2BinaryFrame, encodeV2EncryptedFrame, isProtocolRequest, isProtocolResponse, isV2HandshakeFrame, } from "./protocol.js";
3
+ import { V2_FRAME_ENCRYPTED_MESSAGE, buildSessionV2WsUrl, decodeV2BinaryFrame, encodeV2EncryptedFrame, isEncryptedProtocolEnvelope, isProtocolRequest, isProtocolResponse, isV2HandshakeFrame, } from "./protocol.js";
4
+ const encoder = new TextEncoder();
5
+ const decoder = new TextDecoder();
4
6
  const require = createRequire(import.meta.url);
5
7
  const sodium = require("libsodium-wrappers");
6
8
  function toUint8Array(data) {
@@ -10,15 +12,20 @@ function toUint8Array(data) {
10
12
  return new Uint8Array(Buffer.concat(data.map((chunk) => Buffer.from(chunk))));
11
13
  return new Uint8Array(data);
12
14
  }
15
+ function encodeUtf8(value) {
16
+ return encoder.encode(value);
17
+ }
18
+ function decodeUtf8(value) {
19
+ return decoder.decode(value);
20
+ }
13
21
  export class V2SessionTransport {
14
22
  options;
15
23
  ws = null;
16
24
  closed = false;
17
25
  state = "idle";
18
26
  keyPair = null;
27
+ remotePublicKey = null;
19
28
  sessionKeys = null;
20
- pushState = null;
21
- pullState = null;
22
29
  secureReadyResolve = null;
23
30
  secureReadyReject = null;
24
31
  secureReadyPromise = null;
@@ -49,9 +56,9 @@ export class V2SessionTransport {
49
56
  this.state = "open";
50
57
  resolve();
51
58
  });
52
- ws.on("message", async (data) => {
59
+ ws.on("message", async (data, isBinary) => {
53
60
  try {
54
- await this.handleMessage(data);
61
+ await this.handleMessage(data, isBinary);
55
62
  }
56
63
  catch (error) {
57
64
  this.options.debugLog?.("[transport:v2] message handling failed", error);
@@ -86,11 +93,15 @@ export class V2SessionTransport {
86
93
  await this.secureReadyPromise;
87
94
  }
88
95
  async sendMessage(message) {
89
- const ciphertext = await this.encryptMessage(message);
96
+ const ciphertext = this.encryptEnvelope({ kind: "request", message });
90
97
  this.sendBinaryFrame(ciphertext);
91
98
  }
92
99
  async sendResponse(response) {
93
- const ciphertext = await this.encryptMessage(response);
100
+ const ciphertext = this.encryptEnvelope({ kind: "response", message: response });
101
+ this.sendBinaryFrame(ciphertext);
102
+ }
103
+ async sendEvent(message) {
104
+ const ciphertext = this.encryptEnvelope({ kind: "event", message });
94
105
  this.sendBinaryFrame(ciphertext);
95
106
  }
96
107
  close() {
@@ -103,9 +114,13 @@ export class V2SessionTransport {
103
114
  }
104
115
  this.ws = null;
105
116
  }
106
- async handleMessage(data) {
107
- if (typeof data === "string") {
108
- const raw = JSON.parse(data);
117
+ isSecure() {
118
+ return this.state === "secure";
119
+ }
120
+ async handleMessage(data, isBinary) {
121
+ if (!isBinary) {
122
+ const text = typeof data === "string" ? data : Buffer.from(data).toString("utf-8");
123
+ const raw = JSON.parse(text);
109
124
  if ("type" in raw) {
110
125
  await this.options.handlers.onSystemMessage(raw);
111
126
  if (raw.type === "peer_connected") {
@@ -139,24 +154,33 @@ export class V2SessionTransport {
139
154
  if (frame.type !== V2_FRAME_ENCRYPTED_MESSAGE) {
140
155
  throw new Error(`unsupported v2 frame type ${frame.type}`);
141
156
  }
142
- if (this.state !== "secure" || !this.pullState) {
157
+ if (this.state !== "secure" || !this.sessionKeys) {
143
158
  throw new Error("received encrypted frame before secure transport");
144
159
  }
145
- const pulled = sodium.crypto_secretstream_xchacha20poly1305_pull(this.pullState, frame.payload);
146
- if (!pulled.message) {
147
- throw new Error("failed to decrypt v2 frame");
160
+ if (frame.payload.length < sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES) {
161
+ throw new Error("encrypted frame missing nonce");
162
+ }
163
+ const nonce = frame.payload.subarray(0, sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
164
+ const ciphertext = frame.payload.subarray(sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
165
+ const plaintext = sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(null, ciphertext, null, nonce, this.sessionKeys.rx);
166
+ const parsed = JSON.parse(decodeUtf8(plaintext));
167
+ if (!isEncryptedProtocolEnvelope(parsed)) {
168
+ throw new Error("invalid decrypted protocol envelope");
148
169
  }
149
- const parsed = JSON.parse(sodium.to_string(pulled.message));
150
- if (isProtocolResponse(parsed)) {
151
- await this.options.handlers.onProtocolResponse?.(parsed);
170
+ if (parsed.kind === "response") {
171
+ await this.options.handlers.onProtocolResponse?.(parsed.message);
152
172
  return;
153
173
  }
154
- if (isProtocolRequest(parsed)) {
155
- const response = await this.options.handlers.onProtocolRequest(parsed);
174
+ if (parsed.kind === "event") {
175
+ await this.options.handlers.onProtocolEvent?.(parsed.message);
176
+ return;
177
+ }
178
+ if (parsed.kind === "request") {
179
+ const response = await this.options.handlers.onProtocolRequest(parsed.message);
156
180
  await this.sendResponse(response);
157
181
  return;
158
182
  }
159
- throw new Error("invalid decrypted protocol message");
183
+ throw new Error("invalid decrypted protocol envelope");
160
184
  }
161
185
  async maybeStartHandshake() {
162
186
  if (this.state === "secure" || this.state === "handshaking")
@@ -178,72 +202,115 @@ export class V2SessionTransport {
178
202
  if (this.options.role !== "cli") {
179
203
  throw new Error("unexpected client_hello on app transport");
180
204
  }
181
- const clientPublicKey = sodium.from_base64(frame.pubkey, sodium.base64_variants.URLSAFE_NO_PADDING);
205
+ this.remotePublicKey = sodium.from_base64(frame.pubkey, sodium.base64_variants.URLSAFE_NO_PADDING);
182
206
  const keyPair = this.ensureKeyPair();
183
- const keys = sodium.crypto_kx_server_session_keys(keyPair.publicKey, keyPair.privateKey, clientPublicKey);
184
- this.sessionKeys = { rx: keys.sharedRx, tx: keys.sharedTx };
185
- const pushInit = sodium.crypto_secretstream_xchacha20poly1305_init_push(keys.sharedTx);
186
- this.pushState = pushInit.state;
187
- const response = {
207
+ this.sendJsonFrame({
188
208
  t: "lunel_v2",
189
209
  kind: "server_hello",
190
210
  pubkey: sodium.to_base64(keyPair.publicKey, sodium.base64_variants.URLSAFE_NO_PADDING),
191
- header: sodium.to_base64(pushInit.header, sodium.base64_variants.URLSAFE_NO_PADDING),
192
- };
193
- this.sendJsonFrame(response);
211
+ });
194
212
  return;
195
213
  }
196
214
  if (frame.kind === "server_hello") {
197
215
  if (this.options.role !== "app") {
198
216
  throw new Error("unexpected server_hello on cli transport");
199
217
  }
200
- const serverPublicKey = sodium.from_base64(frame.pubkey, sodium.base64_variants.URLSAFE_NO_PADDING);
201
- const serverHeader = sodium.from_base64(frame.header, sodium.base64_variants.URLSAFE_NO_PADDING);
218
+ this.remotePublicKey = sodium.from_base64(frame.pubkey, sodium.base64_variants.URLSAFE_NO_PADDING);
202
219
  const keyPair = this.ensureKeyPair();
203
- const keys = sodium.crypto_kx_client_session_keys(keyPair.publicKey, keyPair.privateKey, serverPublicKey);
204
- this.sessionKeys = { rx: keys.sharedRx, tx: keys.sharedTx };
205
- this.pullState = sodium.crypto_secretstream_xchacha20poly1305_init_pull(serverHeader, keys.sharedRx);
206
- const pushInit = sodium.crypto_secretstream_xchacha20poly1305_init_push(keys.sharedTx);
207
- this.pushState = pushInit.state;
208
- const ready = {
220
+ const c2sKey = sodium.crypto_aead_xchacha20poly1305_ietf_keygen();
221
+ const s2cKey = sodium.crypto_aead_xchacha20poly1305_ietf_keygen();
222
+ const nonce = sodium.randombytes_buf(sodium.crypto_box_NONCEBYTES);
223
+ const payload = {
224
+ c2s: sodium.to_base64(c2sKey, sodium.base64_variants.URLSAFE_NO_PADDING),
225
+ s2c: sodium.to_base64(s2cKey, sodium.base64_variants.URLSAFE_NO_PADDING),
226
+ };
227
+ const boxed = sodium.crypto_box_easy(encodeUtf8(JSON.stringify(payload)), nonce, this.remotePublicKey, keyPair.privateKey);
228
+ const auth = this.computeHandshakeAuth("client_key", "app", frame.pubkey, nonce, boxed);
229
+ this.sessionKeys = { rx: s2cKey, tx: c2sKey };
230
+ this.sendJsonFrame({
209
231
  t: "lunel_v2",
210
- kind: "client_ready",
211
- header: sodium.to_base64(pushInit.header, sodium.base64_variants.URLSAFE_NO_PADDING),
232
+ kind: "client_key",
233
+ nonce: sodium.to_base64(nonce, sodium.base64_variants.URLSAFE_NO_PADDING),
234
+ box: sodium.to_base64(boxed, sodium.base64_variants.URLSAFE_NO_PADDING),
235
+ auth,
236
+ });
237
+ return;
238
+ }
239
+ if (frame.kind === "client_key") {
240
+ if (this.options.role !== "cli") {
241
+ throw new Error("unexpected client_key on app transport");
242
+ }
243
+ if (!this.remotePublicKey) {
244
+ throw new Error("missing client public key before client_key");
245
+ }
246
+ const keyPair = this.ensureKeyPair();
247
+ const nonce = sodium.from_base64(frame.nonce, sodium.base64_variants.URLSAFE_NO_PADDING);
248
+ const boxed = sodium.from_base64(frame.box, sodium.base64_variants.URLSAFE_NO_PADDING);
249
+ const expectedAuth = this.computeHandshakeAuth("client_key", "app", sodium.to_base64(this.remotePublicKey, sodium.base64_variants.URLSAFE_NO_PADDING), nonce, boxed);
250
+ if (frame.auth !== expectedAuth) {
251
+ throw new Error("client_key authentication failed");
252
+ }
253
+ const opened = sodium.crypto_box_open_easy(boxed, nonce, this.remotePublicKey, keyPair.privateKey);
254
+ const payload = JSON.parse(decodeUtf8(opened));
255
+ this.sessionKeys = {
256
+ rx: sodium.from_base64(payload.c2s, sodium.base64_variants.URLSAFE_NO_PADDING),
257
+ tx: sodium.from_base64(payload.s2c, sodium.base64_variants.URLSAFE_NO_PADDING),
212
258
  };
213
- this.sendJsonFrame(ready);
259
+ this.sendJsonFrame({
260
+ t: "lunel_v2",
261
+ kind: "server_ready",
262
+ auth: this.computeHandshakeAuth("server_ready", "cli", sodium.to_base64(this.remotePublicKey, sodium.base64_variants.URLSAFE_NO_PADDING)),
263
+ });
214
264
  this.markSecure();
215
265
  return;
216
266
  }
217
- if (frame.kind === "client_ready") {
218
- if (this.options.role !== "cli") {
219
- throw new Error("unexpected client_ready on app transport");
267
+ if (frame.kind === "server_ready") {
268
+ if (this.options.role !== "app") {
269
+ throw new Error("unexpected server_ready on cli transport");
220
270
  }
221
271
  if (!this.sessionKeys) {
222
- throw new Error("missing session keys before client_ready");
272
+ throw new Error("missing session keys before server_ready");
273
+ }
274
+ const expectedAuth = this.computeHandshakeAuth("server_ready", "cli", sodium.to_base64(this.remotePublicKey, sodium.base64_variants.URLSAFE_NO_PADDING));
275
+ if (frame.auth !== expectedAuth) {
276
+ throw new Error("server_ready authentication failed");
223
277
  }
224
- const clientHeader = sodium.from_base64(frame.header, sodium.base64_variants.URLSAFE_NO_PADDING);
225
- this.pullState = sodium.crypto_secretstream_xchacha20poly1305_init_pull(clientHeader, this.sessionKeys.rx);
226
278
  this.markSecure();
227
279
  }
228
280
  }
229
- async encryptMessage(message) {
230
- await sodium.ready;
231
- if (this.state !== "secure" || !this.pushState) {
281
+ encryptEnvelope(envelope) {
282
+ if (this.state !== "secure" || !this.sessionKeys) {
232
283
  throw new Error("secure transport is not active");
233
284
  }
234
- const plaintext = sodium.from_string(JSON.stringify(message));
235
- return sodium.crypto_secretstream_xchacha20poly1305_push(this.pushState, plaintext, null, sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE);
285
+ const nonce = sodium.randombytes_buf(sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
286
+ const ciphertext = sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(encodeUtf8(JSON.stringify(envelope)), null, null, nonce, this.sessionKeys.tx);
287
+ const payload = new Uint8Array(nonce.length + ciphertext.length);
288
+ payload.set(nonce, 0);
289
+ payload.set(ciphertext, nonce.length);
290
+ return payload;
236
291
  }
237
292
  ensureKeyPair() {
238
293
  if (this.keyPair)
239
294
  return this.keyPair;
240
- const pair = sodium.crypto_kx_keypair();
295
+ const pair = sodium.crypto_box_keypair();
241
296
  this.keyPair = {
242
297
  publicKey: pair.publicKey,
243
298
  privateKey: pair.privateKey,
244
299
  };
245
300
  return this.keyPair;
246
301
  }
302
+ computeHandshakeAuth(phase, senderRole, peerPubkeyB64, nonce, boxed) {
303
+ const authKey = sodium.crypto_generichash(sodium.crypto_auth_KEYBYTES, encodeUtf8(this.options.sessionCode), undefined);
304
+ const parts = [
305
+ phase,
306
+ senderRole,
307
+ peerPubkeyB64,
308
+ nonce ? sodium.to_base64(nonce, sodium.base64_variants.URLSAFE_NO_PADDING) : "",
309
+ boxed ? sodium.to_base64(boxed, sodium.base64_variants.URLSAFE_NO_PADDING) : "",
310
+ ];
311
+ const tag = sodium.crypto_auth(parts.join(":"), authKey);
312
+ return sodium.to_base64(tag, sodium.base64_variants.URLSAFE_NO_PADDING);
313
+ }
247
314
  sendJsonFrame(frame) {
248
315
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
249
316
  throw new Error("v2 transport is not connected");
@@ -255,7 +322,7 @@ export class V2SessionTransport {
255
322
  throw new Error("v2 transport is not connected");
256
323
  }
257
324
  const framed = encodeV2EncryptedFrame(ciphertext);
258
- this.ws.send(Buffer.from(framed));
325
+ this.ws.send(framed);
259
326
  }
260
327
  markSecure() {
261
328
  this.state = "secure";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lunel-cli",
3
- "version": "0.1.86",
3
+ "version": "0.1.88",
4
4
  "author": [
5
5
  {
6
6
  "name": "Soham Bharambe",