@babylen/legion 0.1.4 → 0.1.5

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 +449 -49
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -27,7 +27,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
27
  var import_child_process = require("child_process");
28
28
 
29
29
  // package.json
30
- var version = "0.1.4";
30
+ var version = "0.1.5";
31
31
 
32
32
  // src/index.ts
33
33
  var import_socket = require("socket.io-client");
@@ -43,7 +43,9 @@ var ConfigSchema = import_zod.z.object({
43
43
  // Token ID from database (for faster lookup)
44
44
  token: import_zod.z.string(),
45
45
  // Secret token (lg_xxx format)
46
- serverUrl: import_zod.z.string().url()
46
+ serverUrl: import_zod.z.string().url(),
47
+ allowedPaths: import_zod.z.array(import_zod.z.string()).optional()
48
+ // Whitelist paths for file system access
47
49
  });
48
50
  var HOME_DIR = import_os.default.homedir();
49
51
  var CONFIG_DIR = import_path.default.join(HOME_DIR, ".tanuki");
@@ -96,8 +98,17 @@ async function getConfig() {
96
98
  if (id) {
97
99
  config2.id = id;
98
100
  }
101
+ if (fileConfig?.allowedPaths) {
102
+ config2.allowedPaths = fileConfig.allowedPaths;
103
+ }
99
104
  return config2;
100
105
  }
106
+ function getAllowedPaths(config2) {
107
+ if (config2.allowedPaths && config2.allowedPaths.length > 0) {
108
+ return config2.allowedPaths.map((p) => import_path.default.resolve(p));
109
+ }
110
+ return [HOME_DIR];
111
+ }
101
112
  async function updateConfig(updates) {
102
113
  const currentConfig = await loadConfig();
103
114
  if (!currentConfig) {
@@ -326,6 +337,389 @@ async function getSystemFingerprint() {
326
337
  return cachedFingerprint;
327
338
  }
328
339
 
340
+ // src/file/legion-bridge.ts
341
+ var import_promises4 = __toESM(require("fs/promises"));
342
+ var import_path4 = __toESM(require("path"));
343
+ var log = Log.create({ service: "file.legion-bridge" });
344
+ async function listFiles(targetPath, depth = 1) {
345
+ const resolved = import_path4.default.resolve(targetPath);
346
+ const exclude = [".git", ".DS_Store", ".tanuki"];
347
+ const nodes = [];
348
+ try {
349
+ const entries = await import_promises4.default.readdir(resolved, { withFileTypes: true });
350
+ for (const entry of entries) {
351
+ if (exclude.includes(entry.name)) continue;
352
+ const fullPath = import_path4.default.join(resolved, entry.name);
353
+ const isDirectory = entry.isDirectory();
354
+ const node = {
355
+ name: entry.name,
356
+ path: fullPath,
357
+ type: isDirectory ? "directory" : "file"
358
+ };
359
+ if (!isDirectory) {
360
+ try {
361
+ const stats = await import_promises4.default.stat(fullPath);
362
+ node.size = stats.size;
363
+ } catch {
364
+ }
365
+ }
366
+ nodes.push(node);
367
+ }
368
+ } catch (error) {
369
+ log.error("Failed to list directory", { error, targetPath });
370
+ throw error;
371
+ }
372
+ return nodes.sort((a, b) => {
373
+ if (a.type !== b.type) {
374
+ return a.type === "directory" ? -1 : 1;
375
+ }
376
+ return a.name.localeCompare(b.name);
377
+ });
378
+ }
379
+ async function shouldEncode(file) {
380
+ const type = file.type?.toLowerCase();
381
+ if (!type) return false;
382
+ if (type.startsWith("text/")) return false;
383
+ if (type.includes("charset=")) return false;
384
+ const parts = type.split("/", 2);
385
+ const top = parts[0];
386
+ const rest = parts[1] ?? "";
387
+ const sub = rest.split(";", 1)[0];
388
+ const tops = ["image", "audio", "video", "font", "model", "multipart"];
389
+ if (tops.includes(top)) return true;
390
+ const bins = [
391
+ "zip",
392
+ "gzip",
393
+ "bzip",
394
+ "compressed",
395
+ "binary",
396
+ "pdf",
397
+ "msword",
398
+ "vnd.ms",
399
+ "octet-stream"
400
+ ];
401
+ if (bins.includes(sub)) return true;
402
+ try {
403
+ const buffer = await file.arrayBuffer();
404
+ if (buffer.byteLength === 0) return false;
405
+ const view = new Uint8Array(buffer.slice(0, 512));
406
+ return view.some((byte) => byte === 0);
407
+ } catch {
408
+ return false;
409
+ }
410
+ }
411
+ async function readFile(filePath, maxSize = 1024 * 1024) {
412
+ const resolved = import_path4.default.resolve(filePath);
413
+ const bunFile = Bun.file(resolved);
414
+ if (!await bunFile.exists()) {
415
+ throw new Error(`File not found: ${filePath}`);
416
+ }
417
+ let stats;
418
+ try {
419
+ stats = await import_promises4.default.stat(resolved);
420
+ } catch (error) {
421
+ throw new Error(`Failed to stat file: ${filePath}`);
422
+ }
423
+ if (stats.size > maxSize) {
424
+ return {
425
+ type: "blob",
426
+ size: stats.size,
427
+ error: "too_large"
428
+ };
429
+ }
430
+ const encode = await shouldEncode(bunFile);
431
+ if (encode) {
432
+ const buffer = await bunFile.arrayBuffer();
433
+ const content2 = Buffer.from(buffer).toString("base64");
434
+ const mimeType = bunFile.type || "application/octet-stream";
435
+ return {
436
+ type: "blob",
437
+ content: content2,
438
+ encoding: "base64",
439
+ mimeType,
440
+ size: stats.size
441
+ };
442
+ }
443
+ const content = await bunFile.text().catch(() => "");
444
+ return {
445
+ type: "text",
446
+ content,
447
+ encoding: "utf-8",
448
+ size: stats.size
449
+ };
450
+ }
451
+
452
+ // src/core/path-validator.ts
453
+ var import_path6 = __toESM(require("path"));
454
+ var import_fs3 = require("fs");
455
+
456
+ // src/util/filesystem.ts
457
+ var import_fs2 = require("fs");
458
+ var import_path5 = require("path");
459
+ var Filesystem;
460
+ ((Filesystem2) => {
461
+ Filesystem2.exists = (p) => Bun.file(p).stat().then(() => true).catch(() => false);
462
+ Filesystem2.isDir = (p) => Bun.file(p).stat().then((s) => s.isDirectory()).catch(() => false);
463
+ function normalizePath(p) {
464
+ if (process.platform !== "win32") return p;
465
+ try {
466
+ return import_fs2.realpathSync.native(p);
467
+ } catch {
468
+ return p;
469
+ }
470
+ }
471
+ Filesystem2.normalizePath = normalizePath;
472
+ function overlaps(a, b) {
473
+ const relA = (0, import_path5.relative)(a, b);
474
+ const relB = (0, import_path5.relative)(b, a);
475
+ return !relA || !relA.startsWith("..") || !relB || !relB.startsWith("..");
476
+ }
477
+ Filesystem2.overlaps = overlaps;
478
+ function contains(parent, child) {
479
+ return !(0, import_path5.relative)(parent, child).startsWith("..");
480
+ }
481
+ Filesystem2.contains = contains;
482
+ async function findUp(target, start, stop) {
483
+ let current = start;
484
+ const result = [];
485
+ while (true) {
486
+ const search = (0, import_path5.join)(current, target);
487
+ if (await (0, Filesystem2.exists)(search)) result.push(search);
488
+ if (stop === current) break;
489
+ const parent = (0, import_path5.dirname)(current);
490
+ if (parent === current) break;
491
+ current = parent;
492
+ }
493
+ return result;
494
+ }
495
+ Filesystem2.findUp = findUp;
496
+ async function* up(options) {
497
+ const { targets, start, stop } = options;
498
+ let current = start;
499
+ while (true) {
500
+ for (const target of targets) {
501
+ const search = (0, import_path5.join)(current, target);
502
+ if (await (0, Filesystem2.exists)(search)) yield search;
503
+ }
504
+ if (stop === current) break;
505
+ const parent = (0, import_path5.dirname)(current);
506
+ if (parent === current) break;
507
+ current = parent;
508
+ }
509
+ }
510
+ Filesystem2.up = up;
511
+ async function globUp(pattern, start, stop) {
512
+ let current = start;
513
+ const result = [];
514
+ while (true) {
515
+ try {
516
+ const glob = new Bun.Glob(pattern);
517
+ for await (const match of glob.scan({
518
+ cwd: current,
519
+ absolute: true,
520
+ onlyFiles: true,
521
+ followSymlinks: true,
522
+ dot: true
523
+ })) {
524
+ result.push(match);
525
+ }
526
+ } catch {
527
+ }
528
+ if (stop === current) break;
529
+ const parent = (0, import_path5.dirname)(current);
530
+ if (parent === current) break;
531
+ current = parent;
532
+ }
533
+ return result;
534
+ }
535
+ Filesystem2.globUp = globUp;
536
+ })(Filesystem || (Filesystem = {}));
537
+
538
+ // src/core/path-validator.ts
539
+ var log2 = Log.create({ service: "path-validator" });
540
+ function isPathAllowed(requestedPath, allowedPaths) {
541
+ try {
542
+ const normalized = import_path6.default.resolve(requestedPath);
543
+ let realPath;
544
+ try {
545
+ realPath = (0, import_fs3.realpathSync)(normalized);
546
+ } catch {
547
+ realPath = normalized;
548
+ }
549
+ for (const allowed of allowedPaths) {
550
+ const allowedResolved = import_path6.default.resolve(allowed);
551
+ if (Filesystem.contains(allowedResolved, realPath)) {
552
+ return true;
553
+ }
554
+ }
555
+ log2.warn("Path access denied", { requestedPath, realPath, allowedPaths });
556
+ return false;
557
+ } catch (error) {
558
+ log2.error("Path validation error", { error, requestedPath });
559
+ return false;
560
+ }
561
+ }
562
+
563
+ // src/socket/handlers/fs-list.ts
564
+ async function handleFsList(req, socket) {
565
+ const { path: requestedPath, depth = 1 } = req;
566
+ const targetPath = requestedPath || process.cwd();
567
+ const config2 = socket.legionConfig;
568
+ const allowedPaths = getAllowedPaths(config2);
569
+ if (!isPathAllowed(targetPath, allowedPaths)) {
570
+ return {
571
+ id: req.id,
572
+ status: "error",
573
+ error: "Access denied: path not in whitelist"
574
+ };
575
+ }
576
+ try {
577
+ const files = await listFiles(targetPath, depth);
578
+ return {
579
+ id: req.id,
580
+ status: "ok",
581
+ data: files
582
+ };
583
+ } catch (error) {
584
+ return {
585
+ id: req.id,
586
+ status: "error",
587
+ error: error instanceof Error ? error.message : "Unknown error"
588
+ };
589
+ }
590
+ }
591
+
592
+ // src/socket/handlers/fs-read.ts
593
+ async function handleFsRead(req, socket) {
594
+ const { path: requestedPath, maxSize } = req;
595
+ if (!requestedPath) {
596
+ return {
597
+ id: req.id,
598
+ status: "error",
599
+ error: "Path is required"
600
+ };
601
+ }
602
+ const config2 = socket.legionConfig;
603
+ const allowedPaths = getAllowedPaths(config2);
604
+ if (!isPathAllowed(requestedPath, allowedPaths)) {
605
+ return {
606
+ id: req.id,
607
+ status: "error",
608
+ error: "Access denied: path not in whitelist"
609
+ };
610
+ }
611
+ try {
612
+ const content = await readFile(requestedPath, maxSize || 1024 * 1024);
613
+ return {
614
+ id: req.id,
615
+ status: "ok",
616
+ data: content
617
+ };
618
+ } catch (error) {
619
+ return {
620
+ id: req.id,
621
+ status: "error",
622
+ error: error instanceof Error ? error.message : "Unknown error"
623
+ };
624
+ }
625
+ }
626
+
627
+ // src/project/binding.ts
628
+ var import_promises5 = __toESM(require("fs/promises"));
629
+ var import_path7 = __toESM(require("path"));
630
+ var log3 = Log.create({ service: "project.binding" });
631
+ async function bindProject(targetPath, projectId, projectName) {
632
+ const resolved = import_path7.default.resolve(targetPath);
633
+ const tanukiDir = import_path7.default.join(resolved, ".tanuki");
634
+ const projectFile = import_path7.default.join(tanukiDir, "project.json");
635
+ await import_promises5.default.mkdir(tanukiDir, { recursive: true });
636
+ const config2 = {
637
+ id: projectId,
638
+ name: projectName || import_path7.default.basename(resolved)
639
+ };
640
+ await import_promises5.default.writeFile(projectFile, JSON.stringify(config2, null, 2), "utf-8");
641
+ const gitignorePath = import_path7.default.join(resolved, ".gitignore");
642
+ try {
643
+ let gitignoreContent = await import_promises5.default.readFile(gitignorePath, "utf-8");
644
+ if (!gitignoreContent.includes(".tanuki")) {
645
+ gitignoreContent += "\n.tanuki\n";
646
+ await import_promises5.default.writeFile(gitignorePath, gitignoreContent, "utf-8");
647
+ }
648
+ } catch {
649
+ await import_promises5.default.writeFile(gitignorePath, ".tanuki\n", "utf-8");
650
+ }
651
+ log3.info("Project bound", { path: resolved, projectId });
652
+ return projectFile;
653
+ }
654
+
655
+ // src/socket/handlers/project-bind.ts
656
+ async function handleProjectBind(req, socket) {
657
+ const { path: requestedPath, projectId, projectName } = req;
658
+ if (!requestedPath) {
659
+ return {
660
+ id: req.id,
661
+ status: "error",
662
+ error: "Path is required"
663
+ };
664
+ }
665
+ if (!projectId) {
666
+ return {
667
+ id: req.id,
668
+ status: "error",
669
+ error: "projectId is required"
670
+ };
671
+ }
672
+ const config2 = socket.legionConfig;
673
+ const allowedPaths = getAllowedPaths(config2);
674
+ if (!isPathAllowed(requestedPath, allowedPaths)) {
675
+ return {
676
+ id: req.id,
677
+ status: "error",
678
+ error: "Access denied: path not in whitelist"
679
+ };
680
+ }
681
+ try {
682
+ const configPath = await bindProject(requestedPath, projectId, projectName);
683
+ return {
684
+ id: req.id,
685
+ status: "ok",
686
+ data: { configPath }
687
+ };
688
+ } catch (error) {
689
+ return {
690
+ id: req.id,
691
+ status: "error",
692
+ error: error instanceof Error ? error.message : "Unknown error"
693
+ };
694
+ }
695
+ }
696
+
697
+ // src/socket/dispatcher.ts
698
+ var handlers = {
699
+ "legion:fs:list": handleFsList,
700
+ "legion:fs:read": handleFsRead,
701
+ "legion:project:bind": handleProjectBind
702
+ };
703
+ function setupDispatcher(socket, log4, config2) {
704
+ socket.legionConfig = config2;
705
+ for (const [event, handler] of Object.entries(handlers)) {
706
+ socket.on(event, async (request) => {
707
+ try {
708
+ const response = await handler(request, socket);
709
+ socket.emit(`${event}:response`, response);
710
+ } catch (error) {
711
+ log4.error(`Handler error for ${event}`, { error, request });
712
+ socket.emit(`${event}:response`, {
713
+ id: request.id,
714
+ status: "error",
715
+ error: error instanceof Error ? error.message : "Unknown error"
716
+ });
717
+ }
718
+ });
719
+ }
720
+ log4.info("Socket dispatcher initialized", { events: Object.keys(handlers) });
721
+ }
722
+
329
723
  // src/index.ts
330
724
  if (process.argv.includes("--version") || process.argv.includes("-v")) {
331
725
  console.log(version);
@@ -350,7 +744,7 @@ function parseArgs() {
350
744
  }
351
745
  return parsed;
352
746
  }
353
- async function handleDeviceLogin(log, serverUrl = "https://tanuki.sabw.ru") {
747
+ async function handleDeviceLogin(log4, serverUrl = "https://tanuki.sabw.ru") {
354
748
  const codeEndpoint = `${serverUrl}/api/v1/legion/device/code`;
355
749
  const tokenEndpoint = `${serverUrl}/api/v1/legion/device/token`;
356
750
  const codeResponse = await fetch(codeEndpoint, {
@@ -380,7 +774,7 @@ async function handleDeviceLogin(log, serverUrl = "https://tanuki.sabw.ru") {
380
774
  id: token_id,
381
775
  serverUrl: existingConfig?.serverUrl || "wss://tanuki.sabw.ru"
382
776
  });
383
- log.info("\u2705 Device activated successfully");
777
+ log4.info("\u2705 Device activated successfully");
384
778
  return;
385
779
  } else if (tokenResponse.status === 202) {
386
780
  continue;
@@ -411,20 +805,20 @@ function checkForUpdates() {
411
805
  }
412
806
  }, 100);
413
807
  }
414
- async function enterHibernationMode(log, configFile, onConfigChanged) {
415
- log.warn("\u26D4 Auth failed. Entering hibernation mode. Waiting for config update...");
416
- const fs4 = await import("fs");
808
+ async function enterHibernationMode(log4, configFile, onConfigChanged) {
809
+ log4.warn("\u26D4 Auth failed. Entering hibernation mode. Waiting for config update...");
810
+ const fs6 = await import("fs");
417
811
  return new Promise((resolve) => {
418
- const watchHandle = fs4.watch(configFile, async (eventType) => {
812
+ const watchHandle = fs6.watch(configFile, async (eventType) => {
419
813
  if (eventType === "change") {
420
- log.info("\u{1F4DD} Config file changed. Attempting reconnection...");
814
+ log4.info("\u{1F4DD} Config file changed. Attempting reconnection...");
421
815
  try {
422
816
  await new Promise((resolve2) => setTimeout(resolve2, 100));
423
817
  await onConfigChanged();
424
818
  watchHandle.close();
425
819
  resolve();
426
820
  } catch (error) {
427
- log.error("\u274C Reconnection failed", { error });
821
+ log4.error("\u274C Reconnection failed", { error });
428
822
  }
429
823
  }
430
824
  });
@@ -446,9 +840,9 @@ function createSocket(config2) {
446
840
  reconnectionAttempts: Infinity
447
841
  });
448
842
  }
449
- function setupSocketHandlers(socket, log, fingerprint, version2, onTokenRotation, onReconnect) {
843
+ function setupSocketHandlers(socket, log4, fingerprint, version2, onTokenRotation, onReconnect) {
450
844
  socket.on("connect", () => {
451
- log.info("\u2705 Connected to server", { socketId: socket.id });
845
+ log4.info("\u2705 Connected to server", { socketId: socket.id });
452
846
  const handshakeData = {
453
847
  fingerprint,
454
848
  version: version2,
@@ -458,98 +852,102 @@ function setupSocketHandlers(socket, log, fingerprint, version2, onTokenRotation
458
852
  cwd: process.cwd()
459
853
  };
460
854
  socket.emit("legion:handshake", handshakeData);
461
- log.debug("\u{1F4E4} Sent handshake", handshakeData);
855
+ log4.debug("\u{1F4E4} Sent handshake", handshakeData);
856
+ const socketConfig = socket.legionConfig;
857
+ if (socketConfig) {
858
+ setupDispatcher(socket, log4, socketConfig);
859
+ }
462
860
  });
463
861
  socket.on("connect_error", (err) => {
464
- log.error("\u274C Connection error", {
862
+ log4.error("\u274C Connection error", {
465
863
  message: err.message,
466
864
  type: err.type
467
865
  });
468
866
  if (err.message.includes("auth") || err.message.includes("token")) {
469
- log.error("\u{1F510} Authentication failed. Please check your token.");
470
- log.error("\u{1F4A1} Re-authenticate by updating ~/.tanuki/config.json or LEGION_TOKEN env var");
471
- enterHibernationMode(log, CONFIG_FILE, onReconnect).catch((error) => {
472
- log.error("\u274C Hibernation mode failed", { error });
867
+ log4.error("\u{1F510} Authentication failed. Please check your token.");
868
+ log4.error("\u{1F4A1} Re-authenticate by updating ~/.tanuki/config.json or LEGION_TOKEN env var");
869
+ enterHibernationMode(log4, CONFIG_FILE, onReconnect).catch((error) => {
870
+ log4.error("\u274C Hibernation mode failed", { error });
473
871
  process.exit(1);
474
872
  });
475
873
  }
476
874
  });
477
875
  socket.on("disconnect", (reason) => {
478
- log.warn("\u26A0\uFE0F Disconnected from server", { reason });
876
+ log4.warn("\u26A0\uFE0F Disconnected from server", { reason });
479
877
  if (reason === "io server disconnect") {
480
- log.error("\u{1F6AB} Server disconnected this client. Please check your token.");
481
- enterHibernationMode(log, CONFIG_FILE, onReconnect).catch((error) => {
482
- log.error("\u274C Hibernation mode failed", { error });
878
+ log4.error("\u{1F6AB} Server disconnected this client. Please check your token.");
879
+ enterHibernationMode(log4, CONFIG_FILE, onReconnect).catch((error) => {
880
+ log4.error("\u274C Hibernation mode failed", { error });
483
881
  process.exit(1);
484
882
  });
485
883
  }
486
884
  });
487
885
  socket.on("legion:update_token", async (data2) => {
488
886
  try {
489
- log.info("\u{1F504} Received permanent credentials. Persisting...");
887
+ log4.info("\u{1F504} Received permanent credentials. Persisting...");
490
888
  await updateConfig({
491
889
  token: data2.secret,
492
890
  // secret -> token
493
891
  id: data2.token_id
494
892
  // token_id -> id
495
893
  });
496
- log.info("\u2705 Config updated successfully. Reconnecting with new token...");
894
+ log4.info("\u2705 Config updated successfully. Reconnecting with new token...");
497
895
  await onTokenRotation();
498
896
  } catch (error) {
499
- log.error("\u274C Failed to update config", { error });
897
+ log4.error("\u274C Failed to update config", { error });
500
898
  }
501
899
  });
502
900
  socket.on("server:ping", (data2) => {
503
- log.debug("\u{1F4E9} Ping from server", data2);
901
+ log4.debug("\u{1F4E9} Ping from server", data2);
504
902
  socket.emit("legion:pong", { ts: Date.now() });
505
903
  });
506
904
  socket.on("reconnect", (attemptNumber) => {
507
- log.info("\u{1F504} Reconnected to server", { attempt: attemptNumber });
905
+ log4.info("\u{1F504} Reconnected to server", { attempt: attemptNumber });
508
906
  });
509
907
  socket.on("reconnect_attempt", (attemptNumber) => {
510
- log.debug("\u{1F504} Reconnection attempt", { attempt: attemptNumber });
908
+ log4.debug("\u{1F504} Reconnection attempt", { attempt: attemptNumber });
511
909
  });
512
910
  socket.on("reconnect_error", (error) => {
513
- log.error("\u{1F504} Reconnection error", { message: error.message });
911
+ log4.error("\u{1F504} Reconnection error", { message: error.message });
514
912
  });
515
913
  socket.on("reconnect_failed", () => {
516
- log.error("\u{1F504} Reconnection failed after all attempts");
914
+ log4.error("\u{1F504} Reconnection failed after all attempts");
517
915
  process.exit(1);
518
916
  });
519
917
  }
520
918
  async function main() {
521
- let log = null;
919
+ let log4 = null;
522
920
  const safeLog = {
523
921
  info: (message, extra) => {
524
- if (log) log.info(message, extra);
922
+ if (log4) log4.info(message, extra);
525
923
  else console.log(message, extra);
526
924
  },
527
925
  error: (message, extra) => {
528
- if (log) log.error(message, extra);
926
+ if (log4) log4.error(message, extra);
529
927
  else console.error(message, extra);
530
928
  },
531
929
  warn: (message, extra) => {
532
- if (log) log.warn(message, extra);
930
+ if (log4) log4.warn(message, extra);
533
931
  else console.warn(message, extra);
534
932
  },
535
933
  debug: (message, extra) => {
536
- if (log) log.debug(message, extra);
934
+ if (log4) log4.debug(message, extra);
537
935
  }
538
936
  };
539
937
  try {
540
- log = Log.create({ service: "legion" });
938
+ log4 = Log.create({ service: "legion" });
541
939
  const args = parseArgs();
542
940
  if (args.command === "login") {
543
941
  try {
544
- await handleDeviceLogin(log);
942
+ await handleDeviceLogin(log4);
545
943
  process.exit(0);
546
944
  } catch (error) {
547
- log.error("\u274C Device login failed", { error });
945
+ log4.error("\u274C Device login failed", { error });
548
946
  process.exit(1);
549
947
  }
550
948
  }
551
949
  if (args.token) {
552
- log.info("\u{1F511} Saving token from command line...");
950
+ log4.info("\u{1F511} Saving token from command line...");
553
951
  try {
554
952
  const defaultServerUrl = process.env.TANUKI_SERVER_URL || "wss://tanuki.sabw.ru";
555
953
  const existingConfig = await loadConfig();
@@ -558,18 +956,18 @@ async function main() {
558
956
  serverUrl: existingConfig?.serverUrl || defaultServerUrl,
559
957
  ...existingConfig?.id ? { id: existingConfig.id } : {}
560
958
  });
561
- log.info("\u2705 Token saved successfully");
959
+ log4.info("\u2705 Token saved successfully");
562
960
  } catch (error) {
563
- log.error("\u274C Failed to save token", { error });
961
+ log4.error("\u274C Failed to save token", { error });
564
962
  process.exit(1);
565
963
  }
566
964
  }
567
965
  checkForUpdates();
568
966
  await Global.init();
569
967
  const fingerprint = await getSystemFingerprint();
570
- log.info(`\u{1F6E1}\uFE0F Legion v${version} starting...`);
968
+ log4.info(`\u{1F6E1}\uFE0F Legion v${version} starting...`);
571
969
  let config2 = await getConfig();
572
- log.info("\u{1F517} Connecting to server", { serverUrl: config2.serverUrl });
970
+ log4.info("\u{1F517} Connecting to server", { serverUrl: config2.serverUrl });
573
971
  let currentSocket;
574
972
  let isTokenRotation = false;
575
973
  let handleTokenRotation;
@@ -582,9 +980,10 @@ async function main() {
582
980
  await new Promise((resolve) => setTimeout(resolve, 500));
583
981
  }
584
982
  const newConfig = await getConfig();
585
- log.info("\u{1F504} Reconnecting...");
983
+ log4.info("\u{1F504} Reconnecting...");
586
984
  currentSocket = createSocket(newConfig);
587
- setupSocketHandlers(currentSocket, log, fingerprint, version, handleTokenRotation, handleReconnect);
985
+ currentSocket.legionConfig = newConfig;
986
+ setupSocketHandlers(currentSocket, log4, fingerprint, version, handleTokenRotation, handleReconnect);
588
987
  config2 = newConfig;
589
988
  };
590
989
  handleTokenRotation = async () => {
@@ -593,20 +992,21 @@ async function main() {
593
992
  isTokenRotation = false;
594
993
  };
595
994
  currentSocket = createSocket(config2);
596
- setupSocketHandlers(currentSocket, log, fingerprint, version, handleTokenRotation, handleReconnect);
995
+ currentSocket.legionConfig = config2;
996
+ setupSocketHandlers(currentSocket, log4, fingerprint, version, handleTokenRotation, handleReconnect);
597
997
  process.stdin.resume();
598
998
  const shutdown = () => {
599
- log.info("\u{1F6D1} Shutting down...");
999
+ log4.info("\u{1F6D1} Shutting down...");
600
1000
  currentSocket.disconnect();
601
1001
  process.exit(0);
602
1002
  };
603
1003
  process.on("SIGINT", shutdown);
604
1004
  process.on("SIGTERM", shutdown);
605
1005
  process.on("unhandledRejection", (reason) => {
606
- log.error("Unhandled rejection", { reason });
1006
+ log4.error("Unhandled rejection", { reason });
607
1007
  });
608
1008
  process.on("uncaughtException", (error) => {
609
- log.error("Uncaught exception", { error: error.message, stack: error.stack });
1009
+ log4.error("Uncaught exception", { error: error.message, stack: error.stack });
610
1010
  shutdown();
611
1011
  });
612
1012
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@babylen/legion",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Legion agent for connecting devices to Tanuki Cloud",
5
5
  "main": "./dist/index.js",
6
6
  "keywords": [