jukto-cli 0.1.0

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.
@@ -0,0 +1,77 @@
1
+ export interface Message {
2
+ v: 1;
3
+ id: string;
4
+ ns: string;
5
+ action: string;
6
+ payload: Record<string, unknown>;
7
+ }
8
+ export interface Response {
9
+ v: 1;
10
+ id: string;
11
+ ns: string;
12
+ action: string;
13
+ ok: boolean;
14
+ payload: Record<string, unknown>;
15
+ error?: {
16
+ code: string;
17
+ message: string;
18
+ };
19
+ }
20
+ export interface EventMessage {
21
+ v: 1;
22
+ id: string;
23
+ ns: string;
24
+ action: string;
25
+ payload: Record<string, unknown>;
26
+ }
27
+ export interface SystemMessage {
28
+ type: "connected" | "peer_connected" | "peer_disconnected" | "error" | "app_disconnected" | "close_connection";
29
+ role?: string;
30
+ channel?: string;
31
+ peer?: string;
32
+ reconnectDeadline?: number;
33
+ reason?: string;
34
+ payload?: Record<string, unknown>;
35
+ }
36
+ export type V2HandshakeFrame = {
37
+ t: "jukto_v2";
38
+ kind: "client_hello";
39
+ pubkey: string;
40
+ } | {
41
+ t: "jukto_v2";
42
+ kind: "server_hello";
43
+ pubkey: string;
44
+ } | {
45
+ t: "jukto_v2";
46
+ kind: "client_key";
47
+ nonce: string;
48
+ box: string;
49
+ auth: string;
50
+ } | {
51
+ t: "jukto_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;
64
+ };
65
+ export declare const V2_BINARY_MAGIC_0 = 76;
66
+ export declare const V2_BINARY_MAGIC_1 = 50;
67
+ export declare const V2_FRAME_ENCRYPTED_MESSAGE = 1;
68
+ export declare function isProtocolRequest(value: unknown): value is Message;
69
+ export declare function isProtocolResponse(value: unknown): value is Response;
70
+ export declare function isV2HandshakeFrame(value: unknown): value is V2HandshakeFrame;
71
+ export declare function isEncryptedProtocolEnvelope(value: unknown): value is EncryptedProtocolEnvelope;
72
+ export declare function encodeV2EncryptedFrame(payload: Uint8Array): Uint8Array;
73
+ export declare function decodeV2BinaryFrame(data: Uint8Array): {
74
+ type: number;
75
+ payload: Uint8Array;
76
+ } | null;
77
+ export declare function buildSessionV2WsUrl(gatewayUrl: string, role: "cli" | "app", password: string, generation?: number | null): string;
@@ -0,0 +1,79 @@
1
+ export const V2_BINARY_MAGIC_0 = 0x4c; // L
2
+ export const V2_BINARY_MAGIC_1 = 0x32; // 2
3
+ export const V2_FRAME_ENCRYPTED_MESSAGE = 0x01;
4
+ export function isProtocolRequest(value) {
5
+ if (!value || typeof value !== "object")
6
+ return false;
7
+ const msg = value;
8
+ return (msg.v === 1 &&
9
+ typeof msg.id === "string" &&
10
+ typeof msg.ns === "string" &&
11
+ typeof msg.action === "string" &&
12
+ typeof msg.payload === "object" &&
13
+ msg.payload !== null &&
14
+ typeof msg.ok === "undefined");
15
+ }
16
+ export function isProtocolResponse(value) {
17
+ if (!value || typeof value !== "object")
18
+ return false;
19
+ const msg = value;
20
+ return msg.v === 1 && typeof msg.id === "string" && typeof msg.ok === "boolean";
21
+ }
22
+ export function isV2HandshakeFrame(value) {
23
+ if (!value || typeof value !== "object")
24
+ return false;
25
+ const frame = value;
26
+ if (frame.t !== "jukto_v2" || typeof frame.kind !== "string")
27
+ return false;
28
+ if (frame.kind === "client_hello")
29
+ return typeof frame.pubkey === "string";
30
+ if (frame.kind === "server_hello")
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);
49
+ return false;
50
+ }
51
+ export function encodeV2EncryptedFrame(payload) {
52
+ const frame = new Uint8Array(payload.length + 3);
53
+ frame[0] = V2_BINARY_MAGIC_0;
54
+ frame[1] = V2_BINARY_MAGIC_1;
55
+ frame[2] = V2_FRAME_ENCRYPTED_MESSAGE;
56
+ frame.set(payload, 3);
57
+ return frame;
58
+ }
59
+ export function decodeV2BinaryFrame(data) {
60
+ if (data.length < 3)
61
+ return null;
62
+ if (data[0] !== V2_BINARY_MAGIC_0 || data[1] !== V2_BINARY_MAGIC_1)
63
+ return null;
64
+ return {
65
+ type: data[2],
66
+ payload: data.subarray(3),
67
+ };
68
+ }
69
+ export function buildSessionV2WsUrl(gatewayUrl, role, password, generation) {
70
+ const wsBase = gatewayUrl.replace(/^https:/, "wss:");
71
+ if (!wsBase.startsWith("wss://")) {
72
+ throw new Error("Gateway URL must use https://");
73
+ }
74
+ const query = new URLSearchParams({ password });
75
+ if (typeof generation === "number" && Number.isFinite(generation) && generation > 0) {
76
+ query.set("generation", String(generation));
77
+ }
78
+ return `${wsBase}/v2/ws/${role}?${query.toString()}`;
79
+ }
@@ -0,0 +1,47 @@
1
+ import type { EventMessage, Message, Response, SystemMessage } from "./protocol.js";
2
+ export interface V2TransportHandlers {
3
+ onSystemMessage: (message: SystemMessage) => Promise<void> | void;
4
+ onProtocolRequest: (message: Message) => Promise<Response>;
5
+ onProtocolResponse?: (message: Response) => Promise<void> | void;
6
+ onProtocolEvent?: (message: EventMessage) => Promise<void> | void;
7
+ onClose: (reason: string) => void;
8
+ }
9
+ export interface V2TransportOptions {
10
+ gatewayUrl: string;
11
+ password: string;
12
+ sessionSecret: string;
13
+ generation?: number | null;
14
+ role: "cli" | "app";
15
+ handlers: V2TransportHandlers;
16
+ debugLog?: (message: string, ...args: unknown[]) => void;
17
+ }
18
+ export declare class V2SessionTransport {
19
+ private readonly options;
20
+ private ws;
21
+ private closed;
22
+ private state;
23
+ private keyPair;
24
+ private remotePublicKey;
25
+ private sessionKeys;
26
+ private secureReadyResolve;
27
+ private secureReadyReject;
28
+ private secureReadyPromise;
29
+ constructor(options: V2TransportOptions);
30
+ connect(): Promise<void>;
31
+ sendMessage(message: Message): Promise<void>;
32
+ sendResponse(response: Response): Promise<void>;
33
+ sendEvent(message: EventMessage): Promise<void>;
34
+ close(): void;
35
+ isSecure(): boolean;
36
+ private handleMessage;
37
+ private maybeStartHandshake;
38
+ private handleHandshakeFrame;
39
+ private encryptEnvelope;
40
+ private ensureKeyPair;
41
+ private resetPeerSession;
42
+ private computeHandshakeAuth;
43
+ private sendJsonFrame;
44
+ private sendBinaryFrame;
45
+ private markSecure;
46
+ private failSecure;
47
+ }
@@ -0,0 +1,347 @@
1
+ import { WebSocket } from "ws";
2
+ import { createRequire } from "module";
3
+ import { V2_FRAME_ENCRYPTED_MESSAGE, buildSessionV2WsUrl, decodeV2BinaryFrame, encodeV2EncryptedFrame, isEncryptedProtocolEnvelope, isV2HandshakeFrame, } from "./protocol.js";
4
+ const encoder = new TextEncoder();
5
+ const decoder = new TextDecoder();
6
+ const require = createRequire(import.meta.url);
7
+ const sodium = require("libsodium-wrappers");
8
+ function toUint8Array(data) {
9
+ if (data instanceof Uint8Array)
10
+ return data;
11
+ if (Array.isArray(data))
12
+ return new Uint8Array(Buffer.concat(data.map((chunk) => Buffer.from(chunk))));
13
+ return new Uint8Array(data);
14
+ }
15
+ function encodeUtf8(value) {
16
+ return encoder.encode(value);
17
+ }
18
+ function decodeUtf8(value) {
19
+ return decoder.decode(value);
20
+ }
21
+ export class V2SessionTransport {
22
+ options;
23
+ ws = null;
24
+ closed = false;
25
+ state = "idle";
26
+ keyPair = null;
27
+ remotePublicKey = null;
28
+ sessionKeys = null;
29
+ secureReadyResolve = null;
30
+ secureReadyReject = null;
31
+ secureReadyPromise = null;
32
+ constructor(options) {
33
+ this.options = options;
34
+ }
35
+ async connect() {
36
+ if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) {
37
+ if (this.secureReadyPromise) {
38
+ return await this.secureReadyPromise;
39
+ }
40
+ return;
41
+ }
42
+ await sodium.ready;
43
+ this.secureReadyPromise = new Promise((resolve, reject) => {
44
+ this.secureReadyResolve = resolve;
45
+ this.secureReadyReject = reject;
46
+ });
47
+ const wsUrl = buildSessionV2WsUrl(this.options.gatewayUrl, this.options.role, this.options.password, this.options.generation);
48
+ await new Promise((resolve, reject) => {
49
+ const ws = new WebSocket(wsUrl);
50
+ let opened = false;
51
+ this.ws = ws;
52
+ this.closed = false;
53
+ this.state = "connecting";
54
+ ws.on("open", () => {
55
+ opened = true;
56
+ this.state = "open";
57
+ resolve();
58
+ });
59
+ ws.on("message", async (data, isBinary) => {
60
+ try {
61
+ await this.handleMessage(data, isBinary);
62
+ }
63
+ catch (error) {
64
+ this.options.debugLog?.("[transport:v2] message handling failed", error);
65
+ this.failSecure(new Error(error instanceof Error ? error.message : String(error)));
66
+ this.close();
67
+ }
68
+ });
69
+ ws.on("close", (code, reason) => {
70
+ this.ws = null;
71
+ this.state = "closed";
72
+ if (!opened) {
73
+ reject(new Error(`v2 socket closed during setup (${code}: ${reason.toString()})`));
74
+ return;
75
+ }
76
+ if (!this.closed) {
77
+ this.closed = true;
78
+ this.failSecure(new Error(`v2 socket closed (${code}: ${reason.toString()})`));
79
+ this.options.handlers.onClose(`v2 socket closed (${code}: ${reason.toString()})`);
80
+ }
81
+ });
82
+ ws.on("error", (error) => {
83
+ if (!opened) {
84
+ reject(new Error(`v2 socket error: ${error.message}`));
85
+ return;
86
+ }
87
+ this.options.debugLog?.("[transport:v2] websocket error", error.message);
88
+ });
89
+ });
90
+ if (!this.secureReadyPromise) {
91
+ throw new Error("secure readiness promise missing");
92
+ }
93
+ await this.secureReadyPromise;
94
+ }
95
+ async sendMessage(message) {
96
+ const ciphertext = this.encryptEnvelope({ kind: "request", message });
97
+ this.sendBinaryFrame(ciphertext);
98
+ }
99
+ async sendResponse(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 });
105
+ this.sendBinaryFrame(ciphertext);
106
+ }
107
+ close() {
108
+ this.closed = true;
109
+ this.state = "closed";
110
+ if (!this.ws)
111
+ return;
112
+ if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {
113
+ this.ws.close();
114
+ }
115
+ this.ws = null;
116
+ }
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);
124
+ if ("type" in raw) {
125
+ await this.options.handlers.onSystemMessage(raw);
126
+ if (raw.type === "peer_connected") {
127
+ this.resetPeerSession();
128
+ await this.maybeStartHandshake();
129
+ }
130
+ else if (raw.type === "peer_disconnected" || raw.type === "app_disconnected") {
131
+ this.resetPeerSession();
132
+ }
133
+ return;
134
+ }
135
+ if (isV2HandshakeFrame(raw)) {
136
+ await this.handleHandshakeFrame(raw);
137
+ return;
138
+ }
139
+ throw new Error(this.state === "secure"
140
+ ? "received plaintext app message after secure transport"
141
+ : "received plaintext app message before secure transport");
142
+ }
143
+ const bytes = toUint8Array(data);
144
+ const frame = decodeV2BinaryFrame(bytes);
145
+ if (!frame) {
146
+ throw new Error("invalid binary v2 frame");
147
+ }
148
+ if (frame.type !== V2_FRAME_ENCRYPTED_MESSAGE) {
149
+ throw new Error(`unsupported v2 frame type ${frame.type}`);
150
+ }
151
+ if (this.state !== "secure" || !this.sessionKeys) {
152
+ throw new Error("received encrypted frame before secure transport");
153
+ }
154
+ if (frame.payload.length < sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES) {
155
+ throw new Error("encrypted frame missing nonce");
156
+ }
157
+ const nonce = frame.payload.subarray(0, sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
158
+ const ciphertext = frame.payload.subarray(sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
159
+ const plaintext = sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(null, ciphertext, null, nonce, this.sessionKeys.rx);
160
+ const parsed = JSON.parse(decodeUtf8(plaintext));
161
+ if (!isEncryptedProtocolEnvelope(parsed)) {
162
+ throw new Error("invalid decrypted protocol envelope");
163
+ }
164
+ if (parsed.kind === "response") {
165
+ await this.options.handlers.onProtocolResponse?.(parsed.message);
166
+ return;
167
+ }
168
+ if (parsed.kind === "event") {
169
+ await this.options.handlers.onProtocolEvent?.(parsed.message);
170
+ return;
171
+ }
172
+ if (parsed.kind === "request") {
173
+ const response = await this.options.handlers.onProtocolRequest(parsed.message);
174
+ await this.sendResponse(response);
175
+ return;
176
+ }
177
+ throw new Error("invalid decrypted protocol envelope");
178
+ }
179
+ async maybeStartHandshake() {
180
+ if (this.state === "secure" || this.state === "handshaking")
181
+ return;
182
+ if (this.options.role !== "app")
183
+ return;
184
+ this.state = "handshaking";
185
+ const keyPair = this.ensureKeyPair();
186
+ const hello = {
187
+ t: "jukto_v2",
188
+ kind: "client_hello",
189
+ pubkey: sodium.to_base64(keyPair.publicKey, sodium.base64_variants.URLSAFE_NO_PADDING),
190
+ };
191
+ this.sendJsonFrame(hello);
192
+ }
193
+ async handleHandshakeFrame(frame) {
194
+ this.state = "handshaking";
195
+ if (frame.kind === "client_hello") {
196
+ if (this.options.role !== "cli") {
197
+ throw new Error("unexpected client_hello on app transport");
198
+ }
199
+ this.remotePublicKey = sodium.from_base64(frame.pubkey, sodium.base64_variants.URLSAFE_NO_PADDING);
200
+ const keyPair = this.ensureKeyPair();
201
+ this.sendJsonFrame({
202
+ t: "jukto_v2",
203
+ kind: "server_hello",
204
+ pubkey: sodium.to_base64(keyPair.publicKey, sodium.base64_variants.URLSAFE_NO_PADDING),
205
+ });
206
+ return;
207
+ }
208
+ if (frame.kind === "server_hello") {
209
+ if (this.options.role !== "app") {
210
+ throw new Error("unexpected server_hello on cli transport");
211
+ }
212
+ this.remotePublicKey = sodium.from_base64(frame.pubkey, sodium.base64_variants.URLSAFE_NO_PADDING);
213
+ const keyPair = this.ensureKeyPair();
214
+ const c2sKey = sodium.crypto_aead_xchacha20poly1305_ietf_keygen();
215
+ const s2cKey = sodium.crypto_aead_xchacha20poly1305_ietf_keygen();
216
+ const nonce = sodium.randombytes_buf(sodium.crypto_box_NONCEBYTES);
217
+ const payload = {
218
+ c2s: sodium.to_base64(c2sKey, sodium.base64_variants.URLSAFE_NO_PADDING),
219
+ s2c: sodium.to_base64(s2cKey, sodium.base64_variants.URLSAFE_NO_PADDING),
220
+ };
221
+ const boxed = sodium.crypto_box_easy(encodeUtf8(JSON.stringify(payload)), nonce, this.remotePublicKey, keyPair.privateKey);
222
+ const auth = this.computeHandshakeAuth("client_key", "app", sodium.to_base64(keyPair.publicKey, sodium.base64_variants.URLSAFE_NO_PADDING), nonce, boxed);
223
+ this.sessionKeys = { rx: s2cKey, tx: c2sKey };
224
+ this.sendJsonFrame({
225
+ t: "jukto_v2",
226
+ kind: "client_key",
227
+ nonce: sodium.to_base64(nonce, sodium.base64_variants.URLSAFE_NO_PADDING),
228
+ box: sodium.to_base64(boxed, sodium.base64_variants.URLSAFE_NO_PADDING),
229
+ auth,
230
+ });
231
+ return;
232
+ }
233
+ if (frame.kind === "client_key") {
234
+ if (this.options.role !== "cli") {
235
+ throw new Error("unexpected client_key on app transport");
236
+ }
237
+ if (!this.remotePublicKey) {
238
+ throw new Error("missing client public key before client_key");
239
+ }
240
+ const keyPair = this.ensureKeyPair();
241
+ const nonce = sodium.from_base64(frame.nonce, sodium.base64_variants.URLSAFE_NO_PADDING);
242
+ const boxed = sodium.from_base64(frame.box, sodium.base64_variants.URLSAFE_NO_PADDING);
243
+ const expectedAuth = this.computeHandshakeAuth("client_key", "app", sodium.to_base64(this.remotePublicKey, sodium.base64_variants.URLSAFE_NO_PADDING), nonce, boxed);
244
+ if (frame.auth !== expectedAuth) {
245
+ throw new Error("client_key authentication failed");
246
+ }
247
+ const opened = sodium.crypto_box_open_easy(boxed, nonce, this.remotePublicKey, keyPair.privateKey);
248
+ const payload = JSON.parse(decodeUtf8(opened));
249
+ this.sessionKeys = {
250
+ rx: sodium.from_base64(payload.c2s, sodium.base64_variants.URLSAFE_NO_PADDING),
251
+ tx: sodium.from_base64(payload.s2c, sodium.base64_variants.URLSAFE_NO_PADDING),
252
+ };
253
+ const auth = this.computeHandshakeAuth("server_ready", "cli", sodium.to_base64(keyPair.publicKey, sodium.base64_variants.URLSAFE_NO_PADDING));
254
+ this.sendJsonFrame({
255
+ t: "jukto_v2",
256
+ kind: "server_ready",
257
+ auth,
258
+ });
259
+ this.markSecure();
260
+ return;
261
+ }
262
+ if (frame.kind === "server_ready") {
263
+ if (this.options.role !== "app") {
264
+ throw new Error("unexpected server_ready on cli transport");
265
+ }
266
+ if (!this.sessionKeys) {
267
+ throw new Error("missing session keys before server_ready");
268
+ }
269
+ const expectedAuth = this.computeHandshakeAuth("server_ready", "cli", sodium.to_base64(this.remotePublicKey, sodium.base64_variants.URLSAFE_NO_PADDING));
270
+ if (frame.auth !== expectedAuth) {
271
+ throw new Error("server_ready authentication failed");
272
+ }
273
+ this.markSecure();
274
+ }
275
+ }
276
+ encryptEnvelope(envelope) {
277
+ if (this.state !== "secure" || !this.sessionKeys) {
278
+ throw new Error("secure transport is not active");
279
+ }
280
+ const nonce = sodium.randombytes_buf(sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
281
+ const ciphertext = sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(encodeUtf8(JSON.stringify(envelope)), null, null, nonce, this.sessionKeys.tx);
282
+ const payload = new Uint8Array(nonce.length + ciphertext.length);
283
+ payload.set(nonce, 0);
284
+ payload.set(ciphertext, nonce.length);
285
+ return payload;
286
+ }
287
+ ensureKeyPair() {
288
+ if (this.keyPair)
289
+ return this.keyPair;
290
+ const pair = sodium.crypto_box_keypair();
291
+ this.keyPair = {
292
+ publicKey: pair.publicKey,
293
+ privateKey: pair.privateKey,
294
+ };
295
+ return this.keyPair;
296
+ }
297
+ resetPeerSession() {
298
+ this.remotePublicKey = null;
299
+ this.sessionKeys = null;
300
+ if (this.ws?.readyState === WebSocket.OPEN) {
301
+ this.state = "open";
302
+ }
303
+ else {
304
+ this.state = "idle";
305
+ }
306
+ }
307
+ computeHandshakeAuth(phase, senderRole, peerPubkeyB64, nonce, boxed) {
308
+ const authKey = sodium.crypto_generichash(sodium.crypto_auth_KEYBYTES, encodeUtf8(this.options.sessionSecret), undefined);
309
+ const parts = [
310
+ phase,
311
+ senderRole,
312
+ peerPubkeyB64,
313
+ nonce ? sodium.to_base64(nonce, sodium.base64_variants.URLSAFE_NO_PADDING) : "",
314
+ boxed ? sodium.to_base64(boxed, sodium.base64_variants.URLSAFE_NO_PADDING) : "",
315
+ ];
316
+ const tag = sodium.crypto_auth(parts.join(":"), authKey);
317
+ return sodium.to_base64(tag, sodium.base64_variants.URLSAFE_NO_PADDING);
318
+ }
319
+ sendJsonFrame(frame) {
320
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
321
+ throw new Error("v2 transport is not connected");
322
+ }
323
+ this.ws.send(JSON.stringify(frame));
324
+ }
325
+ sendBinaryFrame(ciphertext) {
326
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
327
+ throw new Error("v2 transport is not connected");
328
+ }
329
+ const framed = encodeV2EncryptedFrame(ciphertext);
330
+ this.ws.send(framed);
331
+ }
332
+ markSecure() {
333
+ this.state = "secure";
334
+ if (this.secureReadyResolve) {
335
+ this.secureReadyResolve();
336
+ this.secureReadyResolve = null;
337
+ this.secureReadyReject = null;
338
+ }
339
+ }
340
+ failSecure(error) {
341
+ if (this.secureReadyReject) {
342
+ this.secureReadyReject(error);
343
+ this.secureReadyResolve = null;
344
+ this.secureReadyReject = null;
345
+ }
346
+ }
347
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "jukto-cli",
3
+ "version": "0.1.0",
4
+ "author": {
5
+ "name": "Saif Ahmed",
6
+ "email": "codingwebsa@gmail.com",
7
+ "username": "ahm0xc"
8
+ },
9
+ "license": "MIT",
10
+ "type": "module",
11
+ "bin": {
12
+ "jukto-cli": "dist/index.js"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "bin"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc && npm run prepare-bin",
20
+ "dev": "tsc && node dist/index.js",
21
+ "prepare-bin": "node -e \"require('fs').chmodSync('dist/index.js', 0o755)\"",
22
+ "prepublishOnly": "npm run build"
23
+ },
24
+ "dependencies": {
25
+ "@opencode-ai/sdk": "1.3.15",
26
+ "ignore": "^6.0.2",
27
+ "libsodium-wrappers": "^0.7.15",
28
+ "qrcode-terminal": "^0.12.0",
29
+ "shelljs": "^0.10.0",
30
+ "ws": "^8.18.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/minimatch": "^5.1.2",
34
+ "@types/node": "^20.0.0",
35
+ "@types/qrcode-terminal": "^0.12.2",
36
+ "@types/shelljs": "^0.10.0",
37
+ "@types/ws": "^8.5.13",
38
+ "typescript": "^5.0.0"
39
+ },
40
+ "engines": {
41
+ "node": ">=18.0.0"
42
+ }
43
+ }