itwillsync 1.5.2 → 1.6.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 +187 -21
- package/dist/hub/daemon.js.map +1 -1
- package/dist/hub/dashboard/assets/{index-Erqx_a0N.css → index-D6z7Ixhf.css} +1 -1
- package/dist/hub/dashboard/assets/index-DbIKRpLQ.js +2 -0
- package/dist/hub/dashboard/index.html +47 -2
- package/dist/index.js +58 -47
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/hub/dashboard/assets/index-DdOxsvuU.js +0 -2
package/dist/hub/daemon.js
CHANGED
|
@@ -3649,10 +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 writeFileSync3, mkdirSync as mkdirSync3, unlinkSync, existsSync as existsSync3, readdirSync, statSync } from "fs";
|
|
3653
|
+
import { homedir as homedir4 } from "os";
|
|
3654
|
+
import { join as join4, dirname as dirname3 } from "path";
|
|
3655
3655
|
import { fileURLToPath } from "url";
|
|
3656
|
+
import { spawn } from "child_process";
|
|
3656
3657
|
|
|
3657
3658
|
// src/auth.ts
|
|
3658
3659
|
import { randomBytes, timingSafeEqual } from "crypto";
|
|
@@ -3840,6 +3841,25 @@ var SessionRegistry = class extends EventEmitter {
|
|
|
3840
3841
|
this.healthCheckInterval = null;
|
|
3841
3842
|
}
|
|
3842
3843
|
}
|
|
3844
|
+
/** Get deduplicated recent working directories from current + persisted sessions. */
|
|
3845
|
+
getRecentDirectories() {
|
|
3846
|
+
const dirMap = /* @__PURE__ */ new Map();
|
|
3847
|
+
for (const s of this.sessions.values()) {
|
|
3848
|
+
const existing = dirMap.get(s.cwd) ?? 0;
|
|
3849
|
+
if (s.lastSeen > existing) {
|
|
3850
|
+
dirMap.set(s.cwd, s.lastSeen);
|
|
3851
|
+
}
|
|
3852
|
+
}
|
|
3853
|
+
if (this.store) {
|
|
3854
|
+
for (const s of this.store.getAllSessions()) {
|
|
3855
|
+
const existing = dirMap.get(s.cwd) ?? 0;
|
|
3856
|
+
if (s.lastSeen > existing) {
|
|
3857
|
+
dirMap.set(s.cwd, s.lastSeen);
|
|
3858
|
+
}
|
|
3859
|
+
}
|
|
3860
|
+
}
|
|
3861
|
+
return Array.from(dirMap.entries()).sort((a, b) => b[1] - a[1]).map(([cwd]) => cwd);
|
|
3862
|
+
}
|
|
3843
3863
|
clear() {
|
|
3844
3864
|
const ids = Array.from(this.sessions.keys());
|
|
3845
3865
|
this.sessions.clear();
|
|
@@ -3891,6 +3911,10 @@ var SessionStore = class {
|
|
|
3891
3911
|
this.writeToDisk();
|
|
3892
3912
|
}, 500);
|
|
3893
3913
|
}
|
|
3914
|
+
/** Get all persisted sessions (including ended). */
|
|
3915
|
+
getAllSessions() {
|
|
3916
|
+
return this.sessions;
|
|
3917
|
+
}
|
|
3894
3918
|
/** Flush any pending save immediately. */
|
|
3895
3919
|
flush() {
|
|
3896
3920
|
if (this.saveTimer) {
|
|
@@ -3923,6 +3947,58 @@ var SessionStore = class {
|
|
|
3923
3947
|
}
|
|
3924
3948
|
};
|
|
3925
3949
|
|
|
3950
|
+
// src/tool-history.ts
|
|
3951
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
3952
|
+
import { homedir as homedir2 } from "os";
|
|
3953
|
+
import { join as join2, dirname as dirname2 } from "path";
|
|
3954
|
+
var MAX_ENTRIES = 20;
|
|
3955
|
+
function getHistoryPath() {
|
|
3956
|
+
const dir = process.env.ITWILLSYNC_CONFIG_DIR || join2(homedir2(), ".itwillsync");
|
|
3957
|
+
return join2(dir, "tool-history.json");
|
|
3958
|
+
}
|
|
3959
|
+
var ToolHistory = class {
|
|
3960
|
+
tools = [];
|
|
3961
|
+
constructor() {
|
|
3962
|
+
this.load();
|
|
3963
|
+
}
|
|
3964
|
+
/** Get tool names sorted by most recently used. */
|
|
3965
|
+
getTools() {
|
|
3966
|
+
return this.tools.sort((a, b) => b.lastUsed - a.lastUsed).map((t) => t.name);
|
|
3967
|
+
}
|
|
3968
|
+
/** Record a tool usage (add or update lastUsed). */
|
|
3969
|
+
recordUsage(toolName) {
|
|
3970
|
+
const existing = this.tools.find((t) => t.name === toolName);
|
|
3971
|
+
if (existing) {
|
|
3972
|
+
existing.lastUsed = Date.now();
|
|
3973
|
+
} else {
|
|
3974
|
+
this.tools.push({ name: toolName, lastUsed: Date.now() });
|
|
3975
|
+
}
|
|
3976
|
+
if (this.tools.length > MAX_ENTRIES) {
|
|
3977
|
+
this.tools.sort((a, b) => b.lastUsed - a.lastUsed);
|
|
3978
|
+
this.tools = this.tools.slice(0, MAX_ENTRIES);
|
|
3979
|
+
}
|
|
3980
|
+
this.save();
|
|
3981
|
+
}
|
|
3982
|
+
load() {
|
|
3983
|
+
const path = getHistoryPath();
|
|
3984
|
+
if (!existsSync2(path)) return;
|
|
3985
|
+
try {
|
|
3986
|
+
const raw = readFileSync2(path, "utf-8");
|
|
3987
|
+
const data = JSON.parse(raw);
|
|
3988
|
+
if (Array.isArray(data.tools)) {
|
|
3989
|
+
this.tools = data.tools;
|
|
3990
|
+
}
|
|
3991
|
+
} catch {
|
|
3992
|
+
}
|
|
3993
|
+
}
|
|
3994
|
+
save() {
|
|
3995
|
+
const path = getHistoryPath();
|
|
3996
|
+
mkdirSync2(dirname2(path), { recursive: true });
|
|
3997
|
+
const data = { tools: this.tools };
|
|
3998
|
+
writeFileSync2(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
3999
|
+
}
|
|
4000
|
+
};
|
|
4001
|
+
|
|
3926
4002
|
// src/internal-api.ts
|
|
3927
4003
|
import { createServer } from "http";
|
|
3928
4004
|
function createInternalApi(options) {
|
|
@@ -4100,8 +4176,9 @@ function readBody(req) {
|
|
|
4100
4176
|
|
|
4101
4177
|
// src/server.ts
|
|
4102
4178
|
import { createServer as createServer2 } from "http";
|
|
4103
|
-
import { readFile } from "fs/promises";
|
|
4104
|
-
import { join as
|
|
4179
|
+
import { readFile, readdir, stat, realpath } from "fs/promises";
|
|
4180
|
+
import { join as join3, extname } from "path";
|
|
4181
|
+
import { homedir as homedir3 } from "os";
|
|
4105
4182
|
import { gzipSync } from "zlib";
|
|
4106
4183
|
|
|
4107
4184
|
// ../../node_modules/.pnpm/ws@8.19.0/node_modules/ws/wrapper.mjs
|
|
@@ -4124,7 +4201,8 @@ var MIME_TYPES = {
|
|
|
4124
4201
|
var COMPRESSIBLE = /* @__PURE__ */ new Set([".html", ".js", ".css", ".json", ".svg"]);
|
|
4125
4202
|
var PING_INTERVAL_MS = 3e4;
|
|
4126
4203
|
function createDashboardServer(options) {
|
|
4127
|
-
const { registry, masterToken, dashboardPath, host, port, previewCollector } = options;
|
|
4204
|
+
const { registry, masterToken, dashboardPath, host, port, previewCollector, toolHistory, onCreateSession } = options;
|
|
4205
|
+
const homeDir = homedir3();
|
|
4128
4206
|
const clients = /* @__PURE__ */ new Set();
|
|
4129
4207
|
const aliveMap = /* @__PURE__ */ new WeakMap();
|
|
4130
4208
|
const gzipCache = /* @__PURE__ */ new Map();
|
|
@@ -4171,10 +4249,49 @@ function createDashboardServer(options) {
|
|
|
4171
4249
|
res.end(JSON.stringify({ sessions }));
|
|
4172
4250
|
return;
|
|
4173
4251
|
}
|
|
4252
|
+
if (pathname === "/api/tool-history") {
|
|
4253
|
+
const tools = toolHistory ? toolHistory.getTools() : [];
|
|
4254
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
4255
|
+
res.end(JSON.stringify({ tools }));
|
|
4256
|
+
return;
|
|
4257
|
+
}
|
|
4258
|
+
if (pathname === "/api/recent-dirs") {
|
|
4259
|
+
const dirs = registry.getRecentDirectories();
|
|
4260
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
4261
|
+
res.end(JSON.stringify({ dirs }));
|
|
4262
|
+
return;
|
|
4263
|
+
}
|
|
4264
|
+
if (pathname === "/api/browse") {
|
|
4265
|
+
const rawPath = url.searchParams.get("path") || "~";
|
|
4266
|
+
const browsePath = rawPath.replace(/^~/, homeDir);
|
|
4267
|
+
try {
|
|
4268
|
+
const resolved = await realpath(browsePath);
|
|
4269
|
+
if (!resolved.startsWith(homeDir)) {
|
|
4270
|
+
res.writeHead(403, { "Content-Type": "application/json" });
|
|
4271
|
+
res.end(JSON.stringify({ error: "Access denied" }));
|
|
4272
|
+
return;
|
|
4273
|
+
}
|
|
4274
|
+
const dirStat = await stat(resolved);
|
|
4275
|
+
if (!dirStat.isDirectory()) {
|
|
4276
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
4277
|
+
res.end(JSON.stringify({ error: "Not a directory" }));
|
|
4278
|
+
return;
|
|
4279
|
+
}
|
|
4280
|
+
const entries = await readdir(resolved, { withFileTypes: true });
|
|
4281
|
+
const dirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort();
|
|
4282
|
+
const displayPath = resolved.replace(homeDir, "~");
|
|
4283
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
4284
|
+
res.end(JSON.stringify({ path: displayPath, resolvedPath: resolved, entries: dirs }));
|
|
4285
|
+
} catch {
|
|
4286
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
4287
|
+
res.end(JSON.stringify({ error: "Cannot read directory" }));
|
|
4288
|
+
}
|
|
4289
|
+
return;
|
|
4290
|
+
}
|
|
4174
4291
|
await serveStaticFile(dashboardPath, pathname, req, res);
|
|
4175
4292
|
});
|
|
4176
4293
|
async function serveStaticFile(basePath, filePath, req, res) {
|
|
4177
|
-
const fullPath =
|
|
4294
|
+
const fullPath = join3(basePath, filePath);
|
|
4178
4295
|
const ext = extname(fullPath);
|
|
4179
4296
|
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
4180
4297
|
try {
|
|
@@ -4280,6 +4397,32 @@ function createDashboardServer(options) {
|
|
|
4280
4397
|
}
|
|
4281
4398
|
break;
|
|
4282
4399
|
}
|
|
4400
|
+
case "create-session": {
|
|
4401
|
+
if (!onCreateSession) {
|
|
4402
|
+
ws.send(JSON.stringify({ type: "session-create-error", error: "Session creation not available" }));
|
|
4403
|
+
break;
|
|
4404
|
+
}
|
|
4405
|
+
const tool = (msg.tool || "").trim();
|
|
4406
|
+
const rawCwd = (msg.cwd || "").trim();
|
|
4407
|
+
if (!tool) {
|
|
4408
|
+
ws.send(JSON.stringify({ type: "session-create-error", error: "Tool name is required" }));
|
|
4409
|
+
break;
|
|
4410
|
+
}
|
|
4411
|
+
const cwd = rawCwd ? rawCwd.replace(/^~/, homeDir) : homeDir;
|
|
4412
|
+
try {
|
|
4413
|
+
const resolved = await realpath(cwd);
|
|
4414
|
+
const dirStat = await stat(resolved);
|
|
4415
|
+
if (!dirStat.isDirectory()) {
|
|
4416
|
+
ws.send(JSON.stringify({ type: "session-create-error", error: "Not a directory" }));
|
|
4417
|
+
break;
|
|
4418
|
+
}
|
|
4419
|
+
ws.send(JSON.stringify({ type: "session-creating", tool, cwd: rawCwd || "~" }));
|
|
4420
|
+
onCreateSession(tool, resolved);
|
|
4421
|
+
} catch (err) {
|
|
4422
|
+
ws.send(JSON.stringify({ type: "session-create-error", error: err.message }));
|
|
4423
|
+
}
|
|
4424
|
+
break;
|
|
4425
|
+
}
|
|
4283
4426
|
case "get-metadata": {
|
|
4284
4427
|
const session = registry.getById(msg.sessionId);
|
|
4285
4428
|
if (!session) {
|
|
@@ -4549,20 +4692,33 @@ var HUB_EXTERNAL_PORT = 7962;
|
|
|
4549
4692
|
var HUB_INTERNAL_PORT = 7963;
|
|
4550
4693
|
var AUTO_SHUTDOWN_DELAY_MS = 3e4;
|
|
4551
4694
|
function getHubDir() {
|
|
4552
|
-
return process.env.ITWILLSYNC_CONFIG_DIR ||
|
|
4695
|
+
return process.env.ITWILLSYNC_CONFIG_DIR || join4(homedir4(), ".itwillsync");
|
|
4553
4696
|
}
|
|
4554
4697
|
function getPidPath() {
|
|
4555
|
-
return
|
|
4698
|
+
return join4(getHubDir(), "hub.pid");
|
|
4556
4699
|
}
|
|
4557
4700
|
function getHubConfigPath() {
|
|
4558
|
-
return
|
|
4701
|
+
return join4(getHubDir(), "hub.json");
|
|
4702
|
+
}
|
|
4703
|
+
function isValidToolName(tool) {
|
|
4704
|
+
return /^[a-zA-Z0-9._-]+$/.test(tool) && tool.length > 0 && tool.length <= 100;
|
|
4705
|
+
}
|
|
4706
|
+
function spawnSession(tool, cwd, cliEntryPath) {
|
|
4707
|
+
const child = spawn(process.execPath, [cliEntryPath, "--headless", "--", tool], {
|
|
4708
|
+
cwd,
|
|
4709
|
+
stdio: "ignore",
|
|
4710
|
+
detached: true,
|
|
4711
|
+
env: { ...process.env }
|
|
4712
|
+
});
|
|
4713
|
+
child.unref();
|
|
4714
|
+
return child;
|
|
4559
4715
|
}
|
|
4560
4716
|
async function main() {
|
|
4561
4717
|
const hubDir = getHubDir();
|
|
4562
|
-
|
|
4718
|
+
mkdirSync3(hubDir, { recursive: true });
|
|
4563
4719
|
const masterToken = generateToken();
|
|
4564
4720
|
const startedAt = Date.now();
|
|
4565
|
-
|
|
4721
|
+
writeFileSync3(getPidPath(), String(process.pid), "utf-8");
|
|
4566
4722
|
const hubConfig = {
|
|
4567
4723
|
masterToken,
|
|
4568
4724
|
externalPort: HUB_EXTERNAL_PORT,
|
|
@@ -4570,28 +4726,30 @@ async function main() {
|
|
|
4570
4726
|
pid: process.pid,
|
|
4571
4727
|
startedAt
|
|
4572
4728
|
};
|
|
4573
|
-
|
|
4729
|
+
writeFileSync3(getHubConfigPath(), JSON.stringify(hubConfig, null, 2) + "\n", "utf-8");
|
|
4574
4730
|
const sessionStore = new SessionStore();
|
|
4575
4731
|
const registry = new SessionRegistry({ store: sessionStore });
|
|
4576
4732
|
registry.startHealthChecks();
|
|
4577
|
-
const
|
|
4578
|
-
|
|
4733
|
+
const toolHistory = new ToolHistory();
|
|
4734
|
+
const logsDir = join4(hubDir, "logs");
|
|
4735
|
+
if (existsSync3(logsDir)) {
|
|
4579
4736
|
const retentionMs = 30 * 864e5;
|
|
4580
4737
|
const cutoff = Date.now() - retentionMs;
|
|
4581
4738
|
try {
|
|
4582
4739
|
for (const file of readdirSync(logsDir)) {
|
|
4583
|
-
const filePath =
|
|
4740
|
+
const filePath = join4(logsDir, file);
|
|
4584
4741
|
try {
|
|
4585
|
-
const
|
|
4586
|
-
if (
|
|
4742
|
+
const stat2 = statSync(filePath);
|
|
4743
|
+
if (stat2.mtimeMs < cutoff) unlinkSync(filePath);
|
|
4587
4744
|
} catch {
|
|
4588
4745
|
}
|
|
4589
4746
|
}
|
|
4590
4747
|
} catch {
|
|
4591
4748
|
}
|
|
4592
4749
|
}
|
|
4593
|
-
const __dirname =
|
|
4594
|
-
const dashboardPath =
|
|
4750
|
+
const __dirname = dirname3(fileURLToPath(import.meta.url));
|
|
4751
|
+
const dashboardPath = join4(__dirname, "dashboard");
|
|
4752
|
+
const cliEntryPath = join4(__dirname, "..", "index.js");
|
|
4595
4753
|
const internalApi = createInternalApi({
|
|
4596
4754
|
registry,
|
|
4597
4755
|
port: HUB_INTERNAL_PORT
|
|
@@ -4603,7 +4761,15 @@ async function main() {
|
|
|
4603
4761
|
dashboardPath,
|
|
4604
4762
|
host: "0.0.0.0",
|
|
4605
4763
|
port: HUB_EXTERNAL_PORT,
|
|
4606
|
-
previewCollector
|
|
4764
|
+
previewCollector,
|
|
4765
|
+
toolHistory,
|
|
4766
|
+
onCreateSession: (tool, cwd) => {
|
|
4767
|
+
if (!isValidToolName(tool)) {
|
|
4768
|
+
throw new Error(`Invalid tool name: ${tool}`);
|
|
4769
|
+
}
|
|
4770
|
+
toolHistory.recordUsage(tool);
|
|
4771
|
+
spawnSession(tool, cwd, cliEntryPath);
|
|
4772
|
+
}
|
|
4607
4773
|
});
|
|
4608
4774
|
await Promise.all([
|
|
4609
4775
|
internalApi.listen(),
|