itwillsync 1.6.2 → 1.7.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/hub/daemon.js +391 -25
- package/dist/hub/daemon.js.map +1 -1
- package/dist/hub/dashboard/assets/index-CUdWjWFv.css +1 -0
- package/dist/hub/dashboard/assets/index-hWUVy-IH.js +2 -0
- package/dist/hub/dashboard/index.html +61 -4
- package/package.json +1 -1
- package/dist/hub/dashboard/assets/index-C0aiYBkq.js +0 -2
- package/dist/hub/dashboard/assets/index-D6z7Ixhf.css +0 -1
package/dist/hub/daemon.js
CHANGED
|
@@ -3649,11 +3649,11 @@ var require_websocket_server = __commonJS({
|
|
|
3649
3649
|
});
|
|
3650
3650
|
|
|
3651
3651
|
// src/daemon.ts
|
|
3652
|
-
import { writeFileSync as
|
|
3653
|
-
import { homedir as
|
|
3654
|
-
import { join as
|
|
3652
|
+
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, unlinkSync as unlinkSync2, existsSync as existsSync4, readdirSync, statSync } from "fs";
|
|
3653
|
+
import { homedir as homedir5 } from "os";
|
|
3654
|
+
import { join as join5, dirname as dirname3 } from "path";
|
|
3655
3655
|
import { fileURLToPath } from "url";
|
|
3656
|
-
import { spawn } from "child_process";
|
|
3656
|
+
import { spawn as spawn2 } from "child_process";
|
|
3657
3657
|
|
|
3658
3658
|
// src/auth.ts
|
|
3659
3659
|
import { randomBytes, timingSafeEqual } from "crypto";
|
|
@@ -4127,8 +4127,8 @@ function createInternalApi(options) {
|
|
|
4127
4127
|
}
|
|
4128
4128
|
let memoryKB = 0;
|
|
4129
4129
|
try {
|
|
4130
|
-
const { execFileSync } = await import("child_process");
|
|
4131
|
-
const output =
|
|
4130
|
+
const { execFileSync: execFileSync2 } = await import("child_process");
|
|
4131
|
+
const output = execFileSync2("ps", ["-o", "rss=", "-p", String(session.pid)], {
|
|
4132
4132
|
encoding: "utf-8",
|
|
4133
4133
|
timeout: 2e3
|
|
4134
4134
|
});
|
|
@@ -4201,7 +4201,7 @@ var MIME_TYPES = {
|
|
|
4201
4201
|
var COMPRESSIBLE = /* @__PURE__ */ new Set([".html", ".js", ".css", ".json", ".svg"]);
|
|
4202
4202
|
var PING_INTERVAL_MS = 3e4;
|
|
4203
4203
|
function createDashboardServer(options) {
|
|
4204
|
-
const { registry, masterToken, dashboardPath, host, port, previewCollector, toolHistory, onCreateSession } = options;
|
|
4204
|
+
const { registry, masterToken, dashboardPath, host, port, previewCollector, toolHistory, sleepPrevention, onCreateSession } = options;
|
|
4205
4205
|
const homeDir = homedir3();
|
|
4206
4206
|
const clients = /* @__PURE__ */ new Set();
|
|
4207
4207
|
const aliveMap = /* @__PURE__ */ new WeakMap();
|
|
@@ -4358,6 +4358,9 @@ function createDashboardServer(options) {
|
|
|
4358
4358
|
aliveMap.set(ws, true);
|
|
4359
4359
|
const sessions = registry.getAll();
|
|
4360
4360
|
ws.send(JSON.stringify({ type: "sessions", sessions }));
|
|
4361
|
+
if (sleepPrevention) {
|
|
4362
|
+
ws.send(JSON.stringify({ type: "sleep-state", state: sleepPrevention.getState() }));
|
|
4363
|
+
}
|
|
4361
4364
|
if (previewCollector) {
|
|
4362
4365
|
const previews = previewCollector.getAllPreviews();
|
|
4363
4366
|
for (const [sessionId, lines] of previews) {
|
|
@@ -4423,6 +4426,30 @@ function createDashboardServer(options) {
|
|
|
4423
4426
|
}
|
|
4424
4427
|
break;
|
|
4425
4428
|
}
|
|
4429
|
+
case "enable-sleep-prevention": {
|
|
4430
|
+
if (!sleepPrevention) {
|
|
4431
|
+
ws.send(JSON.stringify({ type: "sleep-error", error: "Sleep prevention not available" }));
|
|
4432
|
+
break;
|
|
4433
|
+
}
|
|
4434
|
+
const password = typeof msg.password === "string" ? msg.password : "";
|
|
4435
|
+
if (!password) {
|
|
4436
|
+
ws.send(JSON.stringify({ type: "sleep-error", error: "Password is required" }));
|
|
4437
|
+
break;
|
|
4438
|
+
}
|
|
4439
|
+
const enableResult = await sleepPrevention.enable(password);
|
|
4440
|
+
if (enableResult.success) {
|
|
4441
|
+
broadcast({ type: "sleep-state", state: sleepPrevention.getState() });
|
|
4442
|
+
} else {
|
|
4443
|
+
ws.send(JSON.stringify({ type: "sleep-error", error: enableResult.error }));
|
|
4444
|
+
}
|
|
4445
|
+
break;
|
|
4446
|
+
}
|
|
4447
|
+
case "disable-sleep-prevention": {
|
|
4448
|
+
if (!sleepPrevention) break;
|
|
4449
|
+
await sleepPrevention.disable(true);
|
|
4450
|
+
broadcast({ type: "sleep-state", state: sleepPrevention.getState() });
|
|
4451
|
+
break;
|
|
4452
|
+
}
|
|
4426
4453
|
case "get-metadata": {
|
|
4427
4454
|
const session = registry.getById(msg.sessionId);
|
|
4428
4455
|
if (!session) {
|
|
@@ -4431,8 +4458,8 @@ function createDashboardServer(options) {
|
|
|
4431
4458
|
}
|
|
4432
4459
|
let memoryKB = 0;
|
|
4433
4460
|
try {
|
|
4434
|
-
const { execFileSync } = await import("child_process");
|
|
4435
|
-
const output =
|
|
4461
|
+
const { execFileSync: execFileSync2 } = await import("child_process");
|
|
4462
|
+
const output = execFileSync2("ps", ["-o", "rss=", "-p", String(session.pid)], {
|
|
4436
4463
|
encoding: "utf-8",
|
|
4437
4464
|
timeout: 2e3
|
|
4438
4465
|
});
|
|
@@ -4687,24 +4714,360 @@ var PreviewCollector = class extends EventEmitter2 {
|
|
|
4687
4714
|
}
|
|
4688
4715
|
};
|
|
4689
4716
|
|
|
4717
|
+
// src/sleep-prevention.ts
|
|
4718
|
+
import { spawn, execFileSync } from "child_process";
|
|
4719
|
+
import { writeFileSync as writeFileSync3, readFileSync as readFileSync3, unlinkSync, existsSync as existsSync3 } from "fs";
|
|
4720
|
+
import { join as join4 } from "path";
|
|
4721
|
+
import { homedir as homedir4 } from "os";
|
|
4722
|
+
var SUPPORTED_PLATFORMS = /* @__PURE__ */ new Set(["darwin", "win32", "linux"]);
|
|
4723
|
+
var COMMAND_TIMEOUT_MS = 1e4;
|
|
4724
|
+
var SYNC_TIMEOUT_MS = 5e3;
|
|
4725
|
+
function getHubDir() {
|
|
4726
|
+
return process.env.ITWILLSYNC_CONFIG_DIR || join4(homedir4(), ".itwillsync");
|
|
4727
|
+
}
|
|
4728
|
+
var SleepPrevention = class {
|
|
4729
|
+
enabled = false;
|
|
4730
|
+
enabledAt = null;
|
|
4731
|
+
error = null;
|
|
4732
|
+
busy = false;
|
|
4733
|
+
flagFilePath;
|
|
4734
|
+
platform;
|
|
4735
|
+
constructor() {
|
|
4736
|
+
this.platform = process.platform;
|
|
4737
|
+
this.flagFilePath = join4(getHubDir(), "sleep-prevention.json");
|
|
4738
|
+
this.checkOrphanedFlag();
|
|
4739
|
+
}
|
|
4740
|
+
getState() {
|
|
4741
|
+
return {
|
|
4742
|
+
enabled: this.enabled,
|
|
4743
|
+
platform: this.platform,
|
|
4744
|
+
enabledAt: this.enabledAt,
|
|
4745
|
+
error: this.error,
|
|
4746
|
+
supported: SUPPORTED_PLATFORMS.has(this.platform)
|
|
4747
|
+
};
|
|
4748
|
+
}
|
|
4749
|
+
async enable(password) {
|
|
4750
|
+
if (this.busy) {
|
|
4751
|
+
return { success: false, error: "Operation in progress" };
|
|
4752
|
+
}
|
|
4753
|
+
if (!SUPPORTED_PLATFORMS.has(this.platform)) {
|
|
4754
|
+
return { success: false, error: `Sleep prevention is not supported on ${this.platform}` };
|
|
4755
|
+
}
|
|
4756
|
+
if (this.enabled) {
|
|
4757
|
+
return { success: true };
|
|
4758
|
+
}
|
|
4759
|
+
this.busy = true;
|
|
4760
|
+
try {
|
|
4761
|
+
let result;
|
|
4762
|
+
switch (this.platform) {
|
|
4763
|
+
case "darwin":
|
|
4764
|
+
result = await this.runSudo(["pmset", "-a", "disablesleep", "1"], password);
|
|
4765
|
+
break;
|
|
4766
|
+
case "win32":
|
|
4767
|
+
result = await this.enableWindows();
|
|
4768
|
+
break;
|
|
4769
|
+
case "linux":
|
|
4770
|
+
result = await this.enableLinux(password);
|
|
4771
|
+
break;
|
|
4772
|
+
default:
|
|
4773
|
+
result = { success: false, error: "Unsupported platform" };
|
|
4774
|
+
}
|
|
4775
|
+
if (result.success) {
|
|
4776
|
+
this.enabled = true;
|
|
4777
|
+
this.enabledAt = Date.now();
|
|
4778
|
+
this.error = null;
|
|
4779
|
+
this.writeFlagFile();
|
|
4780
|
+
} else {
|
|
4781
|
+
this.error = result.error || "Unknown error";
|
|
4782
|
+
}
|
|
4783
|
+
return result;
|
|
4784
|
+
} finally {
|
|
4785
|
+
this.busy = false;
|
|
4786
|
+
}
|
|
4787
|
+
}
|
|
4788
|
+
/**
|
|
4789
|
+
* Disable sleep prevention. When `force` is true (user-initiated), the flag
|
|
4790
|
+
* file is always deleted. When false (orphan revert), the flag file persists
|
|
4791
|
+
* if the system command fails so the next restart can retry.
|
|
4792
|
+
*/
|
|
4793
|
+
async disable(force = false) {
|
|
4794
|
+
if (this.busy) {
|
|
4795
|
+
return { success: false, error: "Operation in progress" };
|
|
4796
|
+
}
|
|
4797
|
+
if (!this.enabled) {
|
|
4798
|
+
return { success: true };
|
|
4799
|
+
}
|
|
4800
|
+
this.busy = true;
|
|
4801
|
+
try {
|
|
4802
|
+
let reverted = false;
|
|
4803
|
+
switch (this.platform) {
|
|
4804
|
+
case "darwin":
|
|
4805
|
+
reverted = (await this.runCommand("sudo", ["-n", "pmset", "-a", "disablesleep", "0"])).exitCode === 0;
|
|
4806
|
+
break;
|
|
4807
|
+
case "win32":
|
|
4808
|
+
await this.disableWindows();
|
|
4809
|
+
reverted = true;
|
|
4810
|
+
break;
|
|
4811
|
+
case "linux":
|
|
4812
|
+
reverted = await this.disableLinux();
|
|
4813
|
+
break;
|
|
4814
|
+
}
|
|
4815
|
+
this.enabled = false;
|
|
4816
|
+
this.enabledAt = null;
|
|
4817
|
+
this.error = null;
|
|
4818
|
+
if (reverted || force) {
|
|
4819
|
+
this.deleteFlagFile();
|
|
4820
|
+
}
|
|
4821
|
+
return { success: true };
|
|
4822
|
+
} finally {
|
|
4823
|
+
this.busy = false;
|
|
4824
|
+
}
|
|
4825
|
+
}
|
|
4826
|
+
/** Synchronous cleanup for SIGTERM/SIGINT handlers. Best-effort. */
|
|
4827
|
+
cleanupSync() {
|
|
4828
|
+
if (!this.enabled) return;
|
|
4829
|
+
try {
|
|
4830
|
+
switch (this.platform) {
|
|
4831
|
+
case "darwin":
|
|
4832
|
+
execFileSync("sudo", ["-n", "pmset", "-a", "disablesleep", "0"], {
|
|
4833
|
+
timeout: SYNC_TIMEOUT_MS,
|
|
4834
|
+
stdio: "ignore"
|
|
4835
|
+
});
|
|
4836
|
+
break;
|
|
4837
|
+
case "win32":
|
|
4838
|
+
execFileSync("powercfg", ["/setacvalueindex", "SCHEME_CURRENT", "SUB_BUTTONS", "LIDACTION", "1"], { timeout: SYNC_TIMEOUT_MS, stdio: "ignore" });
|
|
4839
|
+
execFileSync("powercfg", ["/setdcvalueindex", "SCHEME_CURRENT", "SUB_BUTTONS", "LIDACTION", "1"], { timeout: SYNC_TIMEOUT_MS, stdio: "ignore" });
|
|
4840
|
+
execFileSync("powercfg", ["/s", "SCHEME_CURRENT"], { timeout: SYNC_TIMEOUT_MS, stdio: "ignore" });
|
|
4841
|
+
break;
|
|
4842
|
+
case "linux":
|
|
4843
|
+
this.revertLogindConfSync();
|
|
4844
|
+
execFileSync("sudo", ["-n", "systemctl", "restart", "systemd-logind"], {
|
|
4845
|
+
timeout: SYNC_TIMEOUT_MS,
|
|
4846
|
+
stdio: "ignore"
|
|
4847
|
+
});
|
|
4848
|
+
break;
|
|
4849
|
+
}
|
|
4850
|
+
} catch {
|
|
4851
|
+
}
|
|
4852
|
+
this.enabled = false;
|
|
4853
|
+
this.enabledAt = null;
|
|
4854
|
+
this.deleteFlagFile();
|
|
4855
|
+
}
|
|
4856
|
+
// --- Windows ---
|
|
4857
|
+
async enableWindows() {
|
|
4858
|
+
const commands = [
|
|
4859
|
+
["powercfg", ["/setacvalueindex", "SCHEME_CURRENT", "SUB_BUTTONS", "LIDACTION", "0"]],
|
|
4860
|
+
["powercfg", ["/setdcvalueindex", "SCHEME_CURRENT", "SUB_BUTTONS", "LIDACTION", "0"]],
|
|
4861
|
+
["powercfg", ["/s", "SCHEME_CURRENT"]]
|
|
4862
|
+
];
|
|
4863
|
+
for (const [cmd, args] of commands) {
|
|
4864
|
+
const result = await this.runCommand(cmd, args);
|
|
4865
|
+
if (result.exitCode !== 0) {
|
|
4866
|
+
return { success: false, error: `powercfg failed: ${result.stderr || "requires admin privileges"}` };
|
|
4867
|
+
}
|
|
4868
|
+
}
|
|
4869
|
+
return { success: true };
|
|
4870
|
+
}
|
|
4871
|
+
async disableWindows() {
|
|
4872
|
+
await this.runCommand("powercfg", ["/setacvalueindex", "SCHEME_CURRENT", "SUB_BUTTONS", "LIDACTION", "1"]);
|
|
4873
|
+
await this.runCommand("powercfg", ["/setdcvalueindex", "SCHEME_CURRENT", "SUB_BUTTONS", "LIDACTION", "1"]);
|
|
4874
|
+
await this.runCommand("powercfg", ["/s", "SCHEME_CURRENT"]);
|
|
4875
|
+
}
|
|
4876
|
+
// --- Linux ---
|
|
4877
|
+
async enableLinux(password) {
|
|
4878
|
+
const confPath = "/etc/systemd/logind.conf";
|
|
4879
|
+
const backupPath = join4(getHubDir(), "logind.conf.backup");
|
|
4880
|
+
try {
|
|
4881
|
+
const readResult = await this.runSudo(["cat", confPath], password, { captureStdout: true });
|
|
4882
|
+
if (!readResult.success) {
|
|
4883
|
+
return { success: false, error: readResult.error || "Failed to read logind.conf" };
|
|
4884
|
+
}
|
|
4885
|
+
const original = readResult.stdout;
|
|
4886
|
+
writeFileSync3(backupPath, original, "utf-8");
|
|
4887
|
+
let modified = original;
|
|
4888
|
+
const settings = {
|
|
4889
|
+
HandleLidSwitch: "ignore",
|
|
4890
|
+
HandleLidSwitchExternalPower: "ignore",
|
|
4891
|
+
HandleLidSwitchDocked: "ignore"
|
|
4892
|
+
};
|
|
4893
|
+
for (const [key, value] of Object.entries(settings)) {
|
|
4894
|
+
const regex = new RegExp(`^#?\\s*${key}\\s*=.*$`, "m");
|
|
4895
|
+
if (regex.test(modified)) {
|
|
4896
|
+
modified = modified.replace(regex, `${key}=${value}`);
|
|
4897
|
+
} else {
|
|
4898
|
+
modified = modified.replace(/^\[Login\]/m, `[Login]
|
|
4899
|
+
${key}=${value}`);
|
|
4900
|
+
}
|
|
4901
|
+
}
|
|
4902
|
+
const writeResult = await this.runSudo(["tee", confPath], password, { stdin: modified });
|
|
4903
|
+
if (!writeResult.success) {
|
|
4904
|
+
return { success: false, error: "Failed to write logind.conf" };
|
|
4905
|
+
}
|
|
4906
|
+
const restartResult = await this.runSudo(["systemctl", "restart", "systemd-logind"], password);
|
|
4907
|
+
if (!restartResult.success) {
|
|
4908
|
+
try {
|
|
4909
|
+
const backup = readFileSync3(backupPath, "utf-8");
|
|
4910
|
+
await this.runSudo(["tee", confPath], password, { stdin: backup });
|
|
4911
|
+
await this.runSudo(["systemctl", "restart", "systemd-logind"], password);
|
|
4912
|
+
} catch {
|
|
4913
|
+
}
|
|
4914
|
+
return { success: false, error: "Failed to restart systemd-logind" };
|
|
4915
|
+
}
|
|
4916
|
+
return { success: true };
|
|
4917
|
+
} catch (err) {
|
|
4918
|
+
return { success: false, error: err.message };
|
|
4919
|
+
}
|
|
4920
|
+
}
|
|
4921
|
+
async disableLinux() {
|
|
4922
|
+
const backupPath = join4(getHubDir(), "logind.conf.backup");
|
|
4923
|
+
if (!existsSync3(backupPath)) return true;
|
|
4924
|
+
try {
|
|
4925
|
+
const backup = readFileSync3(backupPath, "utf-8");
|
|
4926
|
+
const teeResult = await this.runCommand("sudo", ["-n", "tee", "/etc/systemd/logind.conf"], { stdin: backup });
|
|
4927
|
+
const restartResult = await this.runCommand("sudo", ["-n", "systemctl", "restart", "systemd-logind"]);
|
|
4928
|
+
const ok = teeResult.exitCode === 0 && restartResult.exitCode === 0;
|
|
4929
|
+
if (ok) unlinkSync(backupPath);
|
|
4930
|
+
return ok;
|
|
4931
|
+
} catch {
|
|
4932
|
+
return false;
|
|
4933
|
+
}
|
|
4934
|
+
}
|
|
4935
|
+
revertLogindConfSync() {
|
|
4936
|
+
const backupPath = join4(getHubDir(), "logind.conf.backup");
|
|
4937
|
+
if (!existsSync3(backupPath)) return;
|
|
4938
|
+
try {
|
|
4939
|
+
const backup = readFileSync3(backupPath, "utf-8");
|
|
4940
|
+
execFileSync("sudo", ["-n", "tee", "/etc/systemd/logind.conf"], {
|
|
4941
|
+
input: backup,
|
|
4942
|
+
timeout: SYNC_TIMEOUT_MS,
|
|
4943
|
+
stdio: ["pipe", "ignore", "ignore"]
|
|
4944
|
+
});
|
|
4945
|
+
unlinkSync(backupPath);
|
|
4946
|
+
} catch {
|
|
4947
|
+
}
|
|
4948
|
+
}
|
|
4949
|
+
// --- Helpers ---
|
|
4950
|
+
/** Run a command via sudo -S (password on stdin). Optionally pipe extra stdin or capture stdout. */
|
|
4951
|
+
runSudo(args, password, opts) {
|
|
4952
|
+
return new Promise((resolve) => {
|
|
4953
|
+
const capture = opts?.captureStdout ?? false;
|
|
4954
|
+
const proc = spawn("sudo", ["-S", ...args], {
|
|
4955
|
+
stdio: ["pipe", capture ? "pipe" : "ignore", "pipe"]
|
|
4956
|
+
});
|
|
4957
|
+
let stdout = "";
|
|
4958
|
+
let stderr = "";
|
|
4959
|
+
if (capture) proc.stdout.on("data", (d) => {
|
|
4960
|
+
stdout += d.toString();
|
|
4961
|
+
});
|
|
4962
|
+
proc.stderr.on("data", (d) => {
|
|
4963
|
+
stderr += d.toString();
|
|
4964
|
+
});
|
|
4965
|
+
const timer = setTimeout(() => {
|
|
4966
|
+
proc.kill("SIGKILL");
|
|
4967
|
+
resolve({ success: false, error: "Command timed out" });
|
|
4968
|
+
}, COMMAND_TIMEOUT_MS);
|
|
4969
|
+
proc.on("close", (code) => {
|
|
4970
|
+
clearTimeout(timer);
|
|
4971
|
+
if (code === 0) {
|
|
4972
|
+
resolve({ success: true, ...capture && { stdout } });
|
|
4973
|
+
} else {
|
|
4974
|
+
const msg = stderr.toLowerCase().includes("incorrect password") ? "Incorrect password" : stderr.trim() || `Command failed with exit code ${code}`;
|
|
4975
|
+
resolve({ success: false, error: msg });
|
|
4976
|
+
}
|
|
4977
|
+
});
|
|
4978
|
+
proc.on("error", (err) => {
|
|
4979
|
+
clearTimeout(timer);
|
|
4980
|
+
resolve({ success: false, error: err.message });
|
|
4981
|
+
});
|
|
4982
|
+
proc.stdin.write(password + "\n");
|
|
4983
|
+
if (opts?.stdin) proc.stdin.write(opts.stdin);
|
|
4984
|
+
proc.stdin.end();
|
|
4985
|
+
});
|
|
4986
|
+
}
|
|
4987
|
+
/** Run a command without sudo password. Optionally pipe stdin. */
|
|
4988
|
+
runCommand(cmd, args, opts) {
|
|
4989
|
+
return new Promise((resolve) => {
|
|
4990
|
+
const hasStdin = opts?.stdin != null;
|
|
4991
|
+
const proc = spawn(cmd, args, {
|
|
4992
|
+
stdio: [hasStdin ? "pipe" : "ignore", "ignore", "pipe"]
|
|
4993
|
+
});
|
|
4994
|
+
let stderr = "";
|
|
4995
|
+
proc.stderr.on("data", (d) => {
|
|
4996
|
+
stderr += d.toString();
|
|
4997
|
+
});
|
|
4998
|
+
const timer = setTimeout(() => {
|
|
4999
|
+
proc.kill("SIGKILL");
|
|
5000
|
+
resolve({ exitCode: 1, stderr: "Command timed out" });
|
|
5001
|
+
}, COMMAND_TIMEOUT_MS);
|
|
5002
|
+
proc.on("close", (code) => {
|
|
5003
|
+
clearTimeout(timer);
|
|
5004
|
+
resolve({ exitCode: code ?? 1, stderr });
|
|
5005
|
+
});
|
|
5006
|
+
proc.on("error", (err) => {
|
|
5007
|
+
clearTimeout(timer);
|
|
5008
|
+
resolve({ exitCode: 1, stderr: err.message });
|
|
5009
|
+
});
|
|
5010
|
+
if (hasStdin) {
|
|
5011
|
+
proc.stdin.write(opts.stdin);
|
|
5012
|
+
proc.stdin.end();
|
|
5013
|
+
}
|
|
5014
|
+
});
|
|
5015
|
+
}
|
|
5016
|
+
writeFlagFile() {
|
|
5017
|
+
const data = {
|
|
5018
|
+
enabled: true,
|
|
5019
|
+
platform: this.platform,
|
|
5020
|
+
enabledAt: this.enabledAt || Date.now()
|
|
5021
|
+
};
|
|
5022
|
+
try {
|
|
5023
|
+
writeFileSync3(this.flagFilePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
5024
|
+
} catch {
|
|
5025
|
+
}
|
|
5026
|
+
}
|
|
5027
|
+
deleteFlagFile() {
|
|
5028
|
+
try {
|
|
5029
|
+
if (existsSync3(this.flagFilePath)) {
|
|
5030
|
+
unlinkSync(this.flagFilePath);
|
|
5031
|
+
}
|
|
5032
|
+
} catch {
|
|
5033
|
+
}
|
|
5034
|
+
}
|
|
5035
|
+
checkOrphanedFlag() {
|
|
5036
|
+
try {
|
|
5037
|
+
if (!existsSync3(this.flagFilePath)) return;
|
|
5038
|
+
const raw = readFileSync3(this.flagFilePath, "utf-8");
|
|
5039
|
+
const data = JSON.parse(raw);
|
|
5040
|
+
if (data.enabled) {
|
|
5041
|
+
console.log("Reverting orphaned sleep prevention setting...");
|
|
5042
|
+
this.enabled = true;
|
|
5043
|
+
this.enabledAt = data.enabledAt;
|
|
5044
|
+
this.disable().catch(() => {
|
|
5045
|
+
});
|
|
5046
|
+
}
|
|
5047
|
+
} catch {
|
|
5048
|
+
this.deleteFlagFile();
|
|
5049
|
+
}
|
|
5050
|
+
}
|
|
5051
|
+
};
|
|
5052
|
+
|
|
4690
5053
|
// src/daemon.ts
|
|
4691
5054
|
var HUB_EXTERNAL_PORT = 7962;
|
|
4692
5055
|
var HUB_INTERNAL_PORT = 7963;
|
|
4693
5056
|
var AUTO_SHUTDOWN_DELAY_MS = 3e4;
|
|
4694
|
-
function
|
|
4695
|
-
return process.env.ITWILLSYNC_CONFIG_DIR ||
|
|
5057
|
+
function getHubDir2() {
|
|
5058
|
+
return process.env.ITWILLSYNC_CONFIG_DIR || join5(homedir5(), ".itwillsync");
|
|
4696
5059
|
}
|
|
4697
5060
|
function getPidPath() {
|
|
4698
|
-
return
|
|
5061
|
+
return join5(getHubDir2(), "hub.pid");
|
|
4699
5062
|
}
|
|
4700
5063
|
function getHubConfigPath() {
|
|
4701
|
-
return
|
|
5064
|
+
return join5(getHubDir2(), "hub.json");
|
|
4702
5065
|
}
|
|
4703
5066
|
function isValidToolName(tool) {
|
|
4704
5067
|
return /^[a-zA-Z0-9._-]+$/.test(tool) && tool.length > 0 && tool.length <= 100;
|
|
4705
5068
|
}
|
|
4706
5069
|
function spawnSession(tool, cwd, cliEntryPath) {
|
|
4707
|
-
const child =
|
|
5070
|
+
const child = spawn2(process.execPath, [cliEntryPath, "--headless", "--", tool], {
|
|
4708
5071
|
cwd,
|
|
4709
5072
|
stdio: "ignore",
|
|
4710
5073
|
detached: true,
|
|
@@ -4714,11 +5077,11 @@ function spawnSession(tool, cwd, cliEntryPath) {
|
|
|
4714
5077
|
return child;
|
|
4715
5078
|
}
|
|
4716
5079
|
async function main() {
|
|
4717
|
-
const hubDir =
|
|
5080
|
+
const hubDir = getHubDir2();
|
|
4718
5081
|
mkdirSync3(hubDir, { recursive: true });
|
|
4719
5082
|
const masterToken = generateToken();
|
|
4720
5083
|
const startedAt = Date.now();
|
|
4721
|
-
|
|
5084
|
+
writeFileSync4(getPidPath(), String(process.pid), "utf-8");
|
|
4722
5085
|
const hubConfig = {
|
|
4723
5086
|
masterToken,
|
|
4724
5087
|
externalPort: HUB_EXTERNAL_PORT,
|
|
@@ -4726,21 +5089,22 @@ async function main() {
|
|
|
4726
5089
|
pid: process.pid,
|
|
4727
5090
|
startedAt
|
|
4728
5091
|
};
|
|
4729
|
-
|
|
5092
|
+
writeFileSync4(getHubConfigPath(), JSON.stringify(hubConfig, null, 2) + "\n", "utf-8");
|
|
4730
5093
|
const sessionStore = new SessionStore();
|
|
4731
5094
|
const registry = new SessionRegistry({ store: sessionStore });
|
|
4732
5095
|
registry.startHealthChecks();
|
|
4733
5096
|
const toolHistory = new ToolHistory();
|
|
4734
|
-
const
|
|
4735
|
-
|
|
5097
|
+
const sleepPrevention = new SleepPrevention();
|
|
5098
|
+
const logsDir = join5(hubDir, "logs");
|
|
5099
|
+
if (existsSync4(logsDir)) {
|
|
4736
5100
|
const retentionMs = 30 * 864e5;
|
|
4737
5101
|
const cutoff = Date.now() - retentionMs;
|
|
4738
5102
|
try {
|
|
4739
5103
|
for (const file of readdirSync(logsDir)) {
|
|
4740
|
-
const filePath =
|
|
5104
|
+
const filePath = join5(logsDir, file);
|
|
4741
5105
|
try {
|
|
4742
5106
|
const stat2 = statSync(filePath);
|
|
4743
|
-
if (stat2.mtimeMs < cutoff)
|
|
5107
|
+
if (stat2.mtimeMs < cutoff) unlinkSync2(filePath);
|
|
4744
5108
|
} catch {
|
|
4745
5109
|
}
|
|
4746
5110
|
}
|
|
@@ -4748,8 +5112,8 @@ async function main() {
|
|
|
4748
5112
|
}
|
|
4749
5113
|
}
|
|
4750
5114
|
const __dirname = dirname3(fileURLToPath(import.meta.url));
|
|
4751
|
-
const dashboardPath =
|
|
4752
|
-
const cliEntryPath =
|
|
5115
|
+
const dashboardPath = join5(__dirname, "dashboard");
|
|
5116
|
+
const cliEntryPath = join5(__dirname, "..", "index.js");
|
|
4753
5117
|
const internalApi = createInternalApi({
|
|
4754
5118
|
registry,
|
|
4755
5119
|
port: HUB_INTERNAL_PORT
|
|
@@ -4763,6 +5127,7 @@ async function main() {
|
|
|
4763
5127
|
port: HUB_EXTERNAL_PORT,
|
|
4764
5128
|
previewCollector,
|
|
4765
5129
|
toolHistory,
|
|
5130
|
+
sleepPrevention,
|
|
4766
5131
|
onCreateSession: (tool, cwd) => {
|
|
4767
5132
|
if (!isValidToolName(tool)) {
|
|
4768
5133
|
throw new Error(`Invalid tool name: ${tool}`);
|
|
@@ -4802,6 +5167,7 @@ async function main() {
|
|
|
4802
5167
|
});
|
|
4803
5168
|
scheduleShutdown();
|
|
4804
5169
|
function cleanup() {
|
|
5170
|
+
sleepPrevention.cleanupSync();
|
|
4805
5171
|
previewCollector.close();
|
|
4806
5172
|
registry.stopHealthChecks();
|
|
4807
5173
|
sessionStore.flush();
|
|
@@ -4809,11 +5175,11 @@ async function main() {
|
|
|
4809
5175
|
internalApi.close();
|
|
4810
5176
|
dashboardServer.close();
|
|
4811
5177
|
try {
|
|
4812
|
-
|
|
5178
|
+
unlinkSync2(getPidPath());
|
|
4813
5179
|
} catch {
|
|
4814
5180
|
}
|
|
4815
5181
|
try {
|
|
4816
|
-
|
|
5182
|
+
unlinkSync2(getHubConfigPath());
|
|
4817
5183
|
} catch {
|
|
4818
5184
|
}
|
|
4819
5185
|
}
|