anycodex 0.0.14 → 0.0.16
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/bin.js +449 -440
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -22,7 +22,7 @@ import fs5 from "fs";
|
|
|
22
22
|
import readline from "readline";
|
|
23
23
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
24
24
|
|
|
25
|
-
// ../server/dist/chunk-
|
|
25
|
+
// ../server/dist/chunk-YOFLJZXT.js
|
|
26
26
|
import http from "http";
|
|
27
27
|
import { fileURLToPath } from "url";
|
|
28
28
|
import path3 from "path";
|
|
@@ -459,34 +459,30 @@ function simpleGlobMatch(filename, pattern) {
|
|
|
459
459
|
}
|
|
460
460
|
var ANYCODE_DIR = path3.join(os.homedir(), ".anycode");
|
|
461
461
|
var DB_PATH = path3.join(ANYCODE_DIR, "data.db");
|
|
462
|
-
|
|
462
|
+
function loadConfig() {
|
|
463
|
+
let userSettings = {};
|
|
463
464
|
try {
|
|
464
|
-
|
|
465
|
+
userSettings = JSON.parse(fs4.readFileSync(path3.join(ANYCODE_DIR, "settings.json"), "utf-8"));
|
|
465
466
|
} catch {
|
|
466
|
-
return {};
|
|
467
467
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
});
|
|
487
|
-
process.on("unhandledRejection", (reason) => {
|
|
488
|
-
console.error("\u26A0 Unhandled rejection:", reason instanceof Error ? reason.message : reason);
|
|
489
|
-
});
|
|
468
|
+
const provider = process.env.PROVIDER ?? "";
|
|
469
|
+
const model = process.env.MODEL ?? userSettings.MODEL ?? "claude-sonnet-4-20250514";
|
|
470
|
+
const apiKey = process.env.API_KEY ?? userSettings.API_KEY ?? "";
|
|
471
|
+
const baseUrl = process.env.BASE_URL ?? userSettings.BASE_URL ?? "";
|
|
472
|
+
const port = parseInt(process.env.PORT ?? "3210", 10);
|
|
473
|
+
const previewPort = parseInt(process.env.PREVIEW_PORT ?? String(port + 1), 10);
|
|
474
|
+
if (!provider || !model || !baseUrl) {
|
|
475
|
+
console.error("\u274C Missing PROVIDER, MODEL, BASE_URL");
|
|
476
|
+
process.exit(1);
|
|
477
|
+
}
|
|
478
|
+
if (!apiKey) {
|
|
479
|
+
console.error("\u274C Missing API_KEY");
|
|
480
|
+
console.error("Run 'anycode start' to configure, or set API_KEY env var.");
|
|
481
|
+
process.exit(1);
|
|
482
|
+
}
|
|
483
|
+
const appDist = resolveAppDist();
|
|
484
|
+
return { provider, model, apiKey, baseUrl, port, previewPort, appDist, userSettings };
|
|
485
|
+
}
|
|
490
486
|
function makePaths() {
|
|
491
487
|
const dataPath = path3.join(ANYCODE_DIR, "data");
|
|
492
488
|
fs4.mkdirSync(dataPath, { recursive: true });
|
|
@@ -550,10 +546,9 @@ var NodeGitProvider = class {
|
|
|
550
546
|
}
|
|
551
547
|
};
|
|
552
548
|
var sessions = /* @__PURE__ */ new Map();
|
|
553
|
-
var PROVIDER_ID = PROVIDER;
|
|
554
549
|
var sharedStorage;
|
|
555
550
|
var db;
|
|
556
|
-
function createAgentConfig(directory, sessionId, terminal, preview) {
|
|
551
|
+
function createAgentConfig(cfg, directory, sessionId, terminal, preview) {
|
|
557
552
|
return {
|
|
558
553
|
directory,
|
|
559
554
|
fs: new NodeFS(),
|
|
@@ -566,28 +561,26 @@ function createAgentConfig(directory, sessionId, terminal, preview) {
|
|
|
566
561
|
...terminal ? { terminal } : {},
|
|
567
562
|
...preview ? { preview } : {},
|
|
568
563
|
provider: {
|
|
569
|
-
id:
|
|
570
|
-
apiKey:
|
|
571
|
-
model:
|
|
572
|
-
...
|
|
564
|
+
id: cfg.provider,
|
|
565
|
+
apiKey: cfg.apiKey,
|
|
566
|
+
model: cfg.model,
|
|
567
|
+
...cfg.baseUrl ? { baseUrl: cfg.baseUrl } : {}
|
|
573
568
|
},
|
|
574
|
-
settings: userSettings,
|
|
569
|
+
settings: cfg.userSettings,
|
|
575
570
|
config: {
|
|
576
|
-
model: `${
|
|
577
|
-
small_model: `${
|
|
571
|
+
model: `${cfg.provider}/${cfg.model}`,
|
|
572
|
+
small_model: `${cfg.provider}/${cfg.model}`,
|
|
578
573
|
provider: {
|
|
579
|
-
[
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
npm: /claude/i.test(MODEL) ? "@ai-sdk/anthropic" : "@ai-sdk/openai-compatible",
|
|
583
|
-
...BASE_URL ? { api: BASE_URL } : {},
|
|
574
|
+
[cfg.provider]: {
|
|
575
|
+
npm: /claude/i.test(cfg.model) ? "@ai-sdk/anthropic" : "@ai-sdk/openai-compatible",
|
|
576
|
+
...cfg.baseUrl ? { api: cfg.baseUrl } : {},
|
|
584
577
|
options: {
|
|
585
|
-
apiKey:
|
|
586
|
-
...
|
|
578
|
+
apiKey: cfg.apiKey,
|
|
579
|
+
...cfg.baseUrl ? { baseURL: cfg.baseUrl } : {}
|
|
587
580
|
},
|
|
588
581
|
models: {
|
|
589
|
-
[
|
|
590
|
-
name:
|
|
582
|
+
[cfg.model]: {
|
|
583
|
+
name: cfg.model,
|
|
591
584
|
attachment: true,
|
|
592
585
|
tool_call: true,
|
|
593
586
|
temperature: true,
|
|
@@ -601,7 +594,7 @@ function createAgentConfig(directory, sessionId, terminal, preview) {
|
|
|
601
594
|
}
|
|
602
595
|
};
|
|
603
596
|
}
|
|
604
|
-
function registerSession(id, agent, directory, createdAt) {
|
|
597
|
+
function registerSession(cfg, id, agent, directory, createdAt) {
|
|
605
598
|
const entry = { id, agent, directory, createdAt };
|
|
606
599
|
sessions.set(id, entry);
|
|
607
600
|
agent.on("directory.set", (data) => {
|
|
@@ -613,36 +606,36 @@ function registerSession(id, agent, directory, createdAt) {
|
|
|
613
606
|
}
|
|
614
607
|
db.update("user_session", { op: "eq", field: "session_id", value: id }, { directory: dir });
|
|
615
608
|
console.log(`\u{1F4C2} Session ${id} directory set to: ${dir}`);
|
|
616
|
-
pushState(id);
|
|
617
|
-
watchDirectory(id, dir);
|
|
609
|
+
pushState(cfg, id);
|
|
610
|
+
watchDirectory(cfg, id, dir);
|
|
618
611
|
});
|
|
619
612
|
return entry;
|
|
620
613
|
}
|
|
621
|
-
async function resumeSession(row) {
|
|
614
|
+
async function resumeSession(cfg, row) {
|
|
622
615
|
const sessionId = row.session_id;
|
|
623
616
|
const cached = sessions.get(sessionId);
|
|
624
617
|
if (cached) return cached;
|
|
625
618
|
const dir = row.directory || "";
|
|
626
619
|
const tp = getOrCreateTerminalProvider(sessionId);
|
|
627
|
-
const pp = getOrCreatePreviewProvider(sessionId);
|
|
628
|
-
const agent = new CodeAgent(createAgentConfig(dir, sessionId, tp, pp));
|
|
620
|
+
const pp = getOrCreatePreviewProvider(cfg, sessionId);
|
|
621
|
+
const agent = new CodeAgent(createAgentConfig(cfg, dir, sessionId, tp, pp));
|
|
629
622
|
await agent.init();
|
|
630
|
-
const entry = registerSession(sessionId, agent, dir, row.time_created);
|
|
623
|
+
const entry = registerSession(cfg, sessionId, agent, dir, row.time_created);
|
|
631
624
|
if (dir) {
|
|
632
625
|
try {
|
|
633
626
|
agent.setWorkingDirectory(dir);
|
|
634
627
|
} catch {
|
|
635
628
|
}
|
|
636
|
-
watchDirectory(sessionId, dir);
|
|
629
|
+
watchDirectory(cfg, sessionId, dir);
|
|
637
630
|
}
|
|
638
631
|
console.log(`\u267B\uFE0F Session ${sessionId} resumed`);
|
|
639
632
|
return entry;
|
|
640
633
|
}
|
|
641
|
-
async function createNewWindow(userId, isDefault = false) {
|
|
634
|
+
async function createNewWindow(cfg, userId, isDefault = false) {
|
|
642
635
|
const tempId = `temp-${Date.now()}`;
|
|
643
636
|
const tp = getOrCreateTerminalProvider(tempId);
|
|
644
|
-
const pp = getOrCreatePreviewProvider(tempId);
|
|
645
|
-
const agent = new CodeAgent(createAgentConfig("", void 0, tp, pp));
|
|
637
|
+
const pp = getOrCreatePreviewProvider(cfg, tempId);
|
|
638
|
+
const agent = new CodeAgent(createAgentConfig(cfg, "", void 0, tp, pp));
|
|
646
639
|
await agent.init();
|
|
647
640
|
const sessionId = agent.sessionId;
|
|
648
641
|
const now = Date.now();
|
|
@@ -652,7 +645,7 @@ async function createNewWindow(userId, isDefault = false) {
|
|
|
652
645
|
previewProviders.set(sessionId, pp);
|
|
653
646
|
tp.sessionId = sessionId;
|
|
654
647
|
pp.sessionId = sessionId;
|
|
655
|
-
const entry = registerSession(sessionId, agent, "", now);
|
|
648
|
+
const entry = registerSession(cfg, sessionId, agent, "", now);
|
|
656
649
|
db.insert("user_session", {
|
|
657
650
|
user_id: userId,
|
|
658
651
|
session_id: sessionId,
|
|
@@ -663,22 +656,22 @@ async function createNewWindow(userId, isDefault = false) {
|
|
|
663
656
|
console.log(`\u2705 Window ${sessionId} created for user ${userId}${isDefault ? " (default)" : ""}`);
|
|
664
657
|
return entry;
|
|
665
658
|
}
|
|
666
|
-
async function getOrCreateSession(userId) {
|
|
659
|
+
async function getOrCreateSession(cfg, userId) {
|
|
667
660
|
const rows = db.findMany("user_session", { filter: { op: "eq", field: "user_id", value: userId } });
|
|
668
661
|
const defaultRow = rows.find((r) => r.is_default === 1) || rows[0];
|
|
669
662
|
if (defaultRow) {
|
|
670
663
|
if (defaultRow.is_default !== 1) {
|
|
671
664
|
db.update("user_session", { op: "eq", field: "session_id", value: defaultRow.session_id }, { is_default: 1 });
|
|
672
665
|
}
|
|
673
|
-
return resumeSession(defaultRow);
|
|
666
|
+
return resumeSession(cfg, defaultRow);
|
|
674
667
|
}
|
|
675
|
-
return createNewWindow(userId, true);
|
|
668
|
+
return createNewWindow(cfg, userId, true);
|
|
676
669
|
}
|
|
677
|
-
async function getAllWindows(userId) {
|
|
670
|
+
async function getAllWindows(cfg, userId) {
|
|
678
671
|
const rows = db.findMany("user_session", { filter: { op: "eq", field: "user_id", value: userId } });
|
|
679
672
|
const entries = [];
|
|
680
673
|
for (const row of rows) {
|
|
681
|
-
entries.push(await resumeSession(row));
|
|
674
|
+
entries.push(await resumeSession(cfg, row));
|
|
682
675
|
}
|
|
683
676
|
return entries;
|
|
684
677
|
}
|
|
@@ -741,12 +734,12 @@ var sessionClients = /* @__PURE__ */ new Map();
|
|
|
741
734
|
var sessionChatAbort = /* @__PURE__ */ new Map();
|
|
742
735
|
var lastStateJson = /* @__PURE__ */ new Map();
|
|
743
736
|
var statePushTimers = /* @__PURE__ */ new Map();
|
|
744
|
-
function scheduleStatePush(sessionId, delayMs = 300) {
|
|
737
|
+
function scheduleStatePush(cfg, sessionId, delayMs = 300) {
|
|
745
738
|
const existing = statePushTimers.get(sessionId);
|
|
746
739
|
if (existing) clearTimeout(existing);
|
|
747
740
|
const timer = setTimeout(() => {
|
|
748
741
|
statePushTimers.delete(sessionId);
|
|
749
|
-
void pushState(sessionId);
|
|
742
|
+
void pushState(cfg, sessionId);
|
|
750
743
|
}, delayMs);
|
|
751
744
|
statePushTimers.set(sessionId, timer);
|
|
752
745
|
}
|
|
@@ -905,11 +898,11 @@ ${message}` : message;
|
|
|
905
898
|
}
|
|
906
899
|
}
|
|
907
900
|
var watchers = /* @__PURE__ */ new Map();
|
|
908
|
-
function watchDirectory(sessionId, dir) {
|
|
901
|
+
function watchDirectory(cfg, sessionId, dir) {
|
|
909
902
|
const existing = watchers.get(sessionId);
|
|
910
903
|
if (existing) existing.close();
|
|
911
904
|
const debouncedPush = () => {
|
|
912
|
-
scheduleStatePush(sessionId, 500);
|
|
905
|
+
scheduleStatePush(cfg, sessionId, 500);
|
|
913
906
|
};
|
|
914
907
|
try {
|
|
915
908
|
const watcher = fs4.watch(dir, { recursive: true }, (eventType, filename) => {
|
|
@@ -922,7 +915,7 @@ function watchDirectory(sessionId, dir) {
|
|
|
922
915
|
console.error(`\u274C fs.watch failed for ${dir}:`, err);
|
|
923
916
|
}
|
|
924
917
|
}
|
|
925
|
-
async function buildState(sessionId) {
|
|
918
|
+
async function buildState(cfg, sessionId) {
|
|
926
919
|
const session = getSession(sessionId);
|
|
927
920
|
if (!session) return null;
|
|
928
921
|
const dir = session.directory;
|
|
@@ -933,12 +926,12 @@ async function buildState(sessionId) {
|
|
|
933
926
|
directory: dir,
|
|
934
927
|
changes,
|
|
935
928
|
topLevel,
|
|
936
|
-
previewPort: previewSessionId === sessionId && previewTarget ?
|
|
929
|
+
previewPort: previewSessionId === sessionId && previewTarget ? cfg.previewPort : null
|
|
937
930
|
};
|
|
938
931
|
}
|
|
939
|
-
async function pushState(sessionId) {
|
|
932
|
+
async function pushState(cfg, sessionId) {
|
|
940
933
|
try {
|
|
941
|
-
const payload = await buildState(sessionId);
|
|
934
|
+
const payload = await buildState(cfg, sessionId);
|
|
942
935
|
if (!payload) return;
|
|
943
936
|
const json = JSON.stringify(payload);
|
|
944
937
|
const prev = lastStateJson.get(sessionId);
|
|
@@ -955,11 +948,11 @@ async function pushState(sessionId) {
|
|
|
955
948
|
console.error(`\u274C pushState error:`, err);
|
|
956
949
|
}
|
|
957
950
|
}
|
|
958
|
-
async function sendStateTo(sessionId, client) {
|
|
951
|
+
async function sendStateTo(cfg, sessionId, client) {
|
|
959
952
|
try {
|
|
960
953
|
let json = lastStateJson.get(sessionId);
|
|
961
954
|
if (!json) {
|
|
962
|
-
const payload = await buildState(sessionId);
|
|
955
|
+
const payload = await buildState(cfg, sessionId);
|
|
963
956
|
if (!payload) return;
|
|
964
957
|
json = JSON.stringify(payload);
|
|
965
958
|
lastStateJson.set(sessionId, json);
|
|
@@ -1162,99 +1155,104 @@ var previewTarget = null;
|
|
|
1162
1155
|
var previewSessionId = null;
|
|
1163
1156
|
var NodePreviewProvider = class {
|
|
1164
1157
|
sessionId;
|
|
1165
|
-
|
|
1158
|
+
cfg;
|
|
1159
|
+
constructor(cfg, sessionId) {
|
|
1160
|
+
this.cfg = cfg;
|
|
1166
1161
|
this.sessionId = sessionId;
|
|
1167
1162
|
}
|
|
1168
1163
|
setPreviewTarget(forwardedLocalUrl) {
|
|
1169
1164
|
previewTarget = forwardedLocalUrl.replace(/\/+$/, "");
|
|
1170
1165
|
previewSessionId = this.sessionId;
|
|
1171
|
-
console.log(`\u{1F517} Preview proxy: :${
|
|
1166
|
+
console.log(`\u{1F517} Preview proxy: :${this.cfg.previewPort} \u2192 ${previewTarget} (session ${this.sessionId})`);
|
|
1172
1167
|
broadcast(this.sessionId, {
|
|
1173
1168
|
type: "preview",
|
|
1174
|
-
port:
|
|
1169
|
+
port: this.cfg.previewPort
|
|
1175
1170
|
});
|
|
1176
1171
|
}
|
|
1177
1172
|
};
|
|
1178
1173
|
var previewProviders = /* @__PURE__ */ new Map();
|
|
1179
|
-
function getOrCreatePreviewProvider(sessionId) {
|
|
1174
|
+
function getOrCreatePreviewProvider(cfg, sessionId) {
|
|
1180
1175
|
let pp = previewProviders.get(sessionId);
|
|
1181
1176
|
if (!pp) {
|
|
1182
|
-
pp = new NodePreviewProvider(sessionId);
|
|
1177
|
+
pp = new NodePreviewProvider(cfg, sessionId);
|
|
1183
1178
|
previewProviders.set(sessionId, pp);
|
|
1184
1179
|
}
|
|
1185
1180
|
return pp;
|
|
1186
1181
|
}
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1182
|
+
function createPreviewServer(cfg) {
|
|
1183
|
+
const previewServer = http.createServer((req, res) => {
|
|
1184
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1185
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
|
1186
|
+
res.setHeader("Access-Control-Allow-Headers", "*");
|
|
1187
|
+
if (req.method === "OPTIONS") {
|
|
1188
|
+
res.writeHead(204);
|
|
1189
|
+
res.end();
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
if (!previewTarget) {
|
|
1193
|
+
res.writeHead(502, { "Content-Type": "text/plain" });
|
|
1194
|
+
res.end("No preview target configured");
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
try {
|
|
1198
|
+
const targetUrl = previewTarget + (req.url || "/");
|
|
1199
|
+
const parsed = new URL(targetUrl);
|
|
1200
|
+
const options = {
|
|
1201
|
+
hostname: parsed.hostname,
|
|
1202
|
+
port: parsed.port,
|
|
1203
|
+
path: parsed.pathname + parsed.search,
|
|
1204
|
+
method: req.method,
|
|
1205
|
+
headers: { ...req.headers, host: parsed.host }
|
|
1206
|
+
};
|
|
1207
|
+
const proxyReq = http.request(options, (proxyRes) => {
|
|
1208
|
+
res.writeHead(proxyRes.statusCode || 502, proxyRes.headers);
|
|
1209
|
+
proxyRes.pipe(res);
|
|
1210
|
+
});
|
|
1211
|
+
proxyReq.on("error", (err) => {
|
|
1212
|
+
if (!res.headersSent) res.writeHead(502, { "Content-Type": "text/plain" });
|
|
1213
|
+
res.end(`Preview proxy error: ${err.message}`);
|
|
1214
|
+
});
|
|
1215
|
+
req.pipe(proxyReq);
|
|
1216
|
+
} catch (err) {
|
|
1217
|
+
res.writeHead(502, { "Content-Type": "text/plain" });
|
|
1218
|
+
res.end(`Invalid proxy target: ${err.message}`);
|
|
1219
|
+
}
|
|
1220
|
+
});
|
|
1221
|
+
previewServer.on("upgrade", (req, socket, head) => {
|
|
1222
|
+
if (!previewTarget) {
|
|
1223
|
+
socket.destroy();
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
try {
|
|
1227
|
+
const parsed = new URL(previewTarget);
|
|
1228
|
+
const targetWs = `ws://${parsed.hostname}:${parsed.port}${req.url || "/"}`;
|
|
1229
|
+
const wsTarget = new URL(targetWs);
|
|
1230
|
+
const options = {
|
|
1231
|
+
hostname: wsTarget.hostname,
|
|
1232
|
+
port: wsTarget.port,
|
|
1233
|
+
path: wsTarget.pathname + wsTarget.search,
|
|
1234
|
+
method: "GET",
|
|
1235
|
+
headers: { ...req.headers, host: wsTarget.host }
|
|
1236
|
+
};
|
|
1237
|
+
const proxyReq = http.request(options);
|
|
1238
|
+
proxyReq.on("upgrade", (_proxyRes, proxySocket, proxyHead) => {
|
|
1239
|
+
socket.write(
|
|
1240
|
+
"HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n" + Object.entries(_proxyRes.headers).filter(([k]) => !["upgrade", "connection"].includes(k.toLowerCase())).map(([k, v]) => `${k}: ${v}`).join("\r\n") + "\r\n\r\n"
|
|
1241
|
+
);
|
|
1242
|
+
if (proxyHead.length > 0) socket.write(proxyHead);
|
|
1243
|
+
proxySocket.pipe(socket);
|
|
1244
|
+
socket.pipe(proxySocket);
|
|
1245
|
+
});
|
|
1246
|
+
proxyReq.on("error", () => socket.destroy());
|
|
1247
|
+
socket.on("error", () => proxyReq.destroy());
|
|
1248
|
+
proxyReq.end();
|
|
1249
|
+
} catch {
|
|
1250
|
+
socket.destroy();
|
|
1251
|
+
}
|
|
1252
|
+
});
|
|
1253
|
+
return previewServer;
|
|
1254
|
+
}
|
|
1255
|
+
function adminHTML(cfg) {
|
|
1258
1256
|
return (
|
|
1259
1257
|
/* html */
|
|
1260
1258
|
`<!DOCTYPE html>
|
|
@@ -1305,9 +1303,9 @@ function adminHTML() {
|
|
|
1305
1303
|
<h1><span class="dot"></span> AnyCode Server</h1>
|
|
1306
1304
|
<div class="card">
|
|
1307
1305
|
<h2>\u2699 Configuration</h2>
|
|
1308
|
-
<div class="row"><span class="label">Provider</span><span class="value">${
|
|
1309
|
-
<div class="row"><span class="label">Model</span><span class="value">${
|
|
1310
|
-
<div class="row"><span class="label">Port</span><span class="value">${
|
|
1306
|
+
<div class="row"><span class="label">Provider</span><span class="value">${cfg.provider}</span></div>
|
|
1307
|
+
<div class="row"><span class="label">Model</span><span class="value">${cfg.model}</span></div>
|
|
1308
|
+
<div class="row"><span class="label">Port</span><span class="value">${cfg.port}</span></div>
|
|
1311
1309
|
<div class="row"><span class="label">Sessions</span><span class="value" id="session-count">0</span></div>
|
|
1312
1310
|
</div>
|
|
1313
1311
|
<div class="card">
|
|
@@ -1374,10 +1372,9 @@ function resolveAppDist() {
|
|
|
1374
1372
|
}
|
|
1375
1373
|
return bundled;
|
|
1376
1374
|
}
|
|
1377
|
-
|
|
1378
|
-
function serveStatic(req, res) {
|
|
1375
|
+
function serveStatic(cfg, req, res) {
|
|
1379
1376
|
const url = req.url || "/";
|
|
1380
|
-
const filePath = path3.join(
|
|
1377
|
+
const filePath = path3.join(cfg.appDist, url);
|
|
1381
1378
|
if (fs4.existsSync(filePath) && fs4.statSync(filePath).isFile()) {
|
|
1382
1379
|
const ext = path3.extname(filePath);
|
|
1383
1380
|
res.writeHead(200, { "Content-Type": MIME_TYPES[ext] || "application/octet-stream" });
|
|
@@ -1386,10 +1383,10 @@ function serveStatic(req, res) {
|
|
|
1386
1383
|
}
|
|
1387
1384
|
return false;
|
|
1388
1385
|
}
|
|
1389
|
-
function serveAppIndex(res) {
|
|
1390
|
-
const indexPath = path3.join(
|
|
1386
|
+
function serveAppIndex(cfg, res) {
|
|
1387
|
+
const indexPath = path3.join(cfg.appDist, "index.html");
|
|
1391
1388
|
if (fs4.existsSync(indexPath)) {
|
|
1392
|
-
const webSocket = userSettings.webSocket === true;
|
|
1389
|
+
const webSocket = cfg.userSettings.webSocket === true;
|
|
1393
1390
|
const configScript = `<script>window.__ANYCODE_CONFIG__=${JSON.stringify({ webSocket })}</script>`;
|
|
1394
1391
|
let html = fs4.readFileSync(indexPath, "utf-8");
|
|
1395
1392
|
html = html.replace("</head>", `${configScript}</head>`);
|
|
@@ -1399,331 +1396,343 @@ function serveAppIndex(res) {
|
|
|
1399
1396
|
}
|
|
1400
1397
|
return false;
|
|
1401
1398
|
}
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
}
|
|
1411
|
-
if (req.method === "POST" && req.url === "/api/poll/connect") {
|
|
1412
|
-
let body = "";
|
|
1413
|
-
for await (const chunk of req) body += chunk;
|
|
1414
|
-
const { sessionId } = body ? JSON.parse(body) : {};
|
|
1415
|
-
const session = sessionId ? getSession(sessionId) : void 0;
|
|
1416
|
-
if (!session) {
|
|
1417
|
-
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1418
|
-
res.end(JSON.stringify({ error: "Session not found" }));
|
|
1399
|
+
function createMainServer(cfg) {
|
|
1400
|
+
const server = http.createServer(async (req, res) => {
|
|
1401
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1402
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
1403
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
1404
|
+
if (req.method === "OPTIONS") {
|
|
1405
|
+
res.writeHead(204);
|
|
1406
|
+
res.end();
|
|
1419
1407
|
return;
|
|
1420
1408
|
}
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
res.
|
|
1409
|
+
if (req.method === "POST" && req.url === "/api/poll/connect") {
|
|
1410
|
+
let body = "";
|
|
1411
|
+
for await (const chunk of req) body += chunk;
|
|
1412
|
+
const { sessionId } = body ? JSON.parse(body) : {};
|
|
1413
|
+
const session = sessionId ? getSession(sessionId) : void 0;
|
|
1414
|
+
if (!session) {
|
|
1415
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1416
|
+
res.end(JSON.stringify({ error: "Session not found" }));
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1419
|
+
const channelId = Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
1420
|
+
const client = new PollingClient(channelId, sessionId);
|
|
1421
|
+
pollingClients.set(channelId, client);
|
|
1422
|
+
getSessionClients(sessionId).add(client);
|
|
1423
|
+
console.log(`\u{1F50C} Poll client connected to session ${sessionId} (channel=${channelId})`);
|
|
1424
|
+
sendStateTo(cfg, sessionId, client);
|
|
1425
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1426
|
+
res.end(JSON.stringify({ channelId }));
|
|
1438
1427
|
return;
|
|
1439
1428
|
}
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
res.end(JSON.stringify({ error: "Channel not found" }));
|
|
1429
|
+
if (req.method === "GET" && req.url?.startsWith("/api/poll?")) {
|
|
1430
|
+
const url = new URL(req.url, `http://localhost:${cfg.port}`);
|
|
1431
|
+
const channelId = url.searchParams.get("channelId");
|
|
1432
|
+
const client = channelId ? pollingClients.get(channelId) : void 0;
|
|
1433
|
+
if (!client || client.readyState !== 1) {
|
|
1434
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1435
|
+
res.end(JSON.stringify({ error: "Channel not found" }));
|
|
1436
|
+
return;
|
|
1437
|
+
}
|
|
1438
|
+
client.hold(res);
|
|
1451
1439
|
return;
|
|
1452
1440
|
}
|
|
1453
|
-
|
|
1454
|
-
handleClientMessage(client.sessionId, client, data).catch(() => {
|
|
1455
|
-
});
|
|
1456
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1457
|
-
res.end(JSON.stringify({ ok: true }));
|
|
1458
|
-
return;
|
|
1459
|
-
}
|
|
1460
|
-
if (req.method === "POST" && req.url === "/api/poll/close") {
|
|
1461
|
-
let body = "";
|
|
1462
|
-
for await (const chunk of req) body += chunk;
|
|
1463
|
-
const { channelId } = body ? JSON.parse(body) : {};
|
|
1464
|
-
const client = channelId ? pollingClients.get(channelId) : void 0;
|
|
1465
|
-
if (client) {
|
|
1466
|
-
client.close();
|
|
1467
|
-
removeClient(client.sessionId, client);
|
|
1468
|
-
pollingClients.delete(channelId);
|
|
1469
|
-
console.log(`\u{1F50C} Poll client disconnected (channel=${channelId})`);
|
|
1470
|
-
}
|
|
1471
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1472
|
-
res.end(JSON.stringify({ ok: true }));
|
|
1473
|
-
return;
|
|
1474
|
-
}
|
|
1475
|
-
if (req.method === "POST" && req.url === "/api/sessions") {
|
|
1476
|
-
(async () => {
|
|
1441
|
+
if (req.method === "POST" && req.url === "/api/poll/send") {
|
|
1477
1442
|
let body = "";
|
|
1478
1443
|
for await (const chunk of req) body += chunk;
|
|
1479
|
-
const {
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
res.
|
|
1444
|
+
const { channelId, data } = body ? JSON.parse(body) : {};
|
|
1445
|
+
const client = channelId ? pollingClients.get(channelId) : void 0;
|
|
1446
|
+
if (!client || client.readyState !== 1) {
|
|
1447
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1448
|
+
res.end(JSON.stringify({ error: "Channel not found" }));
|
|
1483
1449
|
return;
|
|
1484
1450
|
}
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
res.end(JSON.stringify({ id: entry.id, directory: entry.directory }));
|
|
1488
|
-
}).catch((err) => {
|
|
1489
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1490
|
-
res.end(JSON.stringify({ error: err.message }));
|
|
1451
|
+
client.lastActivity = Date.now();
|
|
1452
|
+
handleClientMessage(client.sessionId, client, data).catch(() => {
|
|
1491
1453
|
});
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1510
|
-
res.end(JSON.stringify({ error: "userId is required" }));
|
|
1454
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1455
|
+
res.end(JSON.stringify({ ok: true }));
|
|
1456
|
+
return;
|
|
1457
|
+
}
|
|
1458
|
+
if (req.method === "POST" && req.url === "/api/poll/close") {
|
|
1459
|
+
let body = "";
|
|
1460
|
+
for await (const chunk of req) body += chunk;
|
|
1461
|
+
const { channelId } = body ? JSON.parse(body) : {};
|
|
1462
|
+
const client = channelId ? pollingClients.get(channelId) : void 0;
|
|
1463
|
+
if (client) {
|
|
1464
|
+
client.close();
|
|
1465
|
+
removeClient(client.sessionId, client);
|
|
1466
|
+
pollingClients.delete(channelId);
|
|
1467
|
+
console.log(`\u{1F50C} Poll client disconnected (channel=${channelId})`);
|
|
1468
|
+
}
|
|
1469
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1470
|
+
res.end(JSON.stringify({ ok: true }));
|
|
1511
1471
|
return;
|
|
1512
1472
|
}
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1473
|
+
if (req.method === "POST" && req.url === "/api/sessions") {
|
|
1474
|
+
(async () => {
|
|
1475
|
+
let body = "";
|
|
1476
|
+
for await (const chunk of req) body += chunk;
|
|
1477
|
+
const { userId } = body ? JSON.parse(body) : {};
|
|
1478
|
+
if (!userId) {
|
|
1479
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1480
|
+
res.end(JSON.stringify({ error: "userId is required" }));
|
|
1481
|
+
return;
|
|
1482
|
+
}
|
|
1483
|
+
getOrCreateSession(cfg, userId).then((entry) => {
|
|
1484
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1485
|
+
res.end(JSON.stringify({ id: entry.id, directory: entry.directory }));
|
|
1486
|
+
}).catch((err) => {
|
|
1487
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1488
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
1489
|
+
});
|
|
1490
|
+
})();
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1493
|
+
if (req.method === "GET" && req.url === "/api/sessions") {
|
|
1494
|
+
const list = Array.from(sessions.values()).map((s) => ({
|
|
1495
|
+
id: s.id,
|
|
1496
|
+
directory: s.directory,
|
|
1497
|
+
createdAt: s.createdAt
|
|
1521
1498
|
}));
|
|
1522
1499
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1523
1500
|
res.end(JSON.stringify(list));
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
}
|
|
1530
|
-
if (req.method === "POST" && req.url === "/api/windows") {
|
|
1531
|
-
(async () => {
|
|
1532
|
-
let body = "";
|
|
1533
|
-
for await (const chunk of req) body += chunk;
|
|
1534
|
-
const { userId } = body ? JSON.parse(body) : {};
|
|
1501
|
+
return;
|
|
1502
|
+
}
|
|
1503
|
+
if (req.method === "GET" && req.url?.startsWith("/api/windows")) {
|
|
1504
|
+
const url = new URL(req.url, `http://localhost:${cfg.port}`);
|
|
1505
|
+
const userId = url.searchParams.get("userId");
|
|
1535
1506
|
if (!userId) {
|
|
1536
1507
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1537
1508
|
res.end(JSON.stringify({ error: "userId is required" }));
|
|
1538
1509
|
return;
|
|
1539
1510
|
}
|
|
1540
|
-
|
|
1511
|
+
getAllWindows(cfg, userId).then((entries) => {
|
|
1512
|
+
const rows = db.findMany("user_session", { filter: { op: "eq", field: "user_id", value: userId } });
|
|
1513
|
+
const defaultMap = new Map(rows.map((r) => [r.session_id, r.is_default === 1]));
|
|
1514
|
+
const list = entries.map((e) => ({
|
|
1515
|
+
id: e.id,
|
|
1516
|
+
directory: e.directory,
|
|
1517
|
+
createdAt: e.createdAt,
|
|
1518
|
+
isDefault: defaultMap.get(e.id) ?? false
|
|
1519
|
+
}));
|
|
1541
1520
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1542
|
-
res.end(JSON.stringify(
|
|
1521
|
+
res.end(JSON.stringify(list));
|
|
1543
1522
|
}).catch((err) => {
|
|
1544
1523
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1545
1524
|
res.end(JSON.stringify({ error: err.message }));
|
|
1546
1525
|
});
|
|
1547
|
-
})();
|
|
1548
|
-
return;
|
|
1549
|
-
}
|
|
1550
|
-
const windowDeleteMatch = req.url?.match(/^\/api\/windows\/([^/?]+)$/);
|
|
1551
|
-
if (req.method === "DELETE" && windowDeleteMatch) {
|
|
1552
|
-
const ok = deleteWindow(windowDeleteMatch[1]);
|
|
1553
|
-
if (ok) {
|
|
1554
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1555
|
-
res.end(JSON.stringify({ ok: true }));
|
|
1556
|
-
} else {
|
|
1557
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1558
|
-
res.end(JSON.stringify({ error: "Cannot delete default window or window not found" }));
|
|
1559
|
-
}
|
|
1560
|
-
return;
|
|
1561
|
-
}
|
|
1562
|
-
const sessionMatch = req.url?.match(/^\/api\/sessions\/([^/?]+)(?:\/([a-z]+))?/);
|
|
1563
|
-
if (req.method === "GET" && sessionMatch) {
|
|
1564
|
-
const session = getSession(sessionMatch[1]);
|
|
1565
|
-
if (!session) {
|
|
1566
|
-
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1567
|
-
res.end(JSON.stringify({ error: "Session not found" }));
|
|
1568
1526
|
return;
|
|
1569
1527
|
}
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1528
|
+
if (req.method === "POST" && req.url === "/api/windows") {
|
|
1529
|
+
(async () => {
|
|
1530
|
+
let body = "";
|
|
1531
|
+
for await (const chunk of req) body += chunk;
|
|
1532
|
+
const { userId } = body ? JSON.parse(body) : {};
|
|
1533
|
+
if (!userId) {
|
|
1534
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1535
|
+
res.end(JSON.stringify({ error: "userId is required" }));
|
|
1536
|
+
return;
|
|
1537
|
+
}
|
|
1538
|
+
createNewWindow(cfg, userId, false).then((entry) => {
|
|
1539
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1540
|
+
res.end(JSON.stringify({ id: entry.id, directory: entry.directory, isDefault: false }));
|
|
1541
|
+
}).catch((err) => {
|
|
1542
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1543
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
1544
|
+
});
|
|
1545
|
+
})();
|
|
1581
1546
|
return;
|
|
1582
1547
|
}
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
const
|
|
1586
|
-
if (
|
|
1548
|
+
const windowDeleteMatch = req.url?.match(/^\/api\/windows\/([^/?]+)$/);
|
|
1549
|
+
if (req.method === "DELETE" && windowDeleteMatch) {
|
|
1550
|
+
const ok = deleteWindow(windowDeleteMatch[1]);
|
|
1551
|
+
if (ok) {
|
|
1587
1552
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1588
|
-
res.end(JSON.stringify({
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
if (!target.startsWith(path3.resolve(dir))) {
|
|
1593
|
-
res.writeHead(403, { "Content-Type": "application/json" });
|
|
1594
|
-
res.end(JSON.stringify({ error: "Forbidden" }));
|
|
1595
|
-
return;
|
|
1553
|
+
res.end(JSON.stringify({ ok: true }));
|
|
1554
|
+
} else {
|
|
1555
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1556
|
+
res.end(JSON.stringify({ error: "Cannot delete default window or window not found" }));
|
|
1596
1557
|
}
|
|
1597
|
-
const entries = await listDir(target);
|
|
1598
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1599
|
-
res.end(JSON.stringify({ entries }));
|
|
1600
1558
|
return;
|
|
1601
1559
|
}
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
const
|
|
1605
|
-
if (!
|
|
1606
|
-
res.writeHead(
|
|
1607
|
-
res.end(JSON.stringify({
|
|
1560
|
+
const sessionMatch = req.url?.match(/^\/api\/sessions\/([^/?]+)(?:\/([a-z]+))?/);
|
|
1561
|
+
if (req.method === "GET" && sessionMatch) {
|
|
1562
|
+
const session = getSession(sessionMatch[1]);
|
|
1563
|
+
if (!session) {
|
|
1564
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1565
|
+
res.end(JSON.stringify({ error: "Session not found" }));
|
|
1608
1566
|
return;
|
|
1609
1567
|
}
|
|
1610
|
-
const
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1568
|
+
const sub = sessionMatch[2];
|
|
1569
|
+
const url = new URL(req.url, `http://localhost:${cfg.port}`);
|
|
1570
|
+
if (sub === "state") {
|
|
1571
|
+
const dir = session.directory;
|
|
1572
|
+
const [topLevel, changes] = await Promise.all([
|
|
1573
|
+
dir ? listDir(dir) : Promise.resolve([]),
|
|
1574
|
+
dir ? getGitChanges(dir) : Promise.resolve([])
|
|
1575
|
+
]);
|
|
1576
|
+
const hasPreview = previewSessionId === session.id && previewTarget;
|
|
1577
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1578
|
+
res.end(JSON.stringify({ directory: dir, topLevel, changes, previewPort: hasPreview ? cfg.previewPort : null }));
|
|
1614
1579
|
return;
|
|
1615
1580
|
}
|
|
1616
|
-
|
|
1617
|
-
const
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1581
|
+
if (sub === "ls") {
|
|
1582
|
+
const subPath = url.searchParams.get("path") || "";
|
|
1583
|
+
const dir = session.directory;
|
|
1584
|
+
if (!dir) {
|
|
1585
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1586
|
+
res.end(JSON.stringify({ entries: [] }));
|
|
1587
|
+
return;
|
|
1588
|
+
}
|
|
1589
|
+
const target = path3.resolve(dir, subPath);
|
|
1590
|
+
if (!target.startsWith(path3.resolve(dir))) {
|
|
1591
|
+
res.writeHead(403, { "Content-Type": "application/json" });
|
|
1592
|
+
res.end(JSON.stringify({ error: "Forbidden" }));
|
|
1593
|
+
return;
|
|
1594
|
+
}
|
|
1595
|
+
const entries = await listDir(target);
|
|
1621
1596
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1622
|
-
res.end(JSON.stringify({
|
|
1597
|
+
res.end(JSON.stringify({ entries }));
|
|
1598
|
+
return;
|
|
1623
1599
|
}
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1600
|
+
if (sub === "file") {
|
|
1601
|
+
const filePath = url.searchParams.get("path") || "";
|
|
1602
|
+
const dir = session.directory;
|
|
1603
|
+
if (!dir) {
|
|
1604
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1605
|
+
res.end(JSON.stringify({ content: null, error: "No directory" }));
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
const target = path3.resolve(dir, filePath);
|
|
1609
|
+
if (!target.startsWith(path3.resolve(dir))) {
|
|
1610
|
+
res.writeHead(403, { "Content-Type": "application/json" });
|
|
1611
|
+
res.end(JSON.stringify({ error: "Forbidden" }));
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
try {
|
|
1615
|
+
const content = await fsPromises.readFile(target, "utf-8");
|
|
1616
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1617
|
+
res.end(JSON.stringify({ content }));
|
|
1618
|
+
} catch {
|
|
1619
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1620
|
+
res.end(JSON.stringify({ content: null, error: "\u8BFB\u53D6\u5931\u8D25" }));
|
|
1621
|
+
}
|
|
1632
1622
|
return;
|
|
1633
1623
|
}
|
|
1634
|
-
|
|
1635
|
-
const
|
|
1636
|
-
const
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
{
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1624
|
+
if (sub === "diff") {
|
|
1625
|
+
const filePath = url.searchParams.get("path") || "";
|
|
1626
|
+
const dir = session.directory;
|
|
1627
|
+
if (!dir) {
|
|
1628
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1629
|
+
res.end(JSON.stringify({ added: [], removed: [] }));
|
|
1630
|
+
return;
|
|
1631
|
+
}
|
|
1632
|
+
try {
|
|
1633
|
+
const added = [];
|
|
1634
|
+
const removed = [];
|
|
1635
|
+
let result = await gitProvider.run(
|
|
1636
|
+
["diff", "--unified=0", "--", filePath],
|
|
1644
1637
|
{ cwd: dir }
|
|
1645
1638
|
);
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
const
|
|
1653
|
-
const
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1639
|
+
if (result.exitCode !== 0 || !result.text().trim()) {
|
|
1640
|
+
result = await gitProvider.run(
|
|
1641
|
+
["diff", "--unified=0", "--cached", "--", filePath],
|
|
1642
|
+
{ cwd: dir }
|
|
1643
|
+
);
|
|
1644
|
+
}
|
|
1645
|
+
const diffText = result.text();
|
|
1646
|
+
const hunkRe = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/gm;
|
|
1647
|
+
let m;
|
|
1648
|
+
while (m = hunkRe.exec(diffText)) {
|
|
1649
|
+
const oldStart = parseInt(m[1], 10);
|
|
1650
|
+
const oldCount = parseInt(m[2] ?? "1", 10);
|
|
1651
|
+
const newStart = parseInt(m[3], 10);
|
|
1652
|
+
const newCount = parseInt(m[4] ?? "1", 10);
|
|
1653
|
+
for (let i = 0; i < oldCount; i++) removed.push(oldStart + i);
|
|
1654
|
+
for (let i = 0; i < newCount; i++) added.push(newStart + i);
|
|
1655
|
+
}
|
|
1656
|
+
if (!diffText.trim()) {
|
|
1657
|
+
try {
|
|
1658
|
+
const target = path3.resolve(dir, filePath);
|
|
1659
|
+
if (target.startsWith(path3.resolve(dir))) {
|
|
1660
|
+
const content = await fsPromises.readFile(target, "utf-8");
|
|
1661
|
+
const lineCount = content.split("\n").length;
|
|
1662
|
+
for (let i = 1; i <= lineCount; i++) added.push(i);
|
|
1663
|
+
}
|
|
1664
|
+
} catch {
|
|
1665
1665
|
}
|
|
1666
|
-
} catch {
|
|
1667
1666
|
}
|
|
1667
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1668
|
+
res.end(JSON.stringify({ added, removed }));
|
|
1669
|
+
} catch {
|
|
1670
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1671
|
+
res.end(JSON.stringify({ added: [], removed: [] }));
|
|
1668
1672
|
}
|
|
1669
|
-
|
|
1670
|
-
res.end(JSON.stringify({ added, removed }));
|
|
1671
|
-
} catch {
|
|
1672
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1673
|
-
res.end(JSON.stringify({ added: [], removed: [] }));
|
|
1673
|
+
return;
|
|
1674
1674
|
}
|
|
1675
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1676
|
+
res.end(JSON.stringify({
|
|
1677
|
+
id: session.id,
|
|
1678
|
+
directory: session.directory,
|
|
1679
|
+
createdAt: session.createdAt
|
|
1680
|
+
}));
|
|
1675
1681
|
return;
|
|
1676
1682
|
}
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
const list = Array.from(sessions.values()).map((s) => ({
|
|
1687
|
-
id: s.id,
|
|
1688
|
-
directory: s.directory,
|
|
1689
|
-
stats: s.agent.getStats(),
|
|
1690
|
-
sessionId: s.agent.sessionId
|
|
1691
|
-
}));
|
|
1692
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1693
|
-
res.end(JSON.stringify({ sessions: list }));
|
|
1694
|
-
return;
|
|
1695
|
-
}
|
|
1696
|
-
if (req.method === "GET" && req.url?.startsWith("/api/messages")) {
|
|
1697
|
-
const url = new URL(req.url, `http://localhost:${PORT}`);
|
|
1698
|
-
const sessionId = url.searchParams.get("sessionId");
|
|
1699
|
-
const session = sessionId ? getSession(sessionId) : void 0;
|
|
1700
|
-
if (!session) {
|
|
1701
|
-
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1702
|
-
res.end(JSON.stringify({ error: "Session not found" }));
|
|
1683
|
+
if (req.method === "GET" && req.url === "/api/status") {
|
|
1684
|
+
const list = Array.from(sessions.values()).map((s) => ({
|
|
1685
|
+
id: s.id,
|
|
1686
|
+
directory: s.directory,
|
|
1687
|
+
stats: s.agent.getStats(),
|
|
1688
|
+
sessionId: s.agent.sessionId
|
|
1689
|
+
}));
|
|
1690
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1691
|
+
res.end(JSON.stringify({ sessions: list }));
|
|
1703
1692
|
return;
|
|
1704
1693
|
}
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1694
|
+
if (req.method === "GET" && req.url?.startsWith("/api/messages")) {
|
|
1695
|
+
const url = new URL(req.url, `http://localhost:${cfg.port}`);
|
|
1696
|
+
const sessionId = url.searchParams.get("sessionId");
|
|
1697
|
+
const session = sessionId ? getSession(sessionId) : void 0;
|
|
1698
|
+
if (!session) {
|
|
1699
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1700
|
+
res.end(JSON.stringify({ error: "Session not found" }));
|
|
1701
|
+
return;
|
|
1702
|
+
}
|
|
1703
|
+
session.agent.getSessionMessages({ limit: 30 }).then((messages) => {
|
|
1704
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1705
|
+
res.end(JSON.stringify(messages));
|
|
1706
|
+
}).catch((err) => {
|
|
1707
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1708
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
1709
|
+
});
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
if (req.method === "GET" && req.url === "/admin") {
|
|
1713
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
1714
|
+
res.end(adminHTML(cfg));
|
|
1715
|
+
return;
|
|
1716
|
+
}
|
|
1717
|
+
if (req.method === "GET") {
|
|
1718
|
+
if (serveStatic(cfg, req, res)) return;
|
|
1719
|
+
if (serveAppIndex(cfg, res)) return;
|
|
1720
|
+
}
|
|
1721
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1722
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
1723
|
+
});
|
|
1724
|
+
return server;
|
|
1725
|
+
}
|
|
1726
1726
|
async function startServer() {
|
|
1727
|
+
const cfg = loadConfig();
|
|
1728
|
+
process.on("uncaughtException", (err) => {
|
|
1729
|
+
console.error("\u26A0 Uncaught exception:", err.message);
|
|
1730
|
+
});
|
|
1731
|
+
process.on("unhandledRejection", (reason) => {
|
|
1732
|
+
console.error("\u26A0 Unhandled rejection:", reason instanceof Error ? reason.message : reason);
|
|
1733
|
+
});
|
|
1734
|
+
const previewServer = createPreviewServer(cfg);
|
|
1735
|
+
const server = createMainServer(cfg);
|
|
1727
1736
|
console.log("\u{1F680} Starting @any-code/server\u2026");
|
|
1728
1737
|
sharedStorage = new SqlJsStorage(DB_PATH);
|
|
1729
1738
|
const migrations = Database.getMigrations();
|
|
@@ -1764,7 +1773,7 @@ async function startServer() {
|
|
|
1764
1773
|
)
|
|
1765
1774
|
`);
|
|
1766
1775
|
}
|
|
1767
|
-
const appDistExists = fs4.existsSync(
|
|
1776
|
+
const appDistExists = fs4.existsSync(cfg.appDist);
|
|
1768
1777
|
setInterval(() => {
|
|
1769
1778
|
const now = Date.now();
|
|
1770
1779
|
for (const [id, client] of pollingClients) {
|
|
@@ -1778,7 +1787,7 @@ async function startServer() {
|
|
|
1778
1787
|
}, 3e4);
|
|
1779
1788
|
const wss = new WebSocketServer({ server });
|
|
1780
1789
|
wss.on("connection", (ws, req) => {
|
|
1781
|
-
const url = new URL(req.url || "/", `http://localhost:${
|
|
1790
|
+
const url = new URL(req.url || "/", `http://localhost:${cfg.port}`);
|
|
1782
1791
|
const sessionId = url.searchParams.get("sessionId");
|
|
1783
1792
|
if (!sessionId || !getSession(sessionId)) {
|
|
1784
1793
|
ws.close(4001, "Invalid session");
|
|
@@ -1791,7 +1800,7 @@ async function startServer() {
|
|
|
1791
1800
|
const clients = getSessionClients(sessionId);
|
|
1792
1801
|
clients.add(ws);
|
|
1793
1802
|
console.log(`\u{1F50C} WS client connected to session ${sessionId} (${clients.size} total)`);
|
|
1794
|
-
sendStateTo(sessionId, ws);
|
|
1803
|
+
sendStateTo(cfg, sessionId, ws);
|
|
1795
1804
|
ws.on("message", async (raw) => {
|
|
1796
1805
|
try {
|
|
1797
1806
|
const msg = JSON.parse(raw.toString());
|
|
@@ -1805,20 +1814,20 @@ async function startServer() {
|
|
|
1805
1814
|
});
|
|
1806
1815
|
});
|
|
1807
1816
|
const HOST = process.env.HOST ?? "0.0.0.0";
|
|
1808
|
-
previewServer.listen(
|
|
1809
|
-
console.log(`\u{1F441} Preview proxy: http://${HOST}:${
|
|
1817
|
+
previewServer.listen(cfg.previewPort, HOST, () => {
|
|
1818
|
+
console.log(`\u{1F441} Preview proxy: http://${HOST}:${cfg.previewPort}`);
|
|
1810
1819
|
});
|
|
1811
|
-
server.listen(
|
|
1812
|
-
console.log(`\u{1F310} http://${HOST}:${
|
|
1813
|
-
console.log(`\u{1F916} Provider: ${
|
|
1814
|
-
console.log(`\u{1F5A5} Admin: http://${HOST}:${
|
|
1820
|
+
server.listen(cfg.port, HOST, () => {
|
|
1821
|
+
console.log(`\u{1F310} http://${HOST}:${cfg.port}`);
|
|
1822
|
+
console.log(`\u{1F916} Provider: ${cfg.provider} / ${cfg.model}`);
|
|
1823
|
+
console.log(`\u{1F5A5} Admin: http://${HOST}:${cfg.port}/admin`);
|
|
1815
1824
|
if (appDistExists) {
|
|
1816
|
-
console.log(`\u{1F4F1} App: http://${HOST}:${
|
|
1825
|
+
console.log(`\u{1F4F1} App: http://${HOST}:${cfg.port}`);
|
|
1817
1826
|
} else {
|
|
1818
|
-
console.log(`\u26A0 App dist not found at ${
|
|
1827
|
+
console.log(`\u26A0 App dist not found at ${cfg.appDist} \u2014 run 'pnpm --filter @any-code/app build' first`);
|
|
1819
1828
|
}
|
|
1820
1829
|
console.log(`\u{1F4CB} Sessions: POST /api/sessions to create`);
|
|
1821
|
-
console.log(`\u{1F50C} WebSocket: ws://${HOST}:${
|
|
1830
|
+
console.log(`\u{1F50C} WebSocket: ws://${HOST}:${cfg.port}?sessionId=xxx`);
|
|
1822
1831
|
});
|
|
1823
1832
|
}
|
|
1824
1833
|
|