quvur-dev 1.0.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.
package/dist/index.cjs ADDED
@@ -0,0 +1,789 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/commands/index.ts
27
+ var import_commander7 = require("commander");
28
+
29
+ // src/core/ws-client.ts
30
+ var import_ws = require("ws");
31
+
32
+ // src/utils/config.ts
33
+ var path = __toESM(require("path"), 1);
34
+ var fs = __toESM(require("fs"), 1);
35
+ var os = __toESM(require("os"), 1);
36
+
37
+ // src/utils/logger.ts
38
+ var import_chalk = __toESM(require("chalk"), 1);
39
+ var logger = {
40
+ info: (...msg) => console.log(import_chalk.default.blue("[\u2139]"), ...msg),
41
+ next: (...msg) => console.log(import_chalk.default.green("[\u2192]"), ...msg),
42
+ success: (...msg) => console.log(import_chalk.default.green("[\u2714]"), ...msg),
43
+ error: (...msg) => console.error(import_chalk.default.red("[\u2716]"), ...msg),
44
+ debug: (...msg) => console.debug(import_chalk.default.bgRed("[debug]"), ...msg),
45
+ warn: (...msg) => console.warn(import_chalk.default.bgYellowBright("[warn]"), ...msg),
46
+ log: (...msg) => console.log(...msg)
47
+ };
48
+
49
+ // src/utils/config.ts
50
+ var localConfigPath = path.join(process.cwd(), "config.json");
51
+ var configDir = path.join(os.homedir(), ".quvur");
52
+ var configPath = path.join(os.homedir(), ".quvur", "config.json");
53
+ var loadConfig = () => {
54
+ if (!fs.existsSync(configPath)) {
55
+ if (fs.existsSync(localConfigPath)) {
56
+ return loadConfigFromLocal();
57
+ }
58
+ throw new Error("Config file not found. Run `quvur auth` first.");
59
+ }
60
+ const raw = fs.readFileSync(configPath, "utf-8");
61
+ try {
62
+ const config2 = JSON.parse(raw);
63
+ return config2;
64
+ } catch (error) {
65
+ throw new Error("Invalid config file. Run `quvur auth` first.");
66
+ }
67
+ };
68
+ var loadConfigFromLocal = () => {
69
+ const raw = fs.readFileSync(localConfigPath, "utf-8");
70
+ try {
71
+ const config2 = JSON.parse(raw);
72
+ fs.mkdirSync(configDir, { recursive: true });
73
+ fs.writeFileSync(configPath, raw);
74
+ return config2;
75
+ } catch (error) {
76
+ logger.error(error);
77
+ throw new Error("Invalid config file");
78
+ }
79
+ };
80
+ var saveConfig = (config2) => {
81
+ fs.writeFileSync(configPath, JSON.stringify(config2, null, 2));
82
+ };
83
+ var config = loadConfig();
84
+
85
+ // src/auth/token-manager.ts
86
+ var import_axios = __toESM(require("axios"), 1);
87
+ var TokenManager = class {
88
+ async getValidAccessToken() {
89
+ const { accessToken, refreshToken } = config.auth;
90
+ if (!accessToken) {
91
+ logger.error("No access token found. Please authenticate first.");
92
+ return null;
93
+ }
94
+ try {
95
+ await this.tokenExchange(accessToken);
96
+ logger.debug("Token exchanged");
97
+ return accessToken;
98
+ } catch (error) {
99
+ if (refreshToken) {
100
+ try {
101
+ const newAccessToken = await this.refreshTokens(refreshToken);
102
+ logger.debug("Token refreshed");
103
+ return newAccessToken;
104
+ } catch (error2) {
105
+ if (error2 instanceof Error) {
106
+ logger.error(`Failed to refresh tokens: ${error2.message}`);
107
+ }
108
+ this.clearTokens();
109
+ return null;
110
+ }
111
+ }
112
+ if (error instanceof Error) {
113
+ logger.error(`Unauthorized. ${error.message}`);
114
+ } else {
115
+ logger.error("Error during token exchange");
116
+ }
117
+ return null;
118
+ }
119
+ }
120
+ saveTokens(accessToken, refreshToken) {
121
+ config.auth.accessToken = accessToken;
122
+ config.auth.refreshToken = refreshToken;
123
+ saveConfig(config);
124
+ }
125
+ clearTokens() {
126
+ config.auth.accessToken = null;
127
+ config.auth.refreshToken = null;
128
+ saveConfig(config);
129
+ }
130
+ async refreshTokens(refreshToken) {
131
+ try {
132
+ const response = await import_axios.default.post(
133
+ `${config.apiBaseUrl}/auth/refresh-token`,
134
+ { refresh_token: refreshToken }
135
+ );
136
+ const { data: tokens } = response;
137
+ config.auth = {
138
+ accessToken: tokens.access_token,
139
+ refreshToken: tokens.refresh_token
140
+ };
141
+ saveConfig(config);
142
+ return tokens.access_token;
143
+ } catch (error) {
144
+ if (error instanceof import_axios.AxiosError) {
145
+ const errorData = error.response?.data;
146
+ if (!errorData) throw error;
147
+ throw new Error(errorData.error);
148
+ }
149
+ throw error;
150
+ }
151
+ }
152
+ async tokenExchange(accessToken) {
153
+ try {
154
+ const response = await import_axios.default.post(
155
+ `${config.apiBaseUrl}/auth/token-exchange`,
156
+ {
157
+ access_token: accessToken
158
+ }
159
+ );
160
+ const { data } = response;
161
+ return data.access_token;
162
+ } catch (error) {
163
+ if (error instanceof import_axios.AxiosError) {
164
+ const errorData = error.response?.data;
165
+ if (!errorData) throw error;
166
+ throw new Error(errorData.error);
167
+ }
168
+ throw error;
169
+ }
170
+ }
171
+ };
172
+ var tokenManager = new TokenManager();
173
+
174
+ // src/core/ws-client.ts
175
+ var connectWebSocket = async (data) => {
176
+ const url = new URL(config.wsBaseUrl);
177
+ Object.entries(data).forEach(([key, value]) => {
178
+ if (value) url.searchParams.set(key, String(value));
179
+ });
180
+ const accessToken = await tokenManager.getValidAccessToken();
181
+ if (!accessToken) return null;
182
+ const ws = new import_ws.WebSocket(url, {
183
+ headers: {
184
+ Authorization: `Bearer ${accessToken}`
185
+ }
186
+ });
187
+ return ws;
188
+ };
189
+
190
+ // src/transport/http-tunnel.ts
191
+ var import_node_http = __toESM(require("http"), 1);
192
+ var import_ws3 = require("ws");
193
+
194
+ // src/@protocol/binary-frame.ts
195
+ var encodeBinaryFrame = (frame) => {
196
+ const idBuf = Buffer.from(frame.requestId);
197
+ const idLen = Buffer.alloc(4);
198
+ idLen.writeUInt32BE(idBuf.length);
199
+ return Buffer.concat([idLen, idBuf, frame.chunk]);
200
+ };
201
+ var decodeBinaryFrame = (buffer) => {
202
+ const idLen = buffer.readUInt32BE(0);
203
+ const idStart = 4;
204
+ const idEnd = 4 + idLen;
205
+ if (buffer.length <= idEnd) return null;
206
+ const requestId = buffer.subarray(idStart, idEnd).toString();
207
+ const chunk = buffer.subarray(idEnd);
208
+ return { requestId, chunk };
209
+ };
210
+
211
+ // src/@protocol/schemas/index.ts
212
+ var import_zod6 = __toESM(require("zod"), 1);
213
+
214
+ // src/@protocol/schemas/request.schema.ts
215
+ var import_zod2 = __toESM(require("zod"), 1);
216
+
217
+ // src/@protocol/schemas/base.schema.ts
218
+ var import_zod = __toESM(require("zod"), 1);
219
+
220
+ // src/@protocol/constants.ts
221
+ var PROTOCOL_VERSION = "1.0.0";
222
+ var JsonMessageType = /* @__PURE__ */ ((JsonMessageType2) => {
223
+ JsonMessageType2["CreateDeviceSession"] = "create-device-session";
224
+ JsonMessageType2["AuthSuccess"] = "auth-success";
225
+ JsonMessageType2["AuthError"] = "auth-error";
226
+ JsonMessageType2["RequestStart"] = "request-start";
227
+ JsonMessageType2["RequestEnd"] = "request-end";
228
+ JsonMessageType2["ResponseStart"] = "response-start";
229
+ JsonMessageType2["ResponseEnd"] = "response-end";
230
+ JsonMessageType2["TunnelCreated"] = "tunnel-created";
231
+ JsonMessageType2["Error"] = "error";
232
+ return JsonMessageType2;
233
+ })(JsonMessageType || {});
234
+
235
+ // src/@protocol/schemas/base.schema.ts
236
+ var baseSchema = import_zod.default.object({
237
+ version: import_zod.default.string().default(PROTOCOL_VERSION),
238
+ type: import_zod.default.enum(JsonMessageType)
239
+ });
240
+
241
+ // src/@protocol/schemas/request.schema.ts
242
+ var requestStartSchema = baseSchema.extend({
243
+ type: import_zod2.default.literal("request-start" /* RequestStart */),
244
+ requestId: import_zod2.default.string(),
245
+ method: import_zod2.default.string(),
246
+ url: import_zod2.default.string(),
247
+ headers: import_zod2.default.record(import_zod2.default.string(), import_zod2.default.string())
248
+ });
249
+ var requestEndSchema = baseSchema.extend({
250
+ type: import_zod2.default.literal("request-end" /* RequestEnd */),
251
+ requestId: import_zod2.default.string()
252
+ });
253
+
254
+ // src/@protocol/schemas/response.schema.ts
255
+ var import_zod3 = __toESM(require("zod"), 1);
256
+ var responseStartSchema = baseSchema.extend({
257
+ type: import_zod3.default.literal("response-start" /* ResponseStart */),
258
+ requestId: import_zod3.default.string(),
259
+ statusCode: import_zod3.default.number().int().min(100).max(599),
260
+ headers: import_zod3.default.record(import_zod3.default.string(), import_zod3.default.string())
261
+ });
262
+ var responseEndSchema = baseSchema.extend({
263
+ type: import_zod3.default.literal("response-end" /* ResponseEnd */),
264
+ requestId: import_zod3.default.string()
265
+ });
266
+
267
+ // src/@protocol/schemas/tunnel.schema.ts
268
+ var import_zod4 = __toESM(require("zod"), 1);
269
+ var tunnelCreatedSchema = baseSchema.extend({
270
+ type: import_zod4.default.literal("tunnel-created" /* TunnelCreated */),
271
+ tunnelId: import_zod4.default.string(),
272
+ subdomain: import_zod4.default.string(),
273
+ host: import_zod4.default.string()
274
+ });
275
+
276
+ // src/@protocol/schemas/error-schema.ts
277
+ var import_zod5 = __toESM(require("zod"), 1);
278
+ var errorSchema = baseSchema.extend({
279
+ type: import_zod5.default.literal("error" /* Error */),
280
+ message: import_zod5.default.string()
281
+ });
282
+
283
+ // src/@protocol/schemas/index.ts
284
+ var jsonMessageSchema = import_zod6.default.discriminatedUnion("type", [
285
+ requestStartSchema,
286
+ requestEndSchema,
287
+ responseStartSchema,
288
+ responseEndSchema,
289
+ tunnelCreatedSchema,
290
+ errorSchema
291
+ ]);
292
+
293
+ // src/@protocol/logger/index.ts
294
+ var import_chalk2 = __toESM(require("chalk"), 1);
295
+ var protoLogger = {
296
+ info: (...msg) => console.log(
297
+ import_chalk2.default.blue("[protocol:info]"),
298
+ ...msg,
299
+ (/* @__PURE__ */ new Date()).toLocaleString(),
300
+ "\n"
301
+ ),
302
+ next: (...msg) => console.log(
303
+ import_chalk2.default.green("[protocol:next]"),
304
+ ...msg,
305
+ (/* @__PURE__ */ new Date()).toLocaleString(),
306
+ "\n"
307
+ ),
308
+ success: (...msg) => console.log(
309
+ import_chalk2.default.green("[protocol:success]"),
310
+ ...msg,
311
+ (/* @__PURE__ */ new Date()).toLocaleString(),
312
+ "\n"
313
+ ),
314
+ error: (...msg) => console.error(
315
+ import_chalk2.default.red("[protocol:error]"),
316
+ ...msg,
317
+ (/* @__PURE__ */ new Date()).toLocaleString(),
318
+ "\n"
319
+ ),
320
+ debug: (...msg) => console.debug(
321
+ import_chalk2.default.bgRed("[protocol:debug]"),
322
+ ...msg,
323
+ (/* @__PURE__ */ new Date()).toLocaleString(),
324
+ "\n"
325
+ ),
326
+ warn: (...msg) => console.warn(
327
+ import_chalk2.default.bgYellowBright("[protocol:warn]"),
328
+ ...msg,
329
+ (/* @__PURE__ */ new Date()).toLocaleString(),
330
+ "\n"
331
+ ),
332
+ log: (...msg) => console.log("[protocol:log]", ...msg, (/* @__PURE__ */ new Date()).toLocaleString(), "\n")
333
+ };
334
+
335
+ // src/@protocol/parser.ts
336
+ var parseFrame = (data, isBinary) => {
337
+ if (isBinary) {
338
+ const decoded = decodeBinaryFrame(data);
339
+ if (!decoded) return null;
340
+ return { kind: "binary" /* Binary */, data: decoded };
341
+ }
342
+ try {
343
+ const parsed = JSON.parse(data.toString("utf-8"));
344
+ const validated = jsonMessageSchema.parse(parsed);
345
+ return {
346
+ kind: "json" /* Json */,
347
+ data: validated
348
+ };
349
+ } catch (error) {
350
+ protoLogger.error("Failed to parse frame:", error);
351
+ return null;
352
+ }
353
+ };
354
+
355
+ // src/@protocol/contract.ts
356
+ var import_ws2 = require("ws");
357
+ function createProtocolSide(incomingSchema, outgoingSchema) {
358
+ return {
359
+ validateIncoming: (data) => {
360
+ try {
361
+ return incomingSchema.parse(data);
362
+ } catch (error) {
363
+ return null;
364
+ }
365
+ },
366
+ sendJson: (ws, message) => {
367
+ try {
368
+ const validated = outgoingSchema.parse(message);
369
+ ws.send(JSON.stringify(validated), { binary: false });
370
+ } catch (error) {
371
+ protoLogger.error("Failed to send JSON message:", error);
372
+ ws.close();
373
+ }
374
+ },
375
+ sendBinary(ws, frame) {
376
+ const buffer = encodeBinaryFrame(frame);
377
+ ws.send(buffer, { binary: true });
378
+ }
379
+ };
380
+ }
381
+
382
+ // src/@protocol/directions/client-to-server.ts
383
+ var import_zod7 = __toESM(require("zod"), 1);
384
+ var clientToServerSchema = import_zod7.default.discriminatedUnion("type", [
385
+ responseStartSchema,
386
+ responseEndSchema,
387
+ errorSchema
388
+ ]);
389
+
390
+ // src/@protocol/directions/server-to-client.ts
391
+ var import_zod8 = __toESM(require("zod"), 1);
392
+ var serverToClientSchema = import_zod8.default.discriminatedUnion("type", [
393
+ tunnelCreatedSchema,
394
+ requestStartSchema,
395
+ requestEndSchema,
396
+ errorSchema
397
+ ]);
398
+
399
+ // src/@protocol/index.ts
400
+ var protocol = createProtocolSide(
401
+ serverToClientSchema,
402
+ // incoming
403
+ clientToServerSchema
404
+ // outgoing
405
+ );
406
+
407
+ // src/utils/make-tunnel-url.ts
408
+ var makeTunnelUrl = (host) => {
409
+ return `http://${host}`;
410
+ };
411
+
412
+ // src/transport/http-tunnel.ts
413
+ var connections = /* @__PURE__ */ new Map();
414
+ var startHttpTransport = (ws, localPort) => {
415
+ ws.on("open", () => {
416
+ logger.info("Creating HTTP tunnel for", `localhost:${localPort}...`);
417
+ });
418
+ ws.on("message", (event, isBinary) => {
419
+ const frame = parseFrame(event, isBinary);
420
+ if (!frame) {
421
+ logger.warn("Invalid incoming frame");
422
+ return;
423
+ }
424
+ if (frame.kind === "binary" /* Binary */) {
425
+ const localRequest = connections.get(frame.data.requestId);
426
+ if (localRequest) {
427
+ localRequest.write(frame.data.chunk);
428
+ }
429
+ return;
430
+ }
431
+ const message = protocol.validateIncoming(frame.data);
432
+ if (!message) {
433
+ logger.warn("Invalid direction message");
434
+ return;
435
+ }
436
+ if (message.type === "error" /* Error */) {
437
+ logger.error(message.message);
438
+ return ws.close();
439
+ }
440
+ if (message.type === "tunnel-created" /* TunnelCreated */) {
441
+ logger.success("Tunnel ready");
442
+ logger.next("URL:", makeTunnelUrl(message.host));
443
+ return;
444
+ }
445
+ if (message.type === "request-start" /* RequestStart */) {
446
+ const localRequest = import_node_http.default.request({
447
+ hostname: "127.0.0.1",
448
+ port: localPort,
449
+ method: message.method,
450
+ headers: message.headers,
451
+ path: message.url
452
+ });
453
+ connections.set(message.requestId, localRequest);
454
+ localRequest.on("response", (localResponse) => {
455
+ protocol.sendJson(ws, {
456
+ type: "response-start" /* ResponseStart */,
457
+ version: PROTOCOL_VERSION,
458
+ requestId: message.requestId,
459
+ statusCode: localResponse.statusCode || 200,
460
+ headers: Object(localResponse.headers)
461
+ });
462
+ localResponse.on("data", (chunk) => {
463
+ protocol.sendBinary(ws, { requestId: message.requestId, chunk });
464
+ });
465
+ localResponse.on("end", () => {
466
+ protocol.sendJson(ws, {
467
+ type: "response-end" /* ResponseEnd */,
468
+ version: PROTOCOL_VERSION,
469
+ requestId: message.requestId
470
+ });
471
+ });
472
+ });
473
+ localRequest.on("error", (error) => {
474
+ logger.error(`HTTP request error: ${error.message}`);
475
+ protocol.sendJson(ws, {
476
+ type: "response-start" /* ResponseStart */,
477
+ version: PROTOCOL_VERSION,
478
+ requestId: message.requestId,
479
+ statusCode: 500,
480
+ headers: {}
481
+ });
482
+ protocol.sendJson(ws, {
483
+ type: "response-end" /* ResponseEnd */,
484
+ version: PROTOCOL_VERSION,
485
+ requestId: message.requestId
486
+ });
487
+ ws.close();
488
+ });
489
+ } else if (message.type === "request-end" /* RequestEnd */) {
490
+ const localRequest = connections.get(message.requestId);
491
+ if (localRequest) {
492
+ localRequest.end();
493
+ }
494
+ }
495
+ });
496
+ ws.on("close", () => {
497
+ logger.info("WebSocket connection closed");
498
+ process.exit(1);
499
+ });
500
+ ws.on("error", (error) => {
501
+ logger.error(`Websocket error: ${error.message || error}`);
502
+ process.exit(1);
503
+ });
504
+ };
505
+
506
+ // src/core/tunnel.ts
507
+ async function startHttpTunnel(data) {
508
+ const ws = await connectWebSocket(data);
509
+ if (!ws) return;
510
+ startHttpTransport(ws, data.port);
511
+ }
512
+
513
+ // src/commands/validators/http.validator.ts
514
+ var import_commander = require("commander");
515
+ var validatePort = (value) => {
516
+ const parsed = Number(value);
517
+ if (!Number.isInteger(parsed)) {
518
+ throw new import_commander.InvalidArgumentError("port must be an integer");
519
+ }
520
+ if (parsed <= 0 || parsed > 65535) {
521
+ throw new import_commander.InvalidArgumentError("port must be between 1 and 65535");
522
+ }
523
+ return parsed;
524
+ };
525
+ var validateSubdomain = (value) => {
526
+ if (value.length < 3) {
527
+ throw new import_commander.InvalidArgumentError(
528
+ "subdomain must be at least 3 characters long"
529
+ );
530
+ }
531
+ if (value.length > 63) {
532
+ throw new import_commander.InvalidArgumentError("subdomain must be less than 63 characters");
533
+ }
534
+ const regex = /^[a-z0-9][a-z0-9-]*$/;
535
+ if (!regex.test(value)) {
536
+ throw new import_commander.InvalidArgumentError(
537
+ "subdomain must contain only lowercase letters, numbers, and hyphens"
538
+ );
539
+ }
540
+ return value;
541
+ };
542
+
543
+ // src/commands/http.ts
544
+ var registerHttpCommand = (program2, config2) => {
545
+ program2.command("http").description(config2.description).usage(config2.usage).argument("<port>", "Local port to expose", validatePort).option(
546
+ "-s, --subdomain <name>",
547
+ "Request a custom subdomain",
548
+ validateSubdomain
549
+ ).addHelpText("after", `Examples:
550
+ ${config2.examples.join("\n ")}`).action(async (port, options) => {
551
+ await startHttpTunnel({
552
+ port,
553
+ subdomain: options.subdomain,
554
+ type: "http" /* http */
555
+ });
556
+ });
557
+ };
558
+
559
+ // src/commands/help.ts
560
+ var import_commander2 = require("commander");
561
+ var registerHelpSubcommand = (program2) => {
562
+ program2.addHelpText(
563
+ "after",
564
+ `
565
+ ${config.help.intro}
566
+
567
+ Examples:
568
+ ${config.help.examples.join("\n ")}`
569
+ );
570
+ };
571
+
572
+ // src/commands/tcp.ts
573
+ var import_commander3 = require("commander");
574
+ var registerTcpCommand = (program2, config2) => {
575
+ program2.command("tcp").description(config2.description).usage(config2.usage).argument("<port>", "Local TCP port to expose").option("-t <token>", "Override auth token").addHelpText("after", `
576
+ Examples:
577
+ ${config2.examples.join("\n ")}`).action((port, options) => {
578
+ logger.success("TCP tunnel starting on port:", port);
579
+ logger.info("Options:", options);
580
+ });
581
+ };
582
+
583
+ // src/commands/auth.ts
584
+ var import_commander4 = require("commander");
585
+
586
+ // src/auth/auth-service.ts
587
+ var import_axios2 = __toESM(require("axios"), 1);
588
+
589
+ // src/events/auth-emitter.ts
590
+ var import_events = require("events");
591
+ var AuthEventEmitter = class extends import_events.EventEmitter {
592
+ emit(event, payload) {
593
+ return super.emit(event, payload);
594
+ }
595
+ on(event, listener) {
596
+ return super.on(event, listener);
597
+ }
598
+ };
599
+ var authEmitter = new AuthEventEmitter();
600
+
601
+ // src/auth/auth-service.ts
602
+ var AuthService = class {
603
+ async startLogin() {
604
+ try {
605
+ authEmitter.emit("auth:start");
606
+ const response = await import_axios2.default.post(
607
+ `${config.apiBaseUrl}/auth/github/device-start`
608
+ );
609
+ const { data } = response;
610
+ authEmitter.emit("auth:code", {
611
+ user_code: data.user_code,
612
+ verification_uri: data.verification_uri
613
+ });
614
+ this.startPolling(data.device_code, data.interval, data.expires_in);
615
+ } catch (error) {
616
+ authEmitter.emit("auth:error", error);
617
+ }
618
+ }
619
+ async startPolling(deviceCode, interval, expiresIn) {
620
+ const expiresAt = /* @__PURE__ */ new Date();
621
+ expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
622
+ while (Date.now() < expiresAt.getTime()) {
623
+ try {
624
+ const response = await import_axios2.default.post(
625
+ `${config.apiBaseUrl}/auth/github/device-poll`,
626
+ { device_code: deviceCode }
627
+ );
628
+ const { data } = response;
629
+ authEmitter.emit("auth:success", {
630
+ accessToken: data.access_token,
631
+ refreshToken: data.refresh_token,
632
+ user: data.user
633
+ });
634
+ return authEmitter.emit("auth:completed");
635
+ } catch (error) {
636
+ if (error instanceof import_axios2.AxiosError) {
637
+ const errorData = error.response?.data;
638
+ if (!errorData) {
639
+ authEmitter.emit("auth:error", error);
640
+ return;
641
+ }
642
+ if (errorData.error === "authorization_pending" /* AUTHORIZATION_PENDING */) {
643
+ await this.wait(interval);
644
+ continue;
645
+ }
646
+ if (errorData.error === "slow_down" /* SLOW_DOWN */ && errorData.interval) {
647
+ authEmitter.emit("auth:slow_down");
648
+ await this.wait(errorData.interval);
649
+ continue;
650
+ }
651
+ if (errorData.error === "expired_token" /* EXPIRED_TOKEN */) {
652
+ authEmitter.emit("auth:expired");
653
+ return;
654
+ }
655
+ authEmitter.emit(
656
+ "auth:error",
657
+ new Error(errorData.error_description)
658
+ );
659
+ return;
660
+ }
661
+ return authEmitter.emit("auth:error", error);
662
+ }
663
+ }
664
+ }
665
+ async wait(seconds) {
666
+ await new Promise((resolve) => setTimeout(resolve, seconds * 1e3));
667
+ }
668
+ async logout(refreshToken) {
669
+ try {
670
+ const response = await import_axios2.default.post(
671
+ `${config.apiBaseUrl}/auth/logout`,
672
+ {
673
+ refresh_token: refreshToken
674
+ },
675
+ {
676
+ headers: {
677
+ Authorization: `Bearer ${config.auth.accessToken}`
678
+ }
679
+ }
680
+ );
681
+ return true;
682
+ } catch (error) {
683
+ if (error instanceof import_axios2.AxiosError) {
684
+ const errorData = error.response?.data;
685
+ if (!errorData) throw error;
686
+ throw new Error(`Logout failed: ${errorData.error}`);
687
+ }
688
+ throw error;
689
+ }
690
+ }
691
+ };
692
+ var authService = new AuthService();
693
+
694
+ // src/commands/auth.ts
695
+ var import_chalk3 = __toESM(require("chalk"), 1);
696
+ var registerAuthCommand = (program2, commandConfig) => {
697
+ program2.command("auth").description(commandConfig.description).usage(commandConfig.usage).addHelpText(
698
+ "after",
699
+ `
700
+ Examples:
701
+ ${commandConfig.examples.join("\n ")}`
702
+ ).action(async () => {
703
+ authEmitter.on("auth:start", () => {
704
+ logger.info("Starting GitHub authentication...");
705
+ });
706
+ authEmitter.on("auth:code", ({ user_code, verification_uri }) => {
707
+ logger.info("Please visit the following URL to authorize the CLI:");
708
+ logger.log(import_chalk3.default.bold.cyan(verification_uri));
709
+ logger.info(`Enter the code: ${import_chalk3.default.bold.yellow(user_code)}`);
710
+ });
711
+ authEmitter.on("auth:success", ({ accessToken, refreshToken, user }) => {
712
+ tokenManager.saveTokens(accessToken, refreshToken);
713
+ logger.success(
714
+ `Successfully authenticated as ${import_chalk3.default.bold.green(`${user.name} (${user.username})`)}!`
715
+ );
716
+ });
717
+ authEmitter.on("auth:error", (error) => {
718
+ logger.error(`Authentication failed: ${error.message}`);
719
+ process.exit(1);
720
+ });
721
+ authEmitter.on("auth:expired", () => {
722
+ logger.error("Authentication code expired. Please try again.");
723
+ process.exit(1);
724
+ });
725
+ authEmitter.on("auth:completed", () => {
726
+ process.exit(0);
727
+ });
728
+ await authService.startLogin();
729
+ });
730
+ };
731
+
732
+ // src/commands/logout.ts
733
+ var import_commander5 = require("commander");
734
+ var registerLogoutCommand = (program2, commandConfig) => {
735
+ program2.command("logout").description(commandConfig.description).usage(commandConfig.usage).addHelpText("after", `Examples:
736
+ ${commandConfig.examples.join("\n ")}`).action(async () => {
737
+ const refreshToken = config.auth.refreshToken;
738
+ if (refreshToken) {
739
+ try {
740
+ await authService.logout(refreshToken);
741
+ logger.success("Logged out successfully.");
742
+ } catch (error) {
743
+ if (error instanceof Error) {
744
+ logger.error("Logout failed:", error.message);
745
+ } else {
746
+ logger.error("Logout failed");
747
+ }
748
+ } finally {
749
+ tokenManager.clearTokens();
750
+ }
751
+ }
752
+ });
753
+ };
754
+
755
+ // src/commands/config.ts
756
+ var import_commander6 = require("commander");
757
+ var registerConfigCommand = (program2, config2) => {
758
+ program2.command("config").description(config2.description).usage(config2.usage).addHelpText("after", `Examples:
759
+ ${config2.examples.join("\n ")}`).option("-r, --reset", "Reset config file options to default.").action((options) => {
760
+ if (options.reset) {
761
+ loadConfigFromLocal();
762
+ logger.success("Config file reset successfully.");
763
+ } else {
764
+ loadConfig();
765
+ logger.success("Config file loaded successfully.");
766
+ }
767
+ });
768
+ };
769
+
770
+ // src/commands/index.ts
771
+ var program = new import_commander7.Command();
772
+ program.name(config.name).version(config.version).description(config.description).usage(config.usage).configureOutput({
773
+ writeOut: (message) => logger.info(message),
774
+ writeErr: (message) => logger.error(message)
775
+ });
776
+ registerHttpCommand(program, config.help.commands.http);
777
+ registerTcpCommand(program, config.help.commands.tcp);
778
+ registerAuthCommand(program, config.help.commands.auth);
779
+ registerLogoutCommand(program, config.help.commands.logout);
780
+ registerConfigCommand(program, config.help.commands.config);
781
+ registerHelpSubcommand(program);
782
+
783
+ // src/index.ts
784
+ process.on("SIGINT", () => {
785
+ logger.log("\n");
786
+ logger.warn("Shutting down tunnel...\n");
787
+ process.exit(0);
788
+ });
789
+ program.parse(process.argv);
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "quvur-dev",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "license": "ISC",
6
+ "author": "Muhammadali Yuldoshev <mukhammadaliweb@gmail.com>",
7
+ "type": "module",
8
+ "main": "dist/index.js",
9
+ "bin": {
10
+ "quvur": "dist/index.cjs"
11
+ },
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "scripts": {
16
+ "build": "npx tsup src/index.ts --dts",
17
+ "start:dev": "tsx --watch src/index.ts",
18
+ "prepublishOnly": "npm run build"
19
+ },
20
+ "devDependencies": {
21
+ "@types/axios": "^0.9.36",
22
+ "@types/node": "^25.2.3",
23
+ "@types/ws": "^8.18.1",
24
+ "tsup": "^8.5.1",
25
+ "tsx": "^4.21.0",
26
+ "typescript": "^5.9.3"
27
+ },
28
+ "dependencies": {
29
+ "axios": "^1.13.5",
30
+ "chalk": "^5.6.2",
31
+ "commander": "^14.0.3",
32
+ "dotenv": "^17.3.1",
33
+ "ws": "^8.19.0",
34
+ "zod": "^4.3.6"
35
+ }
36
+ }