@lobehub/cli 0.0.1-canary.1 → 0.0.1-canary.3
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 +355 -91
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/connect.ts
|
|
7
|
-
import
|
|
8
|
-
import
|
|
7
|
+
import fs3 from "fs";
|
|
8
|
+
import os4 from "os";
|
|
9
|
+
import path4 from "path";
|
|
9
10
|
|
|
10
11
|
// ../../packages/device-gateway-client/src/client.ts
|
|
11
12
|
import { randomUUID } from "crypto";
|
|
@@ -440,9 +441,132 @@ async function resolveToken(options) {
|
|
|
440
441
|
process.exit(1);
|
|
441
442
|
}
|
|
442
443
|
|
|
444
|
+
// src/daemon/manager.ts
|
|
445
|
+
import { spawn } from "child_process";
|
|
446
|
+
import fs2 from "fs";
|
|
447
|
+
import os3 from "os";
|
|
448
|
+
import path2 from "path";
|
|
449
|
+
var MAX_LOG_SIZE = 5 * 1024 * 1024;
|
|
450
|
+
function getLobehubDir() {
|
|
451
|
+
return path2.join(os3.homedir(), ".lobehub");
|
|
452
|
+
}
|
|
453
|
+
function getPidPath() {
|
|
454
|
+
return path2.join(getLobehubDir(), "daemon.pid");
|
|
455
|
+
}
|
|
456
|
+
function getStatusPath() {
|
|
457
|
+
return path2.join(getLobehubDir(), "daemon.status.json");
|
|
458
|
+
}
|
|
459
|
+
function getLogFilePath() {
|
|
460
|
+
return path2.join(getLobehubDir(), "daemon.log");
|
|
461
|
+
}
|
|
462
|
+
function ensureDir() {
|
|
463
|
+
fs2.mkdirSync(getLobehubDir(), { mode: 448, recursive: true });
|
|
464
|
+
}
|
|
465
|
+
function readPid() {
|
|
466
|
+
try {
|
|
467
|
+
const raw = fs2.readFileSync(getPidPath(), "utf8").trim();
|
|
468
|
+
const pid = Number.parseInt(raw, 10);
|
|
469
|
+
return Number.isNaN(pid) ? null : pid;
|
|
470
|
+
} catch {
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
function writePid(pid) {
|
|
475
|
+
ensureDir();
|
|
476
|
+
fs2.writeFileSync(getPidPath(), String(pid), { mode: 384 });
|
|
477
|
+
}
|
|
478
|
+
function removePid() {
|
|
479
|
+
try {
|
|
480
|
+
fs2.unlinkSync(getPidPath());
|
|
481
|
+
} catch {
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
function isProcessAlive(pid) {
|
|
485
|
+
try {
|
|
486
|
+
process.kill(pid, 0);
|
|
487
|
+
return true;
|
|
488
|
+
} catch {
|
|
489
|
+
return false;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
function getRunningDaemonPid() {
|
|
493
|
+
const pid = readPid();
|
|
494
|
+
if (pid === null) return null;
|
|
495
|
+
if (isProcessAlive(pid)) return pid;
|
|
496
|
+
removePid();
|
|
497
|
+
removeStatus();
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
function writeStatus(status) {
|
|
501
|
+
ensureDir();
|
|
502
|
+
fs2.writeFileSync(getStatusPath(), JSON.stringify(status, null, 2), { mode: 384 });
|
|
503
|
+
}
|
|
504
|
+
function readStatus() {
|
|
505
|
+
try {
|
|
506
|
+
return JSON.parse(fs2.readFileSync(getStatusPath(), "utf8"));
|
|
507
|
+
} catch {
|
|
508
|
+
return null;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
function removeStatus() {
|
|
512
|
+
try {
|
|
513
|
+
fs2.unlinkSync(getStatusPath());
|
|
514
|
+
} catch {
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
function getLogPath() {
|
|
518
|
+
return getLogFilePath();
|
|
519
|
+
}
|
|
520
|
+
function rotateLogIfNeeded() {
|
|
521
|
+
try {
|
|
522
|
+
const stat2 = fs2.statSync(getLogFilePath());
|
|
523
|
+
if (stat2.size > MAX_LOG_SIZE) {
|
|
524
|
+
const rotated = getLogFilePath() + ".1";
|
|
525
|
+
try {
|
|
526
|
+
fs2.unlinkSync(rotated);
|
|
527
|
+
} catch {
|
|
528
|
+
}
|
|
529
|
+
fs2.renameSync(getLogFilePath(), rotated);
|
|
530
|
+
}
|
|
531
|
+
} catch {
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
function appendLog(line) {
|
|
535
|
+
ensureDir();
|
|
536
|
+
rotateLogIfNeeded();
|
|
537
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
538
|
+
fs2.appendFileSync(getLogFilePath(), `[${ts}] ${line}
|
|
539
|
+
`);
|
|
540
|
+
}
|
|
541
|
+
function spawnDaemon(args) {
|
|
542
|
+
ensureDir();
|
|
543
|
+
const logFd = fs2.openSync(getLogFilePath(), "a");
|
|
544
|
+
const child = spawn(process.execPath, [...process.execArgv, ...args, "--daemon-child"], {
|
|
545
|
+
detached: true,
|
|
546
|
+
env: { ...process.env, LOBEHUB_DAEMON: "1" },
|
|
547
|
+
stdio: ["ignore", logFd, logFd]
|
|
548
|
+
});
|
|
549
|
+
child.unref();
|
|
550
|
+
const pid = child.pid;
|
|
551
|
+
writePid(pid);
|
|
552
|
+
fs2.closeSync(logFd);
|
|
553
|
+
return pid;
|
|
554
|
+
}
|
|
555
|
+
function stopDaemon() {
|
|
556
|
+
const pid = getRunningDaemonPid();
|
|
557
|
+
if (pid === null) return false;
|
|
558
|
+
try {
|
|
559
|
+
process.kill(pid, "SIGTERM");
|
|
560
|
+
} catch {
|
|
561
|
+
}
|
|
562
|
+
removePid();
|
|
563
|
+
removeStatus();
|
|
564
|
+
return true;
|
|
565
|
+
}
|
|
566
|
+
|
|
443
567
|
// src/tools/file.ts
|
|
444
568
|
import { mkdir, readdir, readFile, stat, writeFile } from "fs/promises";
|
|
445
|
-
import
|
|
569
|
+
import path3 from "path";
|
|
446
570
|
import { createPatch } from "diff";
|
|
447
571
|
import fg from "fast-glob";
|
|
448
572
|
async function readLocalFile({ path: filePath, loc, fullContent }) {
|
|
@@ -472,8 +596,8 @@ async function readLocalFile({ path: filePath, loc, fullContent }) {
|
|
|
472
596
|
charCount: selectedContent.length,
|
|
473
597
|
content: selectedContent,
|
|
474
598
|
createdTime: fileStat.birthtime,
|
|
475
|
-
fileType:
|
|
476
|
-
filename:
|
|
599
|
+
fileType: path3.extname(filePath).toLowerCase().replace(".", "") || "unknown",
|
|
600
|
+
filename: path3.basename(filePath),
|
|
477
601
|
lineCount,
|
|
478
602
|
loc: actualLoc,
|
|
479
603
|
modifiedTime: fileStat.mtime,
|
|
@@ -486,8 +610,8 @@ async function readLocalFile({ path: filePath, loc, fullContent }) {
|
|
|
486
610
|
charCount: 0,
|
|
487
611
|
content: `Error accessing or processing file: ${errorMessage}`,
|
|
488
612
|
createdTime: /* @__PURE__ */ new Date(),
|
|
489
|
-
fileType:
|
|
490
|
-
filename:
|
|
613
|
+
fileType: path3.extname(filePath).toLowerCase().replace(".", "") || "unknown",
|
|
614
|
+
filename: path3.basename(filePath),
|
|
491
615
|
lineCount: 0,
|
|
492
616
|
loc: [0, 0],
|
|
493
617
|
modifiedTime: /* @__PURE__ */ new Date(),
|
|
@@ -500,7 +624,7 @@ async function writeLocalFile({ path: filePath, content }) {
|
|
|
500
624
|
if (!filePath) return { error: "Path cannot be empty", success: false };
|
|
501
625
|
if (content === void 0) return { error: "Content cannot be empty", success: false };
|
|
502
626
|
try {
|
|
503
|
-
const dirname =
|
|
627
|
+
const dirname = path3.dirname(filePath);
|
|
504
628
|
await mkdir(dirname, { recursive: true });
|
|
505
629
|
await writeFile(filePath, content, "utf8");
|
|
506
630
|
log.debug(`File written: ${filePath} (${content.length} chars)`);
|
|
@@ -565,7 +689,7 @@ async function listLocalFiles({
|
|
|
565
689
|
const entries = await readdir(dirPath);
|
|
566
690
|
const results = [];
|
|
567
691
|
for (const entry of entries) {
|
|
568
|
-
const fullPath =
|
|
692
|
+
const fullPath = path3.join(dirPath, entry);
|
|
569
693
|
try {
|
|
570
694
|
const stats = await stat(fullPath);
|
|
571
695
|
const isDirectory = stats.isDirectory();
|
|
@@ -577,7 +701,7 @@ async function listLocalFiles({
|
|
|
577
701
|
name: entry,
|
|
578
702
|
path: fullPath,
|
|
579
703
|
size: stats.size,
|
|
580
|
-
type: isDirectory ? "directory" :
|
|
704
|
+
type: isDirectory ? "directory" : path3.extname(entry).toLowerCase().replace(".", "")
|
|
581
705
|
});
|
|
582
706
|
} catch {
|
|
583
707
|
}
|
|
@@ -627,12 +751,12 @@ async function globLocalFiles({ pattern, cwd }) {
|
|
|
627
751
|
}
|
|
628
752
|
}
|
|
629
753
|
async function grepContent({ pattern, cwd, filePattern }) {
|
|
630
|
-
const { spawn:
|
|
754
|
+
const { spawn: spawn3 } = await import("child_process");
|
|
631
755
|
return new Promise((resolve) => {
|
|
632
756
|
const args = ["--json", "-n"];
|
|
633
757
|
if (filePattern) args.push("--glob", filePattern);
|
|
634
758
|
args.push(pattern);
|
|
635
|
-
const child =
|
|
759
|
+
const child = spawn3("rg", args, { cwd: cwd || process.cwd() });
|
|
636
760
|
let stdout = "";
|
|
637
761
|
child.stdout?.on("data", (data) => {
|
|
638
762
|
stdout += data.toString();
|
|
@@ -677,7 +801,7 @@ async function searchLocalFiles({
|
|
|
677
801
|
dot: false,
|
|
678
802
|
ignore: ["**/node_modules/**", "**/.git/**"]
|
|
679
803
|
});
|
|
680
|
-
let results = files.map((f) => ({ name:
|
|
804
|
+
let results = files.map((f) => ({ name: path3.basename(f), path: path3.join(cwd, f) }));
|
|
681
805
|
if (contentContains) {
|
|
682
806
|
const filtered = [];
|
|
683
807
|
for (const file of results) {
|
|
@@ -699,7 +823,7 @@ async function searchLocalFiles({
|
|
|
699
823
|
}
|
|
700
824
|
|
|
701
825
|
// src/tools/shell.ts
|
|
702
|
-
import { spawn } from "child_process";
|
|
826
|
+
import { spawn as spawn2 } from "child_process";
|
|
703
827
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
704
828
|
var MAX_OUTPUT_LENGTH = 8e4;
|
|
705
829
|
var ANSI_REGEX = (
|
|
@@ -735,7 +859,7 @@ async function runCommand({
|
|
|
735
859
|
try {
|
|
736
860
|
if (run_in_background) {
|
|
737
861
|
const shellId = randomUUID2();
|
|
738
|
-
const childProcess =
|
|
862
|
+
const childProcess = spawn2(shellConfig.cmd, shellConfig.args, {
|
|
739
863
|
env: process.env,
|
|
740
864
|
shell: false
|
|
741
865
|
});
|
|
@@ -760,7 +884,7 @@ async function runCommand({
|
|
|
760
884
|
return { shell_id: shellId, success: true };
|
|
761
885
|
} else {
|
|
762
886
|
return new Promise((resolve) => {
|
|
763
|
-
const childProcess =
|
|
887
|
+
const childProcess = spawn2(shellConfig.cmd, shellConfig.args, {
|
|
764
888
|
env: process.env,
|
|
765
889
|
shell: false
|
|
766
890
|
});
|
|
@@ -892,95 +1016,235 @@ async function executeToolCall(apiName, argsStr) {
|
|
|
892
1016
|
|
|
893
1017
|
// src/commands/connect.ts
|
|
894
1018
|
function registerConnectCommand(program2) {
|
|
895
|
-
program2.command("connect").description("Connect to the device gateway and listen for tool calls").option("--token <jwt>", "JWT access token").option("--
|
|
1019
|
+
const connectCmd = program2.command("connect").description("Connect to the device gateway and listen for tool calls").option("--token <jwt>", "JWT access token").option("--gateway <url>", "Gateway URL", "https://device-gateway.lobehub.com").option("--device-id <id>", "Device ID (auto-generated if not provided)").option("-v, --verbose", "Enable verbose logging").option("-d, --daemon", "Run as a background daemon process").option("--daemon-child", "Internal: runs as the daemon child process").action(async (options) => {
|
|
896
1020
|
if (options.verbose) setVerbose(true);
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
1021
|
+
if (options.daemon) {
|
|
1022
|
+
return handleDaemonStart(options);
|
|
1023
|
+
}
|
|
1024
|
+
const isDaemonChild = options.daemonChild || process.env.LOBEHUB_DAEMON === "1";
|
|
1025
|
+
await runConnect(options, isDaemonChild);
|
|
1026
|
+
});
|
|
1027
|
+
connectCmd.command("stop").description("Stop the background daemon process").action(() => {
|
|
1028
|
+
const stopped = stopDaemon();
|
|
1029
|
+
if (stopped) {
|
|
1030
|
+
log.info("Daemon stopped.");
|
|
1031
|
+
} else {
|
|
1032
|
+
log.warn("No daemon is running.");
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
1035
|
+
connectCmd.command("status").description("Show background daemon status").action(() => {
|
|
1036
|
+
const pid = getRunningDaemonPid();
|
|
1037
|
+
if (pid === null) {
|
|
1038
|
+
log.info("No daemon is running.");
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
const status = readStatus();
|
|
1042
|
+
log.info("\u2500\u2500\u2500 Daemon Status \u2500\u2500\u2500");
|
|
1043
|
+
log.info(` PID : ${pid}`);
|
|
1044
|
+
if (status) {
|
|
1045
|
+
log.info(` Started at : ${status.startedAt}`);
|
|
1046
|
+
log.info(` Connection : ${status.connectionStatus}`);
|
|
1047
|
+
log.info(` Gateway : ${status.gatewayUrl}`);
|
|
1048
|
+
const uptime = formatUptime(new Date(status.startedAt));
|
|
1049
|
+
log.info(` Uptime : ${uptime}`);
|
|
1050
|
+
}
|
|
1051
|
+
log.info("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1052
|
+
});
|
|
1053
|
+
connectCmd.command("logs").description("Tail the daemon log file").option("-n, --lines <count>", "Number of lines to show", "50").option("-f, --follow", "Follow log output").action(async (opts) => {
|
|
1054
|
+
const logPath = getLogPath();
|
|
1055
|
+
if (!fs3.existsSync(logPath)) {
|
|
1056
|
+
log.warn("No log file found. Start the daemon first.");
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
const lines = opts.lines || "50";
|
|
1060
|
+
const args = [`-n`, lines];
|
|
1061
|
+
if (opts.follow) args.push("-f");
|
|
1062
|
+
try {
|
|
1063
|
+
const { execFileSync } = await import("child_process");
|
|
1064
|
+
execFileSync("tail", [...args, logPath], { stdio: "inherit" });
|
|
1065
|
+
} catch {
|
|
1066
|
+
}
|
|
1067
|
+
});
|
|
1068
|
+
connectCmd.command("restart").description("Restart the background daemon process").option("--token <jwt>", "JWT access token").option("--gateway <url>", "Gateway URL", "https://device-gateway.lobehub.com").option("--device-id <id>", "Device ID").option("-v, --verbose", "Enable verbose logging").action((options) => {
|
|
1069
|
+
const wasStopped = stopDaemon();
|
|
1070
|
+
if (wasStopped) {
|
|
1071
|
+
log.info("Stopped existing daemon.");
|
|
1072
|
+
}
|
|
1073
|
+
handleDaemonStart({ ...options, daemon: true });
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
function handleDaemonStart(options) {
|
|
1077
|
+
const existingPid = getRunningDaemonPid();
|
|
1078
|
+
if (existingPid !== null) {
|
|
1079
|
+
log.error(`Daemon is already running (PID ${existingPid}).`);
|
|
1080
|
+
log.error("Use 'lh connect stop' to stop it, or 'lh connect restart' to restart.");
|
|
1081
|
+
process.exit(1);
|
|
1082
|
+
}
|
|
1083
|
+
const args = buildDaemonArgs(options);
|
|
1084
|
+
const pid = spawnDaemon(args);
|
|
1085
|
+
log.info(`Daemon started (PID ${pid}).`);
|
|
1086
|
+
log.info(` Logs: ${getLogPath()}`);
|
|
1087
|
+
log.info(" Run 'lh connect status' to check connection.");
|
|
1088
|
+
log.info(" Run 'lh connect stop' to stop.");
|
|
1089
|
+
}
|
|
1090
|
+
function buildDaemonArgs(options) {
|
|
1091
|
+
const script = process.argv[1];
|
|
1092
|
+
const args = [script, "connect"];
|
|
1093
|
+
if (options.token) args.push("--token", options.token);
|
|
1094
|
+
if (options.gateway) args.push("--gateway", options.gateway);
|
|
1095
|
+
if (options.deviceId) args.push("--device-id", options.deviceId);
|
|
1096
|
+
if (options.verbose) args.push("--verbose");
|
|
1097
|
+
return args;
|
|
1098
|
+
}
|
|
1099
|
+
async function runConnect(options, isDaemonChild) {
|
|
1100
|
+
const auth = await resolveToken(options);
|
|
1101
|
+
const gatewayUrl = options.gateway || "https://device-gateway.lobehub.com";
|
|
1102
|
+
const client = new GatewayClient({
|
|
1103
|
+
deviceId: options.deviceId,
|
|
1104
|
+
gatewayUrl,
|
|
1105
|
+
logger: isDaemonChild ? createDaemonLogger() : log,
|
|
1106
|
+
token: auth.token,
|
|
1107
|
+
userId: auth.userId
|
|
1108
|
+
});
|
|
1109
|
+
const info = (msg) => {
|
|
1110
|
+
if (isDaemonChild) appendLog(msg);
|
|
1111
|
+
else log.info(msg);
|
|
1112
|
+
};
|
|
1113
|
+
const error = (msg) => {
|
|
1114
|
+
if (isDaemonChild) appendLog(`[ERROR] ${msg}`);
|
|
1115
|
+
else log.error(msg);
|
|
1116
|
+
};
|
|
1117
|
+
info("\u2500\u2500\u2500 LobeHub CLI \u2500\u2500\u2500");
|
|
1118
|
+
info(` Device ID : ${client.currentDeviceId}`);
|
|
1119
|
+
info(` Hostname : ${os4.hostname()}`);
|
|
1120
|
+
info(` Platform : ${process.platform}`);
|
|
1121
|
+
info(` Gateway : ${gatewayUrl}`);
|
|
1122
|
+
info(` Auth : jwt`);
|
|
1123
|
+
info(` Mode : ${isDaemonChild ? "daemon" : "foreground"}`);
|
|
1124
|
+
info("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1125
|
+
const updateStatus = (connectionStatus) => {
|
|
1126
|
+
if (isDaemonChild) {
|
|
1127
|
+
writeStatus({
|
|
1128
|
+
connectionStatus,
|
|
1129
|
+
gatewayUrl,
|
|
1130
|
+
pid: process.pid,
|
|
1131
|
+
startedAt: startedAt.toISOString()
|
|
918
1132
|
});
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
1135
|
+
const startedAt = /* @__PURE__ */ new Date();
|
|
1136
|
+
updateStatus("connecting");
|
|
1137
|
+
client.on("system_info_request", (request) => {
|
|
1138
|
+
info(`Received system_info_request: requestId=${request.requestId}`);
|
|
1139
|
+
const systemInfo = collectSystemInfo();
|
|
1140
|
+
client.sendSystemInfoResponse({
|
|
1141
|
+
requestId: request.requestId,
|
|
1142
|
+
result: { success: true, systemInfo }
|
|
919
1143
|
});
|
|
920
|
-
|
|
921
|
-
|
|
1144
|
+
});
|
|
1145
|
+
client.on("tool_call_request", async (request) => {
|
|
1146
|
+
const { requestId, toolCall } = request;
|
|
1147
|
+
if (isDaemonChild) {
|
|
1148
|
+
appendLog(`[TOOL] ${toolCall.apiName} (${requestId})`);
|
|
1149
|
+
} else {
|
|
922
1150
|
log.toolCall(toolCall.apiName, requestId, toolCall.arguments);
|
|
923
|
-
|
|
1151
|
+
}
|
|
1152
|
+
const result = await executeToolCall(toolCall.apiName, toolCall.arguments);
|
|
1153
|
+
if (isDaemonChild) {
|
|
1154
|
+
appendLog(`[RESULT] ${result.success ? "OK" : "FAIL"} (${requestId})`);
|
|
1155
|
+
} else {
|
|
924
1156
|
log.toolResult(requestId, result.success, result.content);
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
});
|
|
933
|
-
});
|
|
934
|
-
client.on("auth_failed", (reason) => {
|
|
935
|
-
log.error(`Authentication failed: ${reason}`);
|
|
936
|
-
log.error("Run 'lh login' to re-authenticate.");
|
|
937
|
-
cleanup();
|
|
938
|
-
process.exit(1);
|
|
939
|
-
});
|
|
940
|
-
client.on("auth_expired", async () => {
|
|
941
|
-
log.warn("Authentication expired. Attempting to refresh...");
|
|
942
|
-
const refreshed = await resolveToken({});
|
|
943
|
-
if (refreshed) {
|
|
944
|
-
log.info("Token refreshed. Please reconnect.");
|
|
945
|
-
} else {
|
|
946
|
-
log.error("Could not refresh token. Run 'lh login' to re-authenticate.");
|
|
1157
|
+
}
|
|
1158
|
+
client.sendToolCallResponse({
|
|
1159
|
+
requestId,
|
|
1160
|
+
result: {
|
|
1161
|
+
content: result.content,
|
|
1162
|
+
error: result.error,
|
|
1163
|
+
success: result.success
|
|
947
1164
|
}
|
|
948
|
-
cleanup();
|
|
949
|
-
process.exit(1);
|
|
950
1165
|
});
|
|
951
|
-
client.on("error", (error) => {
|
|
952
|
-
log.error(`Connection error: ${error.message}`);
|
|
953
|
-
});
|
|
954
|
-
const cleanup = () => {
|
|
955
|
-
log.info("Shutting down...");
|
|
956
|
-
cleanupAllProcesses();
|
|
957
|
-
client.disconnect();
|
|
958
|
-
};
|
|
959
|
-
process.on("SIGINT", () => {
|
|
960
|
-
cleanup();
|
|
961
|
-
process.exit(0);
|
|
962
|
-
});
|
|
963
|
-
process.on("SIGTERM", () => {
|
|
964
|
-
cleanup();
|
|
965
|
-
process.exit(0);
|
|
966
|
-
});
|
|
967
|
-
await client.connect();
|
|
968
1166
|
});
|
|
1167
|
+
client.on("connected", () => {
|
|
1168
|
+
updateStatus("connected");
|
|
1169
|
+
});
|
|
1170
|
+
client.on("disconnected", () => {
|
|
1171
|
+
updateStatus("disconnected");
|
|
1172
|
+
});
|
|
1173
|
+
client.on("reconnecting", () => {
|
|
1174
|
+
updateStatus("reconnecting");
|
|
1175
|
+
});
|
|
1176
|
+
client.on("auth_failed", (reason) => {
|
|
1177
|
+
error(`Authentication failed: ${reason}`);
|
|
1178
|
+
error("Run 'lh login' to re-authenticate.");
|
|
1179
|
+
cleanup();
|
|
1180
|
+
process.exit(1);
|
|
1181
|
+
});
|
|
1182
|
+
client.on("auth_expired", async () => {
|
|
1183
|
+
error("Authentication expired. Attempting to refresh...");
|
|
1184
|
+
const refreshed = await resolveToken({});
|
|
1185
|
+
if (refreshed) {
|
|
1186
|
+
info("Token refreshed. Please reconnect.");
|
|
1187
|
+
} else {
|
|
1188
|
+
error("Could not refresh token. Run 'lh login' to re-authenticate.");
|
|
1189
|
+
}
|
|
1190
|
+
cleanup();
|
|
1191
|
+
process.exit(1);
|
|
1192
|
+
});
|
|
1193
|
+
client.on("error", (err) => {
|
|
1194
|
+
error(`Connection error: ${err.message}`);
|
|
1195
|
+
});
|
|
1196
|
+
const cleanup = () => {
|
|
1197
|
+
info("Shutting down...");
|
|
1198
|
+
cleanupAllProcesses();
|
|
1199
|
+
client.disconnect();
|
|
1200
|
+
if (isDaemonChild) {
|
|
1201
|
+
removeStatus();
|
|
1202
|
+
removePid();
|
|
1203
|
+
}
|
|
1204
|
+
};
|
|
1205
|
+
process.on("SIGINT", () => {
|
|
1206
|
+
cleanup();
|
|
1207
|
+
process.exit(0);
|
|
1208
|
+
});
|
|
1209
|
+
process.on("SIGTERM", () => {
|
|
1210
|
+
cleanup();
|
|
1211
|
+
process.exit(0);
|
|
1212
|
+
});
|
|
1213
|
+
await client.connect();
|
|
1214
|
+
}
|
|
1215
|
+
function createDaemonLogger() {
|
|
1216
|
+
return {
|
|
1217
|
+
debug: (msg) => appendLog(`[DEBUG] ${msg}`),
|
|
1218
|
+
error: (msg) => appendLog(`[ERROR] ${msg}`),
|
|
1219
|
+
info: (msg) => appendLog(`[INFO] ${msg}`),
|
|
1220
|
+
warn: (msg) => appendLog(`[WARN] ${msg}`)
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
function formatUptime(startedAt) {
|
|
1224
|
+
const diff = Date.now() - startedAt.getTime();
|
|
1225
|
+
const seconds = Math.floor(diff / 1e3);
|
|
1226
|
+
const minutes = Math.floor(seconds / 60);
|
|
1227
|
+
const hours = Math.floor(minutes / 60);
|
|
1228
|
+
const days = Math.floor(hours / 24);
|
|
1229
|
+
if (days > 0) return `${days}d ${hours % 24}h ${minutes % 60}m`;
|
|
1230
|
+
if (hours > 0) return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
|
|
1231
|
+
if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
|
|
1232
|
+
return `${seconds}s`;
|
|
969
1233
|
}
|
|
970
1234
|
function collectSystemInfo() {
|
|
971
|
-
const home =
|
|
1235
|
+
const home = os4.homedir();
|
|
972
1236
|
const platform = process.platform;
|
|
973
1237
|
const videosDir = platform === "linux" ? "Videos" : "Movies";
|
|
974
1238
|
return {
|
|
975
|
-
arch:
|
|
976
|
-
desktopPath:
|
|
977
|
-
documentsPath:
|
|
978
|
-
downloadsPath:
|
|
1239
|
+
arch: os4.arch(),
|
|
1240
|
+
desktopPath: path4.join(home, "Desktop"),
|
|
1241
|
+
documentsPath: path4.join(home, "Documents"),
|
|
1242
|
+
downloadsPath: path4.join(home, "Downloads"),
|
|
979
1243
|
homePath: home,
|
|
980
|
-
musicPath:
|
|
981
|
-
picturesPath:
|
|
982
|
-
userDataPath:
|
|
983
|
-
videosPath:
|
|
1244
|
+
musicPath: path4.join(home, "Music"),
|
|
1245
|
+
picturesPath: path4.join(home, "Pictures"),
|
|
1246
|
+
userDataPath: path4.join(home, ".lobehub"),
|
|
1247
|
+
videosPath: path4.join(home, videosDir),
|
|
984
1248
|
workingDirectory: process.cwd()
|
|
985
1249
|
};
|
|
986
1250
|
}
|