offsite 0.0.45 → 0.0.47
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/README.md +1 -10
- package/apps/server/dist/main.mjs +665 -238
- package/apps/server/dist/main.mjs.map +1 -1
- package/apps/web/dist/assets/abap-08VXUWAP.js +1 -0
- package/apps/web/dist/assets/accounts-CccrDGY4.js +512 -0
- package/apps/web/dist/assets/apex-BWPQTe0t.js +1 -0
- package/apps/web/dist/assets/audio-player-BE-oXEaT.js +1 -0
- package/apps/web/dist/assets/azcli-Bc_sGQ0U.js +1 -0
- package/apps/web/dist/assets/bat-i0X4ZdIN.js +1 -0
- package/apps/web/dist/assets/bicep-B5-_aFwp.js +2 -0
- package/apps/web/dist/assets/button-Dhs1rqob.js +1 -0
- package/apps/web/dist/assets/cameligo-DMUM7wLl.js +1 -0
- package/apps/web/dist/assets/clojure-Cm7r79vr.js +1 -0
- package/apps/web/dist/assets/codicon-DF1abBS2.ttf +0 -0
- package/apps/web/dist/assets/coffee-Ba7i2nA0.js +1 -0
- package/apps/web/dist/assets/cpp-C7h46wYY.js +1 -0
- package/apps/web/dist/assets/csharp-BKxtCVv1.js +1 -0
- package/apps/web/dist/assets/csp-bTuwJoIa.js +1 -0
- package/apps/web/dist/assets/css-DIMkf-bt.js +3 -0
- package/apps/web/dist/assets/cypher-CVaqCwHa.js +1 -0
- package/apps/web/dist/assets/dart-onAF5SnQ.js +1 -0
- package/apps/web/dist/assets/{preview-dialog-jk_R2034.js → dialog-Bnoy9Qj1.js} +12 -12
- package/apps/web/dist/assets/dist-BrDshb6Z.js +153 -0
- package/apps/web/dist/assets/dist-CFVYlZbS.js +1 -0
- package/apps/web/dist/assets/dockerfile-DZFCIeNp.js +1 -0
- package/apps/web/dist/assets/ecl-D05T4iGw.js +1 -0
- package/apps/web/dist/assets/editor-DjpsE8to.css +1 -0
- package/apps/web/dist/assets/editor-dialog-BNFI__d8.css +1 -0
- package/apps/web/dist/assets/editor-dialog-OElejzVH.js +52 -0
- package/apps/web/dist/assets/editor.api-4LH2ijIL.js +908 -0
- package/apps/web/dist/assets/editor.worker-BmdWdrHZ.js +33 -0
- package/apps/web/dist/assets/editor.worker-D2ouzCu8.js +1 -0
- package/apps/web/dist/assets/elixir-6RTg0lbw.js +1 -0
- package/apps/web/dist/assets/flow9-C5_-GSwl.js +1 -0
- package/apps/web/dist/assets/freemarker2-6NYS9HqW.js +3 -0
- package/apps/web/dist/assets/fsharp-C8Ef5oNN.js +1 -0
- package/apps/web/dist/assets/go-C-y9NEjX.js +1 -0
- package/apps/web/dist/assets/graphql-fmXr3nnJ.js +1 -0
- package/apps/web/dist/assets/handlebars-DOeNnhTE.js +1 -0
- package/apps/web/dist/assets/hcl-CpzslTdj.js +1 -0
- package/apps/web/dist/assets/html-BwGTVsqZ.js +1 -0
- package/apps/web/dist/assets/image-viewer-CwKW6KbM.js +1 -0
- package/apps/web/dist/assets/index-B57iKlGT.js +9 -0
- package/apps/web/dist/assets/index-pX3wrk_t.css +2 -0
- package/apps/web/dist/assets/ini-sBoK_t0W.js +1 -0
- package/apps/web/dist/assets/java-BEtHBSE6.js +1 -0
- package/apps/web/dist/assets/javascript-Dj-Vzbp-.js +1 -0
- package/apps/web/dist/assets/julia-Bri6UV-V.js +1 -0
- package/apps/web/dist/assets/kotlin-BOotOW0E.js +1 -0
- package/apps/web/dist/assets/less-B9JPFI3C.js +2 -0
- package/apps/web/dist/assets/lexon-CfSJPG6W.js +1 -0
- package/apps/web/dist/assets/liquid-DDSU3ltH.js +1 -0
- package/apps/web/dist/assets/lua-CsQS60Ue.js +1 -0
- package/apps/web/dist/assets/m3-D-oSqn_W.js +1 -0
- package/apps/web/dist/assets/markdown-Cimd5fb3.js +1 -0
- package/apps/web/dist/assets/mdx-YllfWysm.js +1 -0
- package/apps/web/dist/assets/mips-CIPQ_RoX.js +1 -0
- package/apps/web/dist/assets/msdax-DauUninz.js +1 -0
- package/apps/web/dist/assets/mysql-SOo6toE5.js +1 -0
- package/apps/web/dist/assets/objective-c-FvmIjYaQ.js +1 -0
- package/apps/web/dist/assets/pascal-DrH0SRf2.js +1 -0
- package/apps/web/dist/assets/pascaligo-D-ptJ9y-.js +1 -0
- package/apps/web/dist/assets/perl-oz_6vUea.js +1 -0
- package/apps/web/dist/assets/pgsql-DTj74zXo.js +1 -0
- package/apps/web/dist/assets/php-nr791fC2.js +1 -0
- package/apps/web/dist/assets/pla-CopQ2nXW.js +1 -0
- package/apps/web/dist/assets/postiats-43DmfD33.js +1 -0
- package/apps/web/dist/assets/powerquery-D3hlyOfw.js +1 -0
- package/apps/web/dist/assets/powershell-DmHpPYUd.js +1 -0
- package/apps/web/dist/assets/preview-dialog-dl8oUCEx.js +1 -0
- package/apps/web/dist/assets/protobuf-C531GsRP.js +2 -0
- package/apps/web/dist/assets/pug-Z5eAx3Zn.js +1 -0
- package/apps/web/dist/assets/python-Bsfjs4Fs.js +1 -0
- package/apps/web/dist/assets/qsharp-DkqhCAOL.js +1 -0
- package/apps/web/dist/assets/r-BwWrilGY.js +1 -0
- package/apps/web/dist/assets/razor-3MUz77_n.js +1 -0
- package/apps/web/dist/assets/{react-99wk3OSj.js → react-DHkIXOR_.js} +1 -1
- package/apps/web/dist/assets/redis-ClamHrr6.js +1 -0
- package/apps/web/dist/assets/redshift-DT7zqm-g.js +1 -0
- package/apps/web/dist/assets/restructuredtext-BYgofb2h.js +1 -0
- package/apps/web/dist/assets/ruby-DezsRK8O.js +1 -0
- package/apps/web/dist/assets/rust-DdL9SqIa.js +1 -0
- package/apps/web/dist/assets/sb-CcwsVR0C.js +1 -0
- package/apps/web/dist/assets/scala-DHpiXF5c.js +1 -0
- package/apps/web/dist/assets/scheme-BeGwcela.js +1 -0
- package/apps/web/dist/assets/scss-gp-XZpBa.js +3 -0
- package/apps/web/dist/assets/shell-CC2rA5mh.js +1 -0
- package/apps/web/dist/assets/solidity-BEEn4gHE.js +1 -0
- package/apps/web/dist/assets/sophia-CRfGWb83.js +1 -0
- package/apps/web/dist/assets/sparql-D_Lu-MrJ.js +1 -0
- package/apps/web/dist/assets/sql-NEE52Syq.js +1 -0
- package/apps/web/dist/assets/st-DbInun42.js +1 -0
- package/apps/web/dist/assets/swift-Bxkupp3x.js +1 -0
- package/apps/web/dist/assets/systemverilog-Bz4Y3fRF.js +1 -0
- package/apps/web/dist/assets/tcl-DISqw1ZD.js +1 -0
- package/apps/web/dist/assets/tooltip-CGSEdyeY.js +1 -0
- package/apps/web/dist/assets/twig-De2hgUGE.js +1 -0
- package/apps/web/dist/assets/typescript-CfCJo9p3.js +1 -0
- package/apps/web/dist/assets/typescript-fNZ3iur6.js +1 -0
- package/apps/web/dist/assets/typespec-B8J7ngcE.js +1 -0
- package/apps/web/dist/assets/utils-KVq36YOZ.js +1 -0
- package/apps/web/dist/assets/vb-DV3o63ZY.js +1 -0
- package/apps/web/dist/assets/video-viewer-DIbmaY7P.js +1 -0
- package/apps/web/dist/assets/wgsl-DpFanUEy.js +298 -0
- package/apps/web/dist/assets/xml-S-oLW5B5.js +1 -0
- package/apps/web/dist/assets/yaml-DlmqY7sN.js +1 -0
- package/apps/web/dist/assets/zoomable-image-Dpslbx28.js +1 -0
- package/apps/web/dist/assets/zoomable-image-oNBiTN5V.js +41 -0
- package/apps/web/dist/index.html +8 -8
- package/apps/web/dist/sw.js +1 -1
- package/package.json +2 -1
- package/apps/web/dist/assets/audio-player-BBuXiV2_.js +0 -1
- package/apps/web/dist/assets/button-BF3wjXXJ.js +0 -1
- package/apps/web/dist/assets/dist-3jCLwLR5.js +0 -1
- package/apps/web/dist/assets/dist-CkDQKgh1.js +0 -153
- package/apps/web/dist/assets/image-viewer-4LhMaTO_.js +0 -1
- package/apps/web/dist/assets/index-CDkqSE9K.js +0 -782
- package/apps/web/dist/assets/index-D3NpzVu2.css +0 -2
- package/apps/web/dist/assets/jsx-runtime-DKj5X9O4.js +0 -1
- package/apps/web/dist/assets/tooltip-CiWlkHa6.js +0 -1
- package/apps/web/dist/assets/utils-PtDt7m8t.js +0 -1
- package/apps/web/dist/assets/video-player-Bcow2OC5.js +0 -1
- package/apps/web/dist/assets/video-player-hDoAwJRk.js +0 -1
- package/apps/web/dist/assets/video-viewer-CJ129jI3.js +0 -1
- package/apps/web/dist/assets/zoomable-image-CZlWBm_z.js +0 -41
- package/apps/web/dist/assets/zoomable-image-DyuZJmov.js +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
2
|
-
import { chmodSync, closeSync, createReadStream, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, watch, writeFileSync } from "node:fs";
|
|
2
|
+
import { appendFileSync, chmodSync, closeSync, createReadStream, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, watch, writeFileSync } from "node:fs";
|
|
3
3
|
import http, { createServer } from "node:http";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { consola } from "consola";
|
|
@@ -21,6 +21,7 @@ import { DatabaseSync } from "node:sqlite";
|
|
|
21
21
|
import { drizzle } from "drizzle-orm/node-sqlite";
|
|
22
22
|
import { migrate } from "drizzle-orm/node-sqlite/migrator";
|
|
23
23
|
import { integer, snakeCase, text } from "drizzle-orm/sqlite-core";
|
|
24
|
+
import sharp from "sharp";
|
|
24
25
|
import { promisify } from "node:util";
|
|
25
26
|
import webPush from "web-push";
|
|
26
27
|
import { spawn as spawn$1 } from "@lydell/node-pty";
|
|
@@ -47,6 +48,24 @@ const mediaTypes = /* @__PURE__ */ new Map([
|
|
|
47
48
|
[".flac", "audio/flac"]
|
|
48
49
|
]);
|
|
49
50
|
//#endregion
|
|
51
|
+
//#region ../core/src/terminal.ts
|
|
52
|
+
const GLOBAL_TERMINALS = "global";
|
|
53
|
+
function encodeFrame(kind, id, data) {
|
|
54
|
+
const frame = new Uint8Array(5 + data.length);
|
|
55
|
+
frame[0] = kind;
|
|
56
|
+
new DataView(frame.buffer).setUint32(1, id);
|
|
57
|
+
frame.set(data, 5);
|
|
58
|
+
return frame;
|
|
59
|
+
}
|
|
60
|
+
function decodeFrame(frame) {
|
|
61
|
+
if (frame.length < 5) throw new Error("terminal frame is too short");
|
|
62
|
+
return {
|
|
63
|
+
kind: frame[0],
|
|
64
|
+
id: new DataView(frame.buffer, frame.byteOffset, frame.byteLength).getUint32(1),
|
|
65
|
+
data: frame.subarray(5)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
50
69
|
//#region src/constants.ts
|
|
51
70
|
const deprecatedModels = {
|
|
52
71
|
claude: [
|
|
@@ -171,6 +190,43 @@ function readJson(name) {
|
|
|
171
190
|
throw error;
|
|
172
191
|
}
|
|
173
192
|
}
|
|
193
|
+
function readSettings(name, schema, defaults) {
|
|
194
|
+
const file = path.join(home, name);
|
|
195
|
+
let value;
|
|
196
|
+
try {
|
|
197
|
+
value = readJson(name);
|
|
198
|
+
} catch (error) {
|
|
199
|
+
console.warn(`invalid ${file}, using the defaults: ${error}`);
|
|
200
|
+
return structuredClone(defaults);
|
|
201
|
+
}
|
|
202
|
+
if (value === null) return structuredClone(defaults);
|
|
203
|
+
let result = schema.safeParse(value);
|
|
204
|
+
if (result.success) return result.data;
|
|
205
|
+
while (!result.success) {
|
|
206
|
+
const issue = result.error.issues[0];
|
|
207
|
+
const targets = issue.code === "unrecognized_keys" ? issue.keys.map((key) => [...issue.path, key]) : [issue.path];
|
|
208
|
+
for (const target of targets) {
|
|
209
|
+
const discarded = discard(value, target);
|
|
210
|
+
if (discarded === null) {
|
|
211
|
+
console.warn(`invalid ${file}, using the defaults:\n${z.prettifyError(result.error)}`);
|
|
212
|
+
return structuredClone(defaults);
|
|
213
|
+
}
|
|
214
|
+
console.warn(`${file}: discarded ${discarded.map(String).join(".")} (${issue.message} at ${target.map(String).join(".")})`);
|
|
215
|
+
}
|
|
216
|
+
result = schema.safeParse(value);
|
|
217
|
+
}
|
|
218
|
+
writeJson(name, result.data);
|
|
219
|
+
return result.data;
|
|
220
|
+
}
|
|
221
|
+
function discard(value, target) {
|
|
222
|
+
const key = target.at(0);
|
|
223
|
+
if (key === void 0 || typeof value !== "object" || value === null || !Object.hasOwn(value, key)) return null;
|
|
224
|
+
const inner = target.length > 1 ? discard(Reflect.get(value, key), target.slice(1)) : null;
|
|
225
|
+
if (inner !== null) return [key, ...inner];
|
|
226
|
+
if (Array.isArray(value)) value.splice(Number(key), 1);
|
|
227
|
+
else Reflect.deleteProperty(value, key);
|
|
228
|
+
return [key];
|
|
229
|
+
}
|
|
174
230
|
function writeJson(name, value) {
|
|
175
231
|
let file = path.join(home, name);
|
|
176
232
|
mkdirSync(home, { recursive: true });
|
|
@@ -880,7 +936,6 @@ function record$1(value) {
|
|
|
880
936
|
}
|
|
881
937
|
//#endregion
|
|
882
938
|
//#region src/storage/config.ts
|
|
883
|
-
const configFile = path.join(home, "config.json");
|
|
884
939
|
const modelSetting = z.strictObject({
|
|
885
940
|
hidden: z.boolean(),
|
|
886
941
|
effort: z.string().nullable()
|
|
@@ -901,8 +956,7 @@ function accountSchema(provider) {
|
|
|
901
956
|
name: z.string(),
|
|
902
957
|
color: z.string().regex(/^#[0-9a-f]{6}$/i, "must be a hex color like #3b82f6").nullable().default(null),
|
|
903
958
|
configDir: platformDirs,
|
|
904
|
-
enabled: z.boolean().default(true)
|
|
905
|
-
hiddenModels: z.record(z.string(), z.boolean()).default({})
|
|
959
|
+
enabled: z.boolean().default(true)
|
|
906
960
|
});
|
|
907
961
|
}
|
|
908
962
|
const configSchema = z.strictObject({
|
|
@@ -919,6 +973,19 @@ const configSchema = z.strictObject({
|
|
|
919
973
|
codex: z.boolean().default(true),
|
|
920
974
|
opencode: z.boolean().default(true)
|
|
921
975
|
}).prefault({}),
|
|
976
|
+
newThreads: z.strictObject({
|
|
977
|
+
provider: z.enum([
|
|
978
|
+
"claude",
|
|
979
|
+
"codex",
|
|
980
|
+
"opencode"
|
|
981
|
+
]).default("claude"),
|
|
982
|
+
accountId: z.string().nullable().default(null),
|
|
983
|
+
models: z.strictObject({
|
|
984
|
+
claude: z.string().nullable().default(null),
|
|
985
|
+
codex: z.string().nullable().default(null),
|
|
986
|
+
opencode: z.string().nullable().default(null)
|
|
987
|
+
}).prefault({})
|
|
988
|
+
}).prefault({}),
|
|
922
989
|
commitMessage: z.strictObject({
|
|
923
990
|
provider: z.enum([
|
|
924
991
|
"claude",
|
|
@@ -981,20 +1048,7 @@ function configInfo() {
|
|
|
981
1048
|
};
|
|
982
1049
|
}
|
|
983
1050
|
function readConfig() {
|
|
984
|
-
|
|
985
|
-
try {
|
|
986
|
-
user = readJson("config.json");
|
|
987
|
-
} catch (error) {
|
|
988
|
-
console.warn(`invalid ${configFile}, using the default config: ${error}`);
|
|
989
|
-
return structuredClone(defaults$1);
|
|
990
|
-
}
|
|
991
|
-
if (user === null) return structuredClone(defaults$1);
|
|
992
|
-
const result = configSchema.safeParse(user);
|
|
993
|
-
if (!result.success) {
|
|
994
|
-
console.warn(`invalid ${configFile}, using the default config:\n${z.prettifyError(result.error)}`);
|
|
995
|
-
return structuredClone(defaults$1);
|
|
996
|
-
}
|
|
997
|
-
return result.data;
|
|
1051
|
+
return readSettings("config.json", configSchema, defaults$1);
|
|
998
1052
|
}
|
|
999
1053
|
function saveConfig(value) {
|
|
1000
1054
|
const result = configSchema.safeParse(value);
|
|
@@ -2564,6 +2618,7 @@ const sessions$1 = snakeCase.table("sessions", {
|
|
|
2564
2618
|
status: text({ enum: [
|
|
2565
2619
|
"active",
|
|
2566
2620
|
"pinned",
|
|
2621
|
+
"snoozed",
|
|
2567
2622
|
"minimized"
|
|
2568
2623
|
] }).notNull()
|
|
2569
2624
|
});
|
|
@@ -2589,13 +2644,201 @@ const pushSubscriptions = snakeCase.table("push_subscriptions", {
|
|
|
2589
2644
|
server: text().notNull()
|
|
2590
2645
|
});
|
|
2591
2646
|
//#endregion
|
|
2647
|
+
//#region src/services/files.ts
|
|
2648
|
+
const FILE_LIMIT = 2e4;
|
|
2649
|
+
const OUTPUT_LIMIT$1 = 262144;
|
|
2650
|
+
const READ_LIMIT = 1048576;
|
|
2651
|
+
const MEDIA_LINK_LIFETIME = 36e5;
|
|
2652
|
+
const browseToken = randomBytes(32).toString("base64url");
|
|
2653
|
+
const mediaLinks = /* @__PURE__ */ new Map();
|
|
2654
|
+
const skipped = /* @__PURE__ */ new Set([
|
|
2655
|
+
"node_modules",
|
|
2656
|
+
".git",
|
|
2657
|
+
".DS_Store",
|
|
2658
|
+
"dist",
|
|
2659
|
+
"target",
|
|
2660
|
+
".next",
|
|
2661
|
+
"__pycache__",
|
|
2662
|
+
".venv"
|
|
2663
|
+
]);
|
|
2664
|
+
async function listFiles(cwd) {
|
|
2665
|
+
return (await listEntries(cwd)).files;
|
|
2666
|
+
}
|
|
2667
|
+
async function listEntries(cwd) {
|
|
2668
|
+
const files = [];
|
|
2669
|
+
const folders = [];
|
|
2670
|
+
const pending = [""];
|
|
2671
|
+
while (pending.length > 0 && files.length < FILE_LIMIT) {
|
|
2672
|
+
const dir = pending.pop() ?? "";
|
|
2673
|
+
let entries;
|
|
2674
|
+
try {
|
|
2675
|
+
entries = await readdir(path.join(cwd, dir), { withFileTypes: true });
|
|
2676
|
+
} catch {
|
|
2677
|
+
continue;
|
|
2678
|
+
}
|
|
2679
|
+
for (const entry of entries) {
|
|
2680
|
+
if (skipped.has(entry.name)) continue;
|
|
2681
|
+
const relative = dir === "" ? entry.name : `${dir}/${entry.name}`;
|
|
2682
|
+
if (entry.isDirectory()) {
|
|
2683
|
+
pending.push(relative);
|
|
2684
|
+
folders.push(relative);
|
|
2685
|
+
}
|
|
2686
|
+
if (entry.isFile()) files.push(relative);
|
|
2687
|
+
if (entry.isSymbolicLink()) {
|
|
2688
|
+
if ((await stat(path.join(cwd, relative)).catch(() => null))?.isFile() === true) files.push(relative);
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
return {
|
|
2693
|
+
files: files.slice(0, FILE_LIMIT).sort(),
|
|
2694
|
+
folders: folders.sort()
|
|
2695
|
+
};
|
|
2696
|
+
}
|
|
2697
|
+
function readProjectFile(cwd, file) {
|
|
2698
|
+
const full = resolveProjectFile(cwd, file);
|
|
2699
|
+
const stats = statSync(full);
|
|
2700
|
+
if (!stats.isFile()) throw new Error(`${file} is not a file`);
|
|
2701
|
+
if (stats.size > READ_LIMIT) return {
|
|
2702
|
+
kind: "large",
|
|
2703
|
+
size: stats.size,
|
|
2704
|
+
limit: READ_LIMIT
|
|
2705
|
+
};
|
|
2706
|
+
const data = readFileSync(full);
|
|
2707
|
+
const mediaType = mediaTypes.get(path.extname(full).toLowerCase());
|
|
2708
|
+
if (mediaType?.startsWith("image/") === true) return {
|
|
2709
|
+
kind: "image",
|
|
2710
|
+
size: stats.size,
|
|
2711
|
+
mediaType,
|
|
2712
|
+
data: data.toString("base64")
|
|
2713
|
+
};
|
|
2714
|
+
if (data.includes(0)) return {
|
|
2715
|
+
kind: "binary",
|
|
2716
|
+
size: stats.size
|
|
2717
|
+
};
|
|
2718
|
+
return {
|
|
2719
|
+
kind: "text",
|
|
2720
|
+
size: stats.size,
|
|
2721
|
+
content: data.toString("utf8")
|
|
2722
|
+
};
|
|
2723
|
+
}
|
|
2724
|
+
function writeProjectFile(cwd, file, content) {
|
|
2725
|
+
const full = resolveProjectFile(cwd, file);
|
|
2726
|
+
if (!statSync(full).isFile()) throw new Error(`${file} is not a file`);
|
|
2727
|
+
writeFileSync(full, content);
|
|
2728
|
+
}
|
|
2729
|
+
function createProjectEntry(cwd, file, folder) {
|
|
2730
|
+
const full = resolveNewProjectPath(cwd, file);
|
|
2731
|
+
if (folder) {
|
|
2732
|
+
mkdirSync(full, { recursive: true });
|
|
2733
|
+
return;
|
|
2734
|
+
}
|
|
2735
|
+
mkdirSync(path.dirname(full), { recursive: true });
|
|
2736
|
+
writeFileSync(full, "", { flag: "wx" });
|
|
2737
|
+
}
|
|
2738
|
+
function renameProjectEntry(cwd, file, to) {
|
|
2739
|
+
const source = resolveProjectEntry(cwd, file);
|
|
2740
|
+
const target = resolveNewProjectPath(cwd, to);
|
|
2741
|
+
mkdirSync(path.dirname(target), { recursive: true });
|
|
2742
|
+
renameSync(source, target);
|
|
2743
|
+
}
|
|
2744
|
+
function deleteProjectEntry(cwd, file) {
|
|
2745
|
+
rmSync(resolveProjectEntry(cwd, file), { recursive: true });
|
|
2746
|
+
}
|
|
2747
|
+
function linkProjectMedia(cwd, file) {
|
|
2748
|
+
const resolved = path.resolve(realpathSync(cwd), file);
|
|
2749
|
+
if (!existsSync(resolved)) throw new Error(`${file} does not exist`);
|
|
2750
|
+
const full = realpathSync(resolved);
|
|
2751
|
+
const mediaType = mediaTypes.get(path.extname(full).toLowerCase());
|
|
2752
|
+
if (mediaType === void 0) throw new Error(`${file} is not an image, video or audio file`);
|
|
2753
|
+
return addLink(full, mediaType);
|
|
2754
|
+
}
|
|
2755
|
+
function addLink(file, mediaType) {
|
|
2756
|
+
for (const [token, link] of mediaLinks) if (link.expiresAt <= Date.now()) mediaLinks.delete(token);
|
|
2757
|
+
const token = randomBytes(32).toString("base64url");
|
|
2758
|
+
mediaLinks.set(token, {
|
|
2759
|
+
file,
|
|
2760
|
+
mediaType,
|
|
2761
|
+
expiresAt: Date.now() + MEDIA_LINK_LIFETIME
|
|
2762
|
+
});
|
|
2763
|
+
return token;
|
|
2764
|
+
}
|
|
2765
|
+
function findMediaLink(token) {
|
|
2766
|
+
if (token === null) return null;
|
|
2767
|
+
const link = mediaLinks.get(token);
|
|
2768
|
+
if (link === void 0 || link.expiresAt <= Date.now()) return null;
|
|
2769
|
+
return link;
|
|
2770
|
+
}
|
|
2771
|
+
function saveUpload(name, data) {
|
|
2772
|
+
const dir = path.join(home, "uploads", randomUUID());
|
|
2773
|
+
mkdirSync(dir, { recursive: true });
|
|
2774
|
+
const file = path.join(dir, path.basename(name));
|
|
2775
|
+
writeFileSync(file, data);
|
|
2776
|
+
return file;
|
|
2777
|
+
}
|
|
2778
|
+
async function taskOutput(sessionId, file) {
|
|
2779
|
+
const tasks = path.dirname(file);
|
|
2780
|
+
if (!/^[\w-]+$/.test(sessionId) || !path.isAbsolute(file) || path.normalize(file) !== file || !/^\w+\.output$/.test(path.basename(file)) || path.basename(tasks) !== "tasks" || path.basename(path.dirname(tasks)) !== sessionId) throw new Error("Not a background task output file of this thread");
|
|
2781
|
+
let stats;
|
|
2782
|
+
try {
|
|
2783
|
+
stats = await lstat(file);
|
|
2784
|
+
} catch (error) {
|
|
2785
|
+
if (hasCode(error, "ENOENT")) return null;
|
|
2786
|
+
throw error;
|
|
2787
|
+
}
|
|
2788
|
+
if (!stats.isFile()) throw new Error("Background task output is not a regular file");
|
|
2789
|
+
const start = Math.max(0, stats.size - OUTPUT_LIMIT$1);
|
|
2790
|
+
const handle = await open(file, "r");
|
|
2791
|
+
try {
|
|
2792
|
+
const buffer = Buffer.alloc(stats.size - start);
|
|
2793
|
+
const read = await handle.read(buffer, 0, buffer.length, start);
|
|
2794
|
+
return {
|
|
2795
|
+
text: buffer.subarray(0, read.bytesRead).toString("utf8"),
|
|
2796
|
+
truncated: start > 0
|
|
2797
|
+
};
|
|
2798
|
+
} finally {
|
|
2799
|
+
await handle.close();
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
function resolveProjectFile(cwd, file) {
|
|
2803
|
+
const root = realpathSync(cwd);
|
|
2804
|
+
const resolved = path.resolve(root, file);
|
|
2805
|
+
if (!existsSync(resolved)) throw new Error(`${file} does not exist`);
|
|
2806
|
+
const full = realpathSync(resolved);
|
|
2807
|
+
checkInProject(root, full, file);
|
|
2808
|
+
return full;
|
|
2809
|
+
}
|
|
2810
|
+
function resolveProjectEntry(cwd, file) {
|
|
2811
|
+
const root = realpathSync(cwd);
|
|
2812
|
+
const resolved = path.resolve(root, file);
|
|
2813
|
+
if (!existsSync(path.dirname(resolved))) throw new Error(`${file} does not exist`);
|
|
2814
|
+
const full = path.join(realpathSync(path.dirname(resolved)), path.basename(resolved));
|
|
2815
|
+
if (full === root) throw new Error("The project folder itself cannot be changed");
|
|
2816
|
+
checkInProject(root, full, file);
|
|
2817
|
+
lstatSync(full);
|
|
2818
|
+
return full;
|
|
2819
|
+
}
|
|
2820
|
+
function resolveNewProjectPath(cwd, file) {
|
|
2821
|
+
const root = realpathSync(cwd);
|
|
2822
|
+
const resolved = path.resolve(root, file);
|
|
2823
|
+
if (existsSync(resolved)) throw new Error(`${file} already exists`);
|
|
2824
|
+
let existing = path.dirname(resolved);
|
|
2825
|
+
while (!existsSync(existing)) existing = path.dirname(existing);
|
|
2826
|
+
const full = path.join(realpathSync(existing), path.relative(existing, resolved));
|
|
2827
|
+
checkInProject(root, full, file);
|
|
2828
|
+
return full;
|
|
2829
|
+
}
|
|
2830
|
+
function checkInProject(root, full, file) {
|
|
2831
|
+
const relative = path.relative(root, full);
|
|
2832
|
+
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) throw new Error(`${file} is outside the project`);
|
|
2833
|
+
}
|
|
2834
|
+
//#endregion
|
|
2592
2835
|
//#region src/services/git.ts
|
|
2593
2836
|
const DIFF_LIMIT = 8e4;
|
|
2594
2837
|
const UNTRACKED_LIMIT = 100;
|
|
2595
2838
|
const NEW_FILE_LIMIT = 262144;
|
|
2596
2839
|
const REPO_DEPTH = 3;
|
|
2597
2840
|
const FETCH_TIMEOUT = 6e4;
|
|
2598
|
-
const run$
|
|
2841
|
+
const run$2 = promisify(execFile);
|
|
2599
2842
|
const PULL_REQUEST_INSTRUCTIONS = `You write GitHub pull requests as Markdown.
|
|
2600
2843
|
|
|
2601
2844
|
## TITLE RULES
|
|
@@ -2631,7 +2874,7 @@ async function gitRepos(cwd) {
|
|
|
2631
2874
|
})));
|
|
2632
2875
|
}
|
|
2633
2876
|
async function commit(cwd, message, andPush) {
|
|
2634
|
-
const text = message.trim() === "" ? await commitMessage(cwd) : message;
|
|
2877
|
+
const text = message.trim() === "" ? await commitMessage(cwd, false) : message;
|
|
2635
2878
|
await git(cwd, ["add", "--all"]);
|
|
2636
2879
|
await git(cwd, [
|
|
2637
2880
|
"commit",
|
|
@@ -2682,7 +2925,7 @@ async function sync(cwd) {
|
|
|
2682
2925
|
async function fetchProject(cwd) {
|
|
2683
2926
|
await Promise.all((await projectRepos(cwd)).map(async (repo) => {
|
|
2684
2927
|
try {
|
|
2685
|
-
await run$
|
|
2928
|
+
await run$2("git", ["fetch"], {
|
|
2686
2929
|
cwd: path.join(cwd, repo),
|
|
2687
2930
|
env: {
|
|
2688
2931
|
...process.env,
|
|
@@ -2713,7 +2956,7 @@ async function switchBranch(cwd, branch, create) {
|
|
|
2713
2956
|
async function pullRequestBranches(cwd) {
|
|
2714
2957
|
const head = (await gitStatus(cwd)).branch;
|
|
2715
2958
|
if (head === null) throw new Error("Not on a branch");
|
|
2716
|
-
const base = (await run$
|
|
2959
|
+
const base = (await run$2("gh", [
|
|
2717
2960
|
"repo",
|
|
2718
2961
|
"view",
|
|
2719
2962
|
"--json",
|
|
@@ -2738,7 +2981,7 @@ async function preparePullRequest(cwd, base, commitChanges) {
|
|
|
2738
2981
|
if (commitChanges) await commit(cwd, "", true);
|
|
2739
2982
|
const status = await gitStatus(cwd);
|
|
2740
2983
|
if (status.upstream === null || status.ahead > 0) await push$1(cwd);
|
|
2741
|
-
const open = (await run$
|
|
2984
|
+
const open = (await run$2("gh", [
|
|
2742
2985
|
"pr",
|
|
2743
2986
|
"list",
|
|
2744
2987
|
"--head",
|
|
@@ -2785,7 +3028,7 @@ async function preparePullRequest(cwd, base, commitChanges) {
|
|
|
2785
3028
|
};
|
|
2786
3029
|
}
|
|
2787
3030
|
async function createPullRequest(cwd, request) {
|
|
2788
|
-
const url = (await run$
|
|
3031
|
+
const url = (await run$2("gh", [
|
|
2789
3032
|
"pr",
|
|
2790
3033
|
"create",
|
|
2791
3034
|
"--title",
|
|
@@ -2802,8 +3045,13 @@ async function createPullRequest(cwd, request) {
|
|
|
2802
3045
|
if (url === void 0) throw new Error("gh did not return a pull request URL");
|
|
2803
3046
|
return { url };
|
|
2804
3047
|
}
|
|
2805
|
-
async function commitMessage(cwd) {
|
|
2806
|
-
const diff = (await
|
|
3048
|
+
async function commitMessage(cwd, staged) {
|
|
3049
|
+
const diff = (staged ? (await git(cwd, [
|
|
3050
|
+
"diff",
|
|
3051
|
+
"--cached",
|
|
3052
|
+
"--no-color",
|
|
3053
|
+
"--no-ext-diff"
|
|
3054
|
+
])).stdout : await repoDiff(cwd, "")).slice(0, DIFF_LIMIT);
|
|
2807
3055
|
if (diff === "") throw new Error("There is nothing to commit");
|
|
2808
3056
|
return generateText(cwd, "You write concise git commit messages.", `Write a git commit message for this diff. Reply with only the message: a short summary line, optionally followed by a blank line and a brief body.\n\n${diff}`);
|
|
2809
3057
|
}
|
|
@@ -2841,7 +3089,7 @@ async function projectDiff(cwd) {
|
|
|
2841
3089
|
return (await Promise.all(repos.map((repo) => repoDiff(path.join(cwd, repo), repo === "" ? "" : `${repo}/`)))).join("");
|
|
2842
3090
|
}
|
|
2843
3091
|
function git(cwd, args) {
|
|
2844
|
-
return run$
|
|
3092
|
+
return run$2("git", args, {
|
|
2845
3093
|
cwd,
|
|
2846
3094
|
maxBuffer: 67108864
|
|
2847
3095
|
});
|
|
@@ -2966,13 +3214,27 @@ function newFilePatch(cwd, file, prefix) {
|
|
|
2966
3214
|
if (lines.at(-1) === "") lines.pop();
|
|
2967
3215
|
return `${header}@@ -0,0 +1,${lines.length} @@\n${lines.map((line) => `+${line}\n`).join("")}`;
|
|
2968
3216
|
}
|
|
3217
|
+
//#endregion
|
|
3218
|
+
//#region src/services/logos.ts
|
|
3219
|
+
const LOGO_LIMIT = 524288;
|
|
3220
|
+
const LOGO_SIZE = 256;
|
|
3221
|
+
const RESIZED_TYPES = [
|
|
3222
|
+
"image/png",
|
|
3223
|
+
"image/jpeg",
|
|
3224
|
+
"image/webp"
|
|
3225
|
+
];
|
|
3226
|
+
const KEPT_TYPES = [
|
|
3227
|
+
"image/svg+xml",
|
|
3228
|
+
"image/x-icon",
|
|
3229
|
+
"image/vnd.microsoft.icon"
|
|
3230
|
+
];
|
|
3231
|
+
const PICKABLE_FILE = /\.(png|jpe?g|webp|svg)$/i;
|
|
2969
3232
|
const LOGO_FILES = /* @__PURE__ */ new Map([
|
|
2970
3233
|
[".svg", "image/svg+xml"],
|
|
2971
3234
|
[".png", "image/png"],
|
|
2972
3235
|
[".webp", "image/webp"],
|
|
2973
3236
|
[".jpg", "image/jpeg"],
|
|
2974
3237
|
[".jpeg", "image/jpeg"],
|
|
2975
|
-
[".gif", "image/gif"],
|
|
2976
3238
|
[".ico", "image/x-icon"]
|
|
2977
3239
|
]);
|
|
2978
3240
|
const PREFERRED = [
|
|
@@ -2995,11 +3257,42 @@ async function findLogo(folder) {
|
|
|
2995
3257
|
for (const { file } of candidates) {
|
|
2996
3258
|
const full = path.join(folder, file);
|
|
2997
3259
|
if (!existsSync(full)) continue;
|
|
2998
|
-
const
|
|
2999
|
-
|
|
3260
|
+
const type = LOGO_FILES.get(path.extname(file).toLowerCase());
|
|
3261
|
+
const data = await readFile(full);
|
|
3262
|
+
try {
|
|
3263
|
+
return await prepareLogo(`data:${type};base64,${data.toString("base64")}`);
|
|
3264
|
+
} catch {
|
|
3265
|
+
continue;
|
|
3266
|
+
}
|
|
3000
3267
|
}
|
|
3001
3268
|
return null;
|
|
3002
3269
|
}
|
|
3270
|
+
async function logoFiles(folder) {
|
|
3271
|
+
const repos = await projectRepos(folder);
|
|
3272
|
+
if (repos.length === 0) return (await listFiles(folder)).filter((file) => PICKABLE_FILE.test(file));
|
|
3273
|
+
const listed = await Promise.all(repos.map(async (repo) => {
|
|
3274
|
+
return (await git(path.join(folder, repo), [
|
|
3275
|
+
"ls-files",
|
|
3276
|
+
"-z",
|
|
3277
|
+
"--cached",
|
|
3278
|
+
"--others",
|
|
3279
|
+
"--exclude-standard"
|
|
3280
|
+
])).stdout.split("\0").map((file) => repo === "" ? file : `${repo}/${file}`);
|
|
3281
|
+
}));
|
|
3282
|
+
return [...new Set(listed.flat())].filter((file) => PICKABLE_FILE.test(file) && existsSync(path.join(folder, file)));
|
|
3283
|
+
}
|
|
3284
|
+
async function prepareLogo(logo) {
|
|
3285
|
+
const header = /^data:([^;,]+);base64,/.exec(logo);
|
|
3286
|
+
if (header !== null && RESIZED_TYPES.includes(header[1])) return `data:image/webp;base64,${(await sharp(Buffer.from(logo.slice(header[0].length), "base64")).resize(LOGO_SIZE, LOGO_SIZE, {
|
|
3287
|
+
fit: "inside",
|
|
3288
|
+
withoutEnlargement: true
|
|
3289
|
+
}).webp({ quality: 90 }).toBuffer()).toString("base64")}`;
|
|
3290
|
+
if (header !== null && KEPT_TYPES.includes(header[1])) {
|
|
3291
|
+
if (logo.length > LOGO_LIMIT) throw new Error("Use an SVG or ICO image smaller than 384 KB");
|
|
3292
|
+
return logo;
|
|
3293
|
+
}
|
|
3294
|
+
throw new Error("Use a PNG, JPEG, WebP, SVG or ICO image");
|
|
3295
|
+
}
|
|
3003
3296
|
function logoRank(file) {
|
|
3004
3297
|
const preferred = PREFERRED.indexOf(path.basename(file).toLowerCase());
|
|
3005
3298
|
if (preferred !== -1) return preferred;
|
|
@@ -3011,15 +3304,6 @@ function logoRank(file) {
|
|
|
3011
3304
|
}
|
|
3012
3305
|
//#endregion
|
|
3013
3306
|
//#region src/services/projects.ts
|
|
3014
|
-
const LOGO_TYPES = [
|
|
3015
|
-
"image/png",
|
|
3016
|
-
"image/jpeg",
|
|
3017
|
-
"image/gif",
|
|
3018
|
-
"image/webp",
|
|
3019
|
-
"image/svg+xml",
|
|
3020
|
-
"image/x-icon",
|
|
3021
|
-
"image/vnd.microsoft.icon"
|
|
3022
|
-
];
|
|
3023
3307
|
function projects$1() {
|
|
3024
3308
|
return db.select({
|
|
3025
3309
|
id: projects$2.id,
|
|
@@ -3049,16 +3333,13 @@ async function addProject(input) {
|
|
|
3049
3333
|
if (project === void 0) throw new Error(`Could not add ${folder}`);
|
|
3050
3334
|
return project;
|
|
3051
3335
|
}
|
|
3052
|
-
function updateProject(id, patch) {
|
|
3336
|
+
async function updateProject(id, patch) {
|
|
3053
3337
|
if (patch.name !== void 0) {
|
|
3054
3338
|
const name = patch.name.trim();
|
|
3055
3339
|
if (name === "") throw new Error("Project name cannot be empty");
|
|
3056
3340
|
db.update(projects$2).set({ name }).where(eq(projects$2.id, id)).run();
|
|
3057
3341
|
}
|
|
3058
|
-
if (patch.logo !== void 0) {
|
|
3059
|
-
if (patch.logo !== null) checkLogo(patch.logo);
|
|
3060
|
-
db.update(projects$2).set({ logo: patch.logo }).where(eq(projects$2.id, id)).run();
|
|
3061
|
-
}
|
|
3342
|
+
if (patch.logo !== void 0) db.update(projects$2).set({ logo: patch.logo === null ? null : await prepareLogo(patch.logo) }).where(eq(projects$2.id, id)).run();
|
|
3062
3343
|
publish$1();
|
|
3063
3344
|
return null;
|
|
3064
3345
|
}
|
|
@@ -3096,11 +3377,6 @@ function publish$1() {
|
|
|
3096
3377
|
projects: projects$1()
|
|
3097
3378
|
});
|
|
3098
3379
|
}
|
|
3099
|
-
function checkLogo(logo) {
|
|
3100
|
-
const type = /^data:([^;,]+);base64,/.exec(logo)?.[1];
|
|
3101
|
-
if (type === void 0 || !LOGO_TYPES.includes(type)) throw new Error("Use a PNG, JPEG, GIF, WebP, SVG or ICO image");
|
|
3102
|
-
if (logo.length > 524288) throw new Error("Use an image smaller than 384 KB");
|
|
3103
|
-
}
|
|
3104
3380
|
function expandHome(input) {
|
|
3105
3381
|
if (input === "~") return os.homedir();
|
|
3106
3382
|
if (input.startsWith("~/")) return path.join(os.homedir(), input.slice(2));
|
|
@@ -3189,21 +3465,6 @@ async function find(provider, paths) {
|
|
|
3189
3465
|
});
|
|
3190
3466
|
}
|
|
3191
3467
|
}
|
|
3192
|
-
function encodeFrame(kind, id, data) {
|
|
3193
|
-
const frame = new Uint8Array(5 + data.length);
|
|
3194
|
-
frame[0] = kind;
|
|
3195
|
-
new DataView(frame.buffer).setUint32(1, id);
|
|
3196
|
-
frame.set(data, 5);
|
|
3197
|
-
return frame;
|
|
3198
|
-
}
|
|
3199
|
-
function decodeFrame(frame) {
|
|
3200
|
-
if (frame.length < 5) throw new Error("terminal frame is too short");
|
|
3201
|
-
return {
|
|
3202
|
-
kind: frame[0],
|
|
3203
|
-
id: new DataView(frame.buffer, frame.byteOffset, frame.byteLength).getUint32(1),
|
|
3204
|
-
data: frame.subarray(5)
|
|
3205
|
-
};
|
|
3206
|
-
}
|
|
3207
3468
|
//#endregion
|
|
3208
3469
|
//#region src/services/push.ts
|
|
3209
3470
|
const TITLE_LIMIT = 100;
|
|
@@ -3669,7 +3930,7 @@ function writeScript(target, source) {
|
|
|
3669
3930
|
}
|
|
3670
3931
|
//#endregion
|
|
3671
3932
|
//#region src/services/terminal/process.ts
|
|
3672
|
-
const run = promisify(execFile);
|
|
3933
|
+
const run$1 = promisify(execFile);
|
|
3673
3934
|
const iterm2Version = "3.6.8";
|
|
3674
3935
|
const ghosttyVersion = "1.3.1";
|
|
3675
3936
|
const shellNames = process.platform === "win32" ? [
|
|
@@ -3730,7 +3991,7 @@ function startShell(cwd) {
|
|
|
3730
3991
|
async function currentDirectory(pid, reported) {
|
|
3731
3992
|
if (process.platform === "linux") return readlink(`/proc/${pid}/cwd`);
|
|
3732
3993
|
if (process.platform === "darwin") {
|
|
3733
|
-
const { stdout } = await run("lsof", [
|
|
3994
|
+
const { stdout } = await run$1("lsof", [
|
|
3734
3995
|
"-a",
|
|
3735
3996
|
"-d",
|
|
3736
3997
|
"cwd",
|
|
@@ -3750,7 +4011,7 @@ async function currentDirectory(pid, reported) {
|
|
|
3750
4011
|
}
|
|
3751
4012
|
async function busyProgram(pid) {
|
|
3752
4013
|
if (process.platform === "win32") {
|
|
3753
|
-
const { stdout } = await run("powershell.exe", [
|
|
4014
|
+
const { stdout } = await run$1("powershell.exe", [
|
|
3754
4015
|
"-NoProfile",
|
|
3755
4016
|
"-Command",
|
|
3756
4017
|
`Get-CimInstance Win32_Process -Filter "ParentProcessId=${pid}" | Select-Object -ExpandProperty Name`
|
|
@@ -3773,7 +4034,7 @@ async function foregroundGroup(pid) {
|
|
|
3773
4034
|
const text = await readFile(`/proc/${pid}/stat`, "utf8");
|
|
3774
4035
|
return Number(text.slice(text.lastIndexOf(")") + 2).split(" ")[5]);
|
|
3775
4036
|
}
|
|
3776
|
-
const { stdout } = await run("ps", [
|
|
4037
|
+
const { stdout } = await run$1("ps", [
|
|
3777
4038
|
"-o",
|
|
3778
4039
|
"tpgid=",
|
|
3779
4040
|
"-p",
|
|
@@ -3783,7 +4044,7 @@ async function foregroundGroup(pid) {
|
|
|
3783
4044
|
}
|
|
3784
4045
|
async function programName(pid) {
|
|
3785
4046
|
if (process.platform === "linux") return (await readFile(`/proc/${pid}/comm`, "utf8")).trimEnd();
|
|
3786
|
-
const { stdout } = await run("ps", [
|
|
4047
|
+
const { stdout } = await run$1("ps", [
|
|
3787
4048
|
"-o",
|
|
3788
4049
|
"comm=",
|
|
3789
4050
|
"-p",
|
|
@@ -3852,7 +4113,7 @@ function executable$1(name) {
|
|
|
3852
4113
|
//#endregion
|
|
3853
4114
|
//#region src/services/terminal/terminal.ts
|
|
3854
4115
|
const HISTORY_LIMIT = 2097152;
|
|
3855
|
-
const OUTPUT_LIMIT
|
|
4116
|
+
const OUTPUT_LIMIT = 1048576;
|
|
3856
4117
|
const STALL_LIMIT = 1e4;
|
|
3857
4118
|
const SILENCE_LIMIT$1 = 9e4;
|
|
3858
4119
|
const KILL_DELAY = 3e3;
|
|
@@ -4081,7 +4342,7 @@ function output(id, terminal, data) {
|
|
|
4081
4342
|
}
|
|
4082
4343
|
for (const connection of connections.values()) forward(connection, id, false, data);
|
|
4083
4344
|
scan(id, terminal, data);
|
|
4084
|
-
if ([...connections.values()].some((connection) => connection.client.socket.bufferedAmount > OUTPUT_LIMIT
|
|
4345
|
+
if ([...connections.values()].some((connection) => connection.client.socket.bufferedAmount > OUTPUT_LIMIT)) {
|
|
4085
4346
|
terminal.pty.pause();
|
|
4086
4347
|
paused.add(terminal.pty);
|
|
4087
4348
|
draining ??= drain();
|
|
@@ -4093,7 +4354,7 @@ function drain() {
|
|
|
4093
4354
|
let full = false;
|
|
4094
4355
|
for (const connection of connections.values()) {
|
|
4095
4356
|
const queued = connection.client.socket.bufferedAmount;
|
|
4096
|
-
if (queued <= OUTPUT_LIMIT
|
|
4357
|
+
if (queued <= OUTPUT_LIMIT) continue;
|
|
4097
4358
|
const now = Date.now();
|
|
4098
4359
|
const last = progress.get(connection) ?? {
|
|
4099
4360
|
queued: Infinity,
|
|
@@ -4169,6 +4430,10 @@ function publish() {
|
|
|
4169
4430
|
layout: layout()
|
|
4170
4431
|
};
|
|
4171
4432
|
for (const connection of connections.values()) send$1(connection, event);
|
|
4433
|
+
broadcast({
|
|
4434
|
+
type: "terminals",
|
|
4435
|
+
count: terminalCount(GLOBAL_TERMINALS)
|
|
4436
|
+
});
|
|
4172
4437
|
}
|
|
4173
4438
|
function forward(connection, id, replay, data) {
|
|
4174
4439
|
connection.client.socket.send(encodeFrame(replay ? 3 : 2, id, data));
|
|
@@ -4260,7 +4525,7 @@ function withStatus(items) {
|
|
|
4260
4525
|
return {
|
|
4261
4526
|
...item,
|
|
4262
4527
|
updatedAt,
|
|
4263
|
-
status: record === void 0 || record.status === "active" && Date.now() - updatedAt > day ? "inactive" : record.status
|
|
4528
|
+
status: record === void 0 || (record.status === "active" || record.status === "snoozed") && Date.now() - updatedAt > day ? "inactive" : record.status
|
|
4264
4529
|
};
|
|
4265
4530
|
});
|
|
4266
4531
|
}
|
|
@@ -4290,7 +4555,7 @@ function recordInteraction(id) {
|
|
|
4290
4555
|
target: sessions$1.sessionId,
|
|
4291
4556
|
set: {
|
|
4292
4557
|
interactedAt: Date.now(),
|
|
4293
|
-
status: sql`CASE ${sessions$1.status} WHEN 'pinned' THEN 'pinned' WHEN 'minimized' THEN 'minimized' ELSE 'active' END`
|
|
4558
|
+
status: sql`CASE ${sessions$1.status} WHEN 'pinned' THEN 'pinned' WHEN 'snoozed' THEN 'snoozed' WHEN 'minimized' THEN 'minimized' ELSE 'active' END`
|
|
4294
4559
|
}
|
|
4295
4560
|
}).run();
|
|
4296
4561
|
}
|
|
@@ -4419,7 +4684,7 @@ async function updateSession(id, patch) {
|
|
|
4419
4684
|
const session = all.find((item) => item.id === id);
|
|
4420
4685
|
if (session !== void 0 && all.every((item) => item.projectId !== session.projectId || item.status === "inactive") && [...runs.values()].every((other) => other.sessionId === id || other.cwd !== projectPath(session.projectId) || all.some((item) => item.id === other.sessionId))) closeProjectTabs(session.projectId);
|
|
4421
4686
|
} else if (patch.status !== void 0) {
|
|
4422
|
-
const set = patch.status === "active" ? {
|
|
4687
|
+
const set = patch.status === "active" || patch.status === "snoozed" ? {
|
|
4423
4688
|
status: patch.status,
|
|
4424
4689
|
interactedAt: Date.now()
|
|
4425
4690
|
} : { status: patch.status };
|
|
@@ -4675,7 +4940,6 @@ async function capabilities(provider, accountId, cwd) {
|
|
|
4675
4940
|
assertEnabled(provider, accountId);
|
|
4676
4941
|
const result = await providerCapabilities(provider, accountId, cwd);
|
|
4677
4942
|
const settings = readConfig().models[provider];
|
|
4678
|
-
const overrides = provider === "opencode" ? {} : storedAccount(provider, accountId).hiddenModels;
|
|
4679
4943
|
return {
|
|
4680
4944
|
...result,
|
|
4681
4945
|
defaultModel: defaultModels[provider],
|
|
@@ -4690,7 +4954,7 @@ async function capabilities(provider, accountId, cwd) {
|
|
|
4690
4954
|
return {
|
|
4691
4955
|
...model,
|
|
4692
4956
|
deprecated,
|
|
4693
|
-
hidden:
|
|
4957
|
+
hidden: setting.hidden,
|
|
4694
4958
|
effort: setting.effort ?? effort
|
|
4695
4959
|
};
|
|
4696
4960
|
})
|
|
@@ -4717,8 +4981,7 @@ function addAccount(provider, name, configDir) {
|
|
|
4717
4981
|
name: account.name,
|
|
4718
4982
|
color: null,
|
|
4719
4983
|
configDir: { [platform()]: account.configDir },
|
|
4720
|
-
enabled: true
|
|
4721
|
-
hiddenModels: {}
|
|
4984
|
+
enabled: true
|
|
4722
4985
|
}]
|
|
4723
4986
|
});
|
|
4724
4987
|
}
|
|
@@ -4738,8 +5001,7 @@ async function updateAccount(update) {
|
|
|
4738
5001
|
...item.configDir,
|
|
4739
5002
|
[platform()]: account.configDir
|
|
4740
5003
|
},
|
|
4741
|
-
enabled: update.enabled
|
|
4742
|
-
hiddenModels: update.hiddenModels
|
|
5004
|
+
enabled: update.enabled
|
|
4743
5005
|
} : item)
|
|
4744
5006
|
});
|
|
4745
5007
|
forgetClaude(update.id);
|
|
@@ -5551,7 +5813,7 @@ async function image(sessionId, uuid, index) {
|
|
|
5551
5813
|
data: block.data
|
|
5552
5814
|
};
|
|
5553
5815
|
}
|
|
5554
|
-
async function history(sessionId, cwd) {
|
|
5816
|
+
async function history$1(sessionId, cwd) {
|
|
5555
5817
|
const found = runs.get(sessionId);
|
|
5556
5818
|
const run = found?.provider === "claude" ? found : void 0;
|
|
5557
5819
|
const [disk, session] = await Promise.all([getSessionMessages(sessionId, { dir: cwd }), loadSession(sessionId)]);
|
|
@@ -5982,142 +6244,6 @@ var Run = class {
|
|
|
5982
6244
|
}
|
|
5983
6245
|
};
|
|
5984
6246
|
//#endregion
|
|
5985
|
-
//#region src/services/files.ts
|
|
5986
|
-
const FILE_LIMIT = 2e4;
|
|
5987
|
-
const OUTPUT_LIMIT = 262144;
|
|
5988
|
-
const READ_LIMIT = 1048576;
|
|
5989
|
-
const MEDIA_LINK_LIFETIME = 36e5;
|
|
5990
|
-
const browseToken = randomBytes(32).toString("base64url");
|
|
5991
|
-
const mediaLinks = /* @__PURE__ */ new Map();
|
|
5992
|
-
const skipped = /* @__PURE__ */ new Set([
|
|
5993
|
-
"node_modules",
|
|
5994
|
-
".git",
|
|
5995
|
-
".DS_Store",
|
|
5996
|
-
"dist",
|
|
5997
|
-
"target",
|
|
5998
|
-
".next",
|
|
5999
|
-
"__pycache__",
|
|
6000
|
-
".venv"
|
|
6001
|
-
]);
|
|
6002
|
-
async function listFiles(cwd) {
|
|
6003
|
-
const files = [];
|
|
6004
|
-
const pending = [""];
|
|
6005
|
-
while (pending.length > 0 && files.length < FILE_LIMIT) {
|
|
6006
|
-
const dir = pending.pop() ?? "";
|
|
6007
|
-
let entries;
|
|
6008
|
-
try {
|
|
6009
|
-
entries = await readdir(path.join(cwd, dir), { withFileTypes: true });
|
|
6010
|
-
} catch {
|
|
6011
|
-
continue;
|
|
6012
|
-
}
|
|
6013
|
-
for (const entry of entries) {
|
|
6014
|
-
if (skipped.has(entry.name)) continue;
|
|
6015
|
-
const relative = dir === "" ? entry.name : `${dir}/${entry.name}`;
|
|
6016
|
-
if (entry.isDirectory()) pending.push(relative);
|
|
6017
|
-
if (entry.isFile()) files.push(relative);
|
|
6018
|
-
if (entry.isSymbolicLink()) {
|
|
6019
|
-
if ((await stat(path.join(cwd, relative)).catch(() => null))?.isFile() === true) files.push(relative);
|
|
6020
|
-
}
|
|
6021
|
-
}
|
|
6022
|
-
}
|
|
6023
|
-
return files.slice(0, FILE_LIMIT).sort();
|
|
6024
|
-
}
|
|
6025
|
-
function readProjectFile(cwd, file) {
|
|
6026
|
-
const full = resolveProjectFile(cwd, file);
|
|
6027
|
-
const stats = statSync(full);
|
|
6028
|
-
if (!stats.isFile()) throw new Error(`${file} is not a file`);
|
|
6029
|
-
if (stats.size > READ_LIMIT) return {
|
|
6030
|
-
kind: "large",
|
|
6031
|
-
size: stats.size,
|
|
6032
|
-
limit: READ_LIMIT
|
|
6033
|
-
};
|
|
6034
|
-
const data = readFileSync(full);
|
|
6035
|
-
const mediaType = mediaTypes.get(path.extname(full).toLowerCase());
|
|
6036
|
-
if (mediaType?.startsWith("image/") === true) return {
|
|
6037
|
-
kind: "image",
|
|
6038
|
-
size: stats.size,
|
|
6039
|
-
mediaType,
|
|
6040
|
-
data: data.toString("base64")
|
|
6041
|
-
};
|
|
6042
|
-
if (data.includes(0)) return {
|
|
6043
|
-
kind: "binary",
|
|
6044
|
-
size: stats.size
|
|
6045
|
-
};
|
|
6046
|
-
return {
|
|
6047
|
-
kind: "text",
|
|
6048
|
-
size: stats.size,
|
|
6049
|
-
content: data.toString("utf8")
|
|
6050
|
-
};
|
|
6051
|
-
}
|
|
6052
|
-
function linkProjectMedia(cwd, file) {
|
|
6053
|
-
const resolved = path.resolve(realpathSync(cwd), file);
|
|
6054
|
-
if (!existsSync(resolved)) throw new Error(`${file} does not exist`);
|
|
6055
|
-
const full = realpathSync(resolved);
|
|
6056
|
-
const mediaType = mediaTypes.get(path.extname(full).toLowerCase());
|
|
6057
|
-
if (mediaType === void 0) throw new Error(`${file} is not an image, video or audio file`);
|
|
6058
|
-
return addLink(full, mediaType, null);
|
|
6059
|
-
}
|
|
6060
|
-
function linkProjectDownload(cwd, file) {
|
|
6061
|
-
return addLink(resolveProjectFile(cwd, file), "application/octet-stream", path.basename(file));
|
|
6062
|
-
}
|
|
6063
|
-
function addLink(file, mediaType, download) {
|
|
6064
|
-
for (const [token, link] of mediaLinks) if (link.expiresAt <= Date.now()) mediaLinks.delete(token);
|
|
6065
|
-
const token = randomBytes(32).toString("base64url");
|
|
6066
|
-
mediaLinks.set(token, {
|
|
6067
|
-
file,
|
|
6068
|
-
mediaType,
|
|
6069
|
-
download,
|
|
6070
|
-
expiresAt: Date.now() + MEDIA_LINK_LIFETIME
|
|
6071
|
-
});
|
|
6072
|
-
return token;
|
|
6073
|
-
}
|
|
6074
|
-
function findMediaLink(token) {
|
|
6075
|
-
if (token === null) return null;
|
|
6076
|
-
const link = mediaLinks.get(token);
|
|
6077
|
-
if (link === void 0 || link.expiresAt <= Date.now()) return null;
|
|
6078
|
-
return link;
|
|
6079
|
-
}
|
|
6080
|
-
function saveUpload(name, data) {
|
|
6081
|
-
const dir = path.join(home, "uploads", randomUUID());
|
|
6082
|
-
mkdirSync(dir, { recursive: true });
|
|
6083
|
-
const file = path.join(dir, path.basename(name));
|
|
6084
|
-
writeFileSync(file, data);
|
|
6085
|
-
return file;
|
|
6086
|
-
}
|
|
6087
|
-
async function taskOutput(sessionId, file) {
|
|
6088
|
-
const tasks = path.dirname(file);
|
|
6089
|
-
if (!/^[\w-]+$/.test(sessionId) || !path.isAbsolute(file) || path.normalize(file) !== file || !/^\w+\.output$/.test(path.basename(file)) || path.basename(tasks) !== "tasks" || path.basename(path.dirname(tasks)) !== sessionId) throw new Error("Not a background task output file of this thread");
|
|
6090
|
-
let stats;
|
|
6091
|
-
try {
|
|
6092
|
-
stats = await lstat(file);
|
|
6093
|
-
} catch (error) {
|
|
6094
|
-
if (hasCode(error, "ENOENT")) return null;
|
|
6095
|
-
throw error;
|
|
6096
|
-
}
|
|
6097
|
-
if (!stats.isFile()) throw new Error("Background task output is not a regular file");
|
|
6098
|
-
const start = Math.max(0, stats.size - OUTPUT_LIMIT);
|
|
6099
|
-
const handle = await open(file, "r");
|
|
6100
|
-
try {
|
|
6101
|
-
const buffer = Buffer.alloc(stats.size - start);
|
|
6102
|
-
const read = await handle.read(buffer, 0, buffer.length, start);
|
|
6103
|
-
return {
|
|
6104
|
-
text: buffer.subarray(0, read.bytesRead).toString("utf8"),
|
|
6105
|
-
truncated: start > 0
|
|
6106
|
-
};
|
|
6107
|
-
} finally {
|
|
6108
|
-
await handle.close();
|
|
6109
|
-
}
|
|
6110
|
-
}
|
|
6111
|
-
function resolveProjectFile(cwd, file) {
|
|
6112
|
-
const root = realpathSync(cwd);
|
|
6113
|
-
const resolved = path.resolve(root, file);
|
|
6114
|
-
if (!existsSync(resolved)) throw new Error(`${file} does not exist`);
|
|
6115
|
-
const full = realpathSync(resolved);
|
|
6116
|
-
const relative = path.relative(root, full);
|
|
6117
|
-
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) throw new Error(`${file} is outside the project`);
|
|
6118
|
-
return full;
|
|
6119
|
-
}
|
|
6120
|
-
//#endregion
|
|
6121
6247
|
//#region src/services/proxy.ts
|
|
6122
6248
|
const proxies = /* @__PURE__ */ new Map();
|
|
6123
6249
|
const pageScriptPath = "/__offsite/browser.js";
|
|
@@ -6261,7 +6387,7 @@ function stopAll() {
|
|
|
6261
6387
|
}
|
|
6262
6388
|
function sessionHistory(sessionId, cwd) {
|
|
6263
6389
|
switch (providerOf(sessionId)) {
|
|
6264
|
-
case "claude": return history(sessionId, cwd);
|
|
6390
|
+
case "claude": return history$1(sessionId, cwd);
|
|
6265
6391
|
case "codex": return codexHistory(sessionId);
|
|
6266
6392
|
case "opencode": return openCodeHistory(sessionId, cwd);
|
|
6267
6393
|
}
|
|
@@ -6313,7 +6439,7 @@ async function rewindNow(request) {
|
|
|
6313
6439
|
resumeAt: disk[index - 1].uuid,
|
|
6314
6440
|
settings: request.settings
|
|
6315
6441
|
});
|
|
6316
|
-
publish$2(sessionId, await history(sessionId, cwd));
|
|
6442
|
+
publish$2(sessionId, await history$1(sessionId, cwd));
|
|
6317
6443
|
return result;
|
|
6318
6444
|
}
|
|
6319
6445
|
async function open$1(client, sessionId, projectId) {
|
|
@@ -6406,6 +6532,294 @@ async function changedElsewhere(run, cwd) {
|
|
|
6406
6532
|
return last !== void 0 && !run.messages.some((item) => item.uuid === last);
|
|
6407
6533
|
}
|
|
6408
6534
|
//#endregion
|
|
6535
|
+
//#region src/services/source-control.ts
|
|
6536
|
+
const SHOW_LIMIT = 1048576;
|
|
6537
|
+
const NETWORK_TIMEOUT = 12e4;
|
|
6538
|
+
const run = promisify(execFile);
|
|
6539
|
+
async function sourceControl(cwd) {
|
|
6540
|
+
return Promise.all((await projectRepos(cwd)).map(async (repo) => ({
|
|
6541
|
+
...await repoChanges(path.join(cwd, repo)),
|
|
6542
|
+
path: repo
|
|
6543
|
+
})));
|
|
6544
|
+
}
|
|
6545
|
+
async function showFile(cwd, ref, file) {
|
|
6546
|
+
const output = await git(cwd, [
|
|
6547
|
+
"cat-file",
|
|
6548
|
+
"blob",
|
|
6549
|
+
`${ref}:${file}`
|
|
6550
|
+
]);
|
|
6551
|
+
if (output.stdout.length > SHOW_LIMIT || output.stdout.includes("\0")) return null;
|
|
6552
|
+
return output.stdout;
|
|
6553
|
+
}
|
|
6554
|
+
async function history(cwd, limit) {
|
|
6555
|
+
const upstream = (await git(cwd, ["branch", "--format=%(if)%(HEAD)%(then)%(if:notequals=[gone])%(upstream:track)%(then)%(upstream)%(end)%(end)"])).stdout.trim();
|
|
6556
|
+
return (await git(cwd, [
|
|
6557
|
+
"log",
|
|
6558
|
+
"--topo-order",
|
|
6559
|
+
`--max-count=${limit}`,
|
|
6560
|
+
"--format=%H%x1f%P%x1f%an%x1f%at%x1f%D%x1f%s%x1e",
|
|
6561
|
+
"HEAD",
|
|
6562
|
+
...upstream === "" ? [] : [upstream],
|
|
6563
|
+
"--"
|
|
6564
|
+
])).stdout.split("").map((record) => record.trim()).filter(Boolean).map((record) => {
|
|
6565
|
+
const fields = record.split("");
|
|
6566
|
+
return {
|
|
6567
|
+
hash: fields[0],
|
|
6568
|
+
parents: fields[1].split(" ").filter(Boolean),
|
|
6569
|
+
author: fields[2],
|
|
6570
|
+
date: Number(fields[3]) * 1e3,
|
|
6571
|
+
refs: fields[4].split(", ").filter(Boolean),
|
|
6572
|
+
subject: fields[5]
|
|
6573
|
+
};
|
|
6574
|
+
});
|
|
6575
|
+
}
|
|
6576
|
+
async function commitFiles(cwd, hash, parent) {
|
|
6577
|
+
const tokens = (await git(cwd, [
|
|
6578
|
+
"diff",
|
|
6579
|
+
"--name-status",
|
|
6580
|
+
"-M",
|
|
6581
|
+
"-z",
|
|
6582
|
+
parent ?? (await git(cwd, [
|
|
6583
|
+
"hash-object",
|
|
6584
|
+
"-t",
|
|
6585
|
+
"tree",
|
|
6586
|
+
"/dev/null"
|
|
6587
|
+
])).stdout.trim(),
|
|
6588
|
+
hash
|
|
6589
|
+
])).stdout.split("\0");
|
|
6590
|
+
const files = [];
|
|
6591
|
+
for (let index = 0; index < tokens.length - 1; index++) {
|
|
6592
|
+
const status = letter(tokens[index][0]);
|
|
6593
|
+
if (status === "R" || status === "C") {
|
|
6594
|
+
files.push({
|
|
6595
|
+
path: tokens[index + 2],
|
|
6596
|
+
from: tokens[index + 1],
|
|
6597
|
+
status
|
|
6598
|
+
});
|
|
6599
|
+
index += 2;
|
|
6600
|
+
continue;
|
|
6601
|
+
}
|
|
6602
|
+
files.push({
|
|
6603
|
+
path: tokens[index + 1],
|
|
6604
|
+
from: null,
|
|
6605
|
+
status
|
|
6606
|
+
});
|
|
6607
|
+
index += 1;
|
|
6608
|
+
}
|
|
6609
|
+
return files;
|
|
6610
|
+
}
|
|
6611
|
+
async function undoCommit(cwd) {
|
|
6612
|
+
const message = (await git(cwd, [
|
|
6613
|
+
"log",
|
|
6614
|
+
"-1",
|
|
6615
|
+
"--format=%B"
|
|
6616
|
+
])).stdout.trim();
|
|
6617
|
+
if ((await git(cwd, [
|
|
6618
|
+
"rev-list",
|
|
6619
|
+
"--parents",
|
|
6620
|
+
"--max-count=1",
|
|
6621
|
+
"HEAD"
|
|
6622
|
+
])).stdout.trim().split(" ").length === 1) await git(cwd, [
|
|
6623
|
+
"update-ref",
|
|
6624
|
+
"-d",
|
|
6625
|
+
"HEAD"
|
|
6626
|
+
]);
|
|
6627
|
+
else await git(cwd, [
|
|
6628
|
+
"reset",
|
|
6629
|
+
"--soft",
|
|
6630
|
+
"HEAD~1"
|
|
6631
|
+
]);
|
|
6632
|
+
return message;
|
|
6633
|
+
}
|
|
6634
|
+
async function runAction(cwd, action) {
|
|
6635
|
+
if (action.kind === "stage") {
|
|
6636
|
+
await git(cwd, [
|
|
6637
|
+
"--literal-pathspecs",
|
|
6638
|
+
"add",
|
|
6639
|
+
"-A",
|
|
6640
|
+
"--",
|
|
6641
|
+
...action.paths
|
|
6642
|
+
]);
|
|
6643
|
+
return;
|
|
6644
|
+
}
|
|
6645
|
+
if (action.kind === "stageContent") {
|
|
6646
|
+
const entry = await git(cwd, [
|
|
6647
|
+
"--literal-pathspecs",
|
|
6648
|
+
"ls-files",
|
|
6649
|
+
"--stage",
|
|
6650
|
+
"--",
|
|
6651
|
+
action.path
|
|
6652
|
+
]);
|
|
6653
|
+
if (entry.stdout === "") throw new Error(`${action.path} is not in the index`);
|
|
6654
|
+
const hashing = run("git", [
|
|
6655
|
+
"hash-object",
|
|
6656
|
+
"-w",
|
|
6657
|
+
"--stdin",
|
|
6658
|
+
`--path=${action.path}`
|
|
6659
|
+
], { cwd });
|
|
6660
|
+
hashing.child.stdin?.end(action.content);
|
|
6661
|
+
const hash = (await hashing).stdout.trim();
|
|
6662
|
+
await git(cwd, [
|
|
6663
|
+
"update-index",
|
|
6664
|
+
"--cacheinfo",
|
|
6665
|
+
`${entry.stdout.split(" ")[0]},${hash},${action.path}`
|
|
6666
|
+
]);
|
|
6667
|
+
return;
|
|
6668
|
+
}
|
|
6669
|
+
if (action.kind === "unstage") {
|
|
6670
|
+
await git(cwd, [
|
|
6671
|
+
"--literal-pathspecs",
|
|
6672
|
+
"reset",
|
|
6673
|
+
"-q",
|
|
6674
|
+
await diffBase(cwd),
|
|
6675
|
+
"--",
|
|
6676
|
+
...action.paths
|
|
6677
|
+
]);
|
|
6678
|
+
return;
|
|
6679
|
+
}
|
|
6680
|
+
if (action.kind === "discard") {
|
|
6681
|
+
if (action.tracked.length > 0) await git(cwd, [
|
|
6682
|
+
"--literal-pathspecs",
|
|
6683
|
+
"checkout",
|
|
6684
|
+
"-q",
|
|
6685
|
+
"--",
|
|
6686
|
+
...action.tracked
|
|
6687
|
+
]);
|
|
6688
|
+
if (action.untracked.length > 0) await git(cwd, [
|
|
6689
|
+
"--literal-pathspecs",
|
|
6690
|
+
"clean",
|
|
6691
|
+
"-f",
|
|
6692
|
+
"-q",
|
|
6693
|
+
"--",
|
|
6694
|
+
...action.untracked
|
|
6695
|
+
]);
|
|
6696
|
+
return;
|
|
6697
|
+
}
|
|
6698
|
+
if (action.kind === "ignore") {
|
|
6699
|
+
const file = path.join(cwd, ".gitignore");
|
|
6700
|
+
const current = existsSync(file) ? readFileSync(file, "utf8") : "";
|
|
6701
|
+
appendFileSync(file, `${current === "" || current.endsWith("\n") ? "" : "\n"}${action.paths.join("\n")}\n`);
|
|
6702
|
+
return;
|
|
6703
|
+
}
|
|
6704
|
+
if (action.kind === "commit") {
|
|
6705
|
+
if (action.all) await git(cwd, ["add", "--all"]);
|
|
6706
|
+
await git(cwd, [
|
|
6707
|
+
"commit",
|
|
6708
|
+
...action.amend ? ["--amend"] : [],
|
|
6709
|
+
...action.message === "" ? ["--no-edit"] : ["--message", action.message]
|
|
6710
|
+
]);
|
|
6711
|
+
return;
|
|
6712
|
+
}
|
|
6713
|
+
if (action.kind === "pull") {
|
|
6714
|
+
await network(cwd, action.rebase ? ["pull", "--rebase"] : ["pull"]);
|
|
6715
|
+
return;
|
|
6716
|
+
}
|
|
6717
|
+
if (action.kind === "fetch") {
|
|
6718
|
+
await network(cwd, [
|
|
6719
|
+
"fetch",
|
|
6720
|
+
...action.prune ? ["--prune"] : [],
|
|
6721
|
+
...action.all ? ["--all"] : []
|
|
6722
|
+
]);
|
|
6723
|
+
return;
|
|
6724
|
+
}
|
|
6725
|
+
if (action.kind === "stash") {
|
|
6726
|
+
if (action.mode === "tracked") await git(cwd, ["stash", "push"]);
|
|
6727
|
+
if (action.mode === "untracked") await git(cwd, [
|
|
6728
|
+
"stash",
|
|
6729
|
+
"push",
|
|
6730
|
+
"--include-untracked"
|
|
6731
|
+
]);
|
|
6732
|
+
if (action.mode === "staged") await git(cwd, [
|
|
6733
|
+
"stash",
|
|
6734
|
+
"push",
|
|
6735
|
+
"--staged"
|
|
6736
|
+
]);
|
|
6737
|
+
return;
|
|
6738
|
+
}
|
|
6739
|
+
if (action.kind === "unstash") {
|
|
6740
|
+
await git(cwd, ["stash", action.pop ? "pop" : "apply"]);
|
|
6741
|
+
return;
|
|
6742
|
+
}
|
|
6743
|
+
throw new Error("Unknown git action");
|
|
6744
|
+
}
|
|
6745
|
+
async function network(cwd, args) {
|
|
6746
|
+
try {
|
|
6747
|
+
await run("git", args, {
|
|
6748
|
+
cwd,
|
|
6749
|
+
env: {
|
|
6750
|
+
...process.env,
|
|
6751
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
6752
|
+
},
|
|
6753
|
+
timeout: NETWORK_TIMEOUT
|
|
6754
|
+
});
|
|
6755
|
+
} catch (error) {
|
|
6756
|
+
throw gitError(error);
|
|
6757
|
+
}
|
|
6758
|
+
}
|
|
6759
|
+
async function repoChanges(cwd) {
|
|
6760
|
+
const output = await git(cwd, [
|
|
6761
|
+
"--no-optional-locks",
|
|
6762
|
+
"status",
|
|
6763
|
+
"--porcelain=v2",
|
|
6764
|
+
"--branch",
|
|
6765
|
+
"--untracked-files=all",
|
|
6766
|
+
"-z"
|
|
6767
|
+
]);
|
|
6768
|
+
const repo = {
|
|
6769
|
+
head: null,
|
|
6770
|
+
branch: null,
|
|
6771
|
+
upstream: null,
|
|
6772
|
+
remote: (await git(cwd, ["remote"])).stdout.trim() !== "",
|
|
6773
|
+
ahead: 0,
|
|
6774
|
+
behind: 0,
|
|
6775
|
+
merge: [],
|
|
6776
|
+
staged: [],
|
|
6777
|
+
changes: []
|
|
6778
|
+
};
|
|
6779
|
+
const tokens = output.stdout.split("\0");
|
|
6780
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
6781
|
+
const fields = tokens[index].split(" ");
|
|
6782
|
+
if (fields[0] === "#" && fields[1] === "branch.oid") repo.head = fields[2] === "(initial)" ? null : fields[2];
|
|
6783
|
+
if (fields[0] === "#" && fields[1] === "branch.head") repo.branch = fields[2] === "(detached)" ? null : fields[2];
|
|
6784
|
+
if (fields[0] === "#" && fields[1] === "branch.upstream") repo.upstream = fields[2];
|
|
6785
|
+
if (fields[0] === "#" && fields[1] === "branch.ab") {
|
|
6786
|
+
repo.ahead = Number(fields[2]);
|
|
6787
|
+
repo.behind = -Number(fields[3]);
|
|
6788
|
+
}
|
|
6789
|
+
if (fields[0] === "?") repo.changes.push({
|
|
6790
|
+
path: tokens[index].slice(2),
|
|
6791
|
+
from: null,
|
|
6792
|
+
status: "U"
|
|
6793
|
+
});
|
|
6794
|
+
if (fields[0] === "u") repo.merge.push({
|
|
6795
|
+
path: fields.slice(10).join(" "),
|
|
6796
|
+
from: null,
|
|
6797
|
+
status: "!"
|
|
6798
|
+
});
|
|
6799
|
+
if (fields[0] === "1" || fields[0] === "2") {
|
|
6800
|
+
const renamed = fields[0] === "2";
|
|
6801
|
+
const file = fields.slice(renamed ? 9 : 8).join(" ");
|
|
6802
|
+
const from = renamed ? tokens[index + 1] : null;
|
|
6803
|
+
if (fields[1][0] !== ".") repo.staged.push({
|
|
6804
|
+
path: file,
|
|
6805
|
+
from,
|
|
6806
|
+
status: letter(fields[1][0])
|
|
6807
|
+
});
|
|
6808
|
+
if (fields[1][1] !== ".") repo.changes.push({
|
|
6809
|
+
path: file,
|
|
6810
|
+
from: null,
|
|
6811
|
+
status: letter(fields[1][1])
|
|
6812
|
+
});
|
|
6813
|
+
if (renamed) index++;
|
|
6814
|
+
}
|
|
6815
|
+
}
|
|
6816
|
+
return repo;
|
|
6817
|
+
}
|
|
6818
|
+
function letter(code) {
|
|
6819
|
+
if (code === "M" || code === "T" || code === "A" || code === "D" || code === "R" || code === "C") return code;
|
|
6820
|
+
throw new Error(`Unknown git status ${code}`);
|
|
6821
|
+
}
|
|
6822
|
+
//#endregion
|
|
6409
6823
|
//#region src/services/terminal/paste.ts
|
|
6410
6824
|
const pasteTypes = {
|
|
6411
6825
|
"image/png": "png",
|
|
@@ -6424,7 +6838,6 @@ function savePastedImage(type, data) {
|
|
|
6424
6838
|
}
|
|
6425
6839
|
//#endregion
|
|
6426
6840
|
//#region src/storage/state.ts
|
|
6427
|
-
const stateFile = path.join(home, "state.json");
|
|
6428
6841
|
const stateSchema = z.strictObject({ composer: z.strictObject({
|
|
6429
6842
|
provider: z.enum([
|
|
6430
6843
|
"claude",
|
|
@@ -6472,20 +6885,7 @@ const stateSchema = z.strictObject({ composer: z.strictObject({
|
|
|
6472
6885
|
}).prefault({}) });
|
|
6473
6886
|
const defaults = stateSchema.parse({});
|
|
6474
6887
|
function readComposer() {
|
|
6475
|
-
|
|
6476
|
-
try {
|
|
6477
|
-
user = readJson("state.json");
|
|
6478
|
-
} catch (error) {
|
|
6479
|
-
console.warn(`invalid ${stateFile}, using the default state: ${error}`);
|
|
6480
|
-
return structuredClone(defaults.composer);
|
|
6481
|
-
}
|
|
6482
|
-
if (user === null) return structuredClone(defaults.composer);
|
|
6483
|
-
const result = stateSchema.safeParse(user);
|
|
6484
|
-
if (!result.success) {
|
|
6485
|
-
console.warn(`invalid ${stateFile}, using the default state:\n${z.prettifyError(result.error)}`);
|
|
6486
|
-
return structuredClone(defaults.composer);
|
|
6487
|
-
}
|
|
6488
|
-
return result.data.composer;
|
|
6888
|
+
return readSettings("state.json", stateSchema, defaults).composer;
|
|
6489
6889
|
}
|
|
6490
6890
|
function saveComposer(value) {
|
|
6491
6891
|
const result = stateSchema.safeParse({ composer: value });
|
|
@@ -6510,7 +6910,8 @@ const handlers = {
|
|
|
6510
6910
|
projects: projects$1(),
|
|
6511
6911
|
sessions: sessions(),
|
|
6512
6912
|
providers: providerStatuses(),
|
|
6513
|
-
activity: activity()
|
|
6913
|
+
activity: activity(),
|
|
6914
|
+
terminals: terminalCount(GLOBAL_TERMINALS)
|
|
6514
6915
|
};
|
|
6515
6916
|
},
|
|
6516
6917
|
"config.get": () => configInfo(),
|
|
@@ -6542,6 +6943,7 @@ const handlers = {
|
|
|
6542
6943
|
return null;
|
|
6543
6944
|
},
|
|
6544
6945
|
"projects.reorder": (params) => reorderProjects(params.ids),
|
|
6946
|
+
"projects.logoFiles": (params) => logoFiles(projectPath(params.id)),
|
|
6545
6947
|
"directories.list": (params) => directories(params.path),
|
|
6546
6948
|
"sessions.update": async (params) => {
|
|
6547
6949
|
try {
|
|
@@ -6630,9 +7032,25 @@ const handlers = {
|
|
|
6630
7032
|
},
|
|
6631
7033
|
capabilities: (params) => capabilities(params.provider, params.accountId, params.projectId === null ? os.homedir() : projectPath(params.projectId)),
|
|
6632
7034
|
"files.list": (params) => listFiles(projectPath(params.projectId)),
|
|
7035
|
+
"files.entries": (params) => listEntries(projectPath(params.projectId)),
|
|
6633
7036
|
"files.read": (params) => readProjectFile(projectPath(params.projectId), params.path),
|
|
7037
|
+
"files.write": (params) => {
|
|
7038
|
+
writeProjectFile(projectPath(params.projectId), params.path, params.content);
|
|
7039
|
+
return null;
|
|
7040
|
+
},
|
|
7041
|
+
"files.create": (params) => {
|
|
7042
|
+
createProjectEntry(projectPath(params.projectId), params.path, params.folder);
|
|
7043
|
+
return null;
|
|
7044
|
+
},
|
|
7045
|
+
"files.rename": (params) => {
|
|
7046
|
+
renameProjectEntry(projectPath(params.projectId), params.path, params.to);
|
|
7047
|
+
return null;
|
|
7048
|
+
},
|
|
7049
|
+
"files.delete": (params) => {
|
|
7050
|
+
deleteProjectEntry(projectPath(params.projectId), params.path);
|
|
7051
|
+
return null;
|
|
7052
|
+
},
|
|
6634
7053
|
"files.media": (params) => linkProjectMedia(projectPath(params.projectId), params.path),
|
|
6635
|
-
"files.download": (params) => linkProjectDownload(projectPath(params.projectId), params.path),
|
|
6636
7054
|
"files.upload": (params) => saveUpload(params.name, Buffer.from(params.data, "base64")),
|
|
6637
7055
|
"files.browse": () => browseToken,
|
|
6638
7056
|
"browser.proxy": (params) => proxyPort(params.origin),
|
|
@@ -6640,6 +7058,16 @@ const handlers = {
|
|
|
6640
7058
|
"git.status": (params) => gitRepos(projectPath(params.projectId)),
|
|
6641
7059
|
"git.diff": (params) => projectDiff(projectPath(params.projectId)),
|
|
6642
7060
|
"git.commit": (params) => commit(path.join(projectPath(params.projectId), params.repo), params.message, params.push),
|
|
7061
|
+
"git.changes": (params) => sourceControl(projectPath(params.projectId)),
|
|
7062
|
+
"git.show": (params) => showFile(path.join(projectPath(params.projectId), params.repo), params.ref, params.path),
|
|
7063
|
+
"git.history": (params) => history(path.join(projectPath(params.projectId), params.repo), params.limit),
|
|
7064
|
+
"git.commitFiles": (params) => commitFiles(path.join(projectPath(params.projectId), params.repo), params.hash, params.parent),
|
|
7065
|
+
"git.message": (params) => commitMessage(path.join(projectPath(params.projectId), params.repo), params.staged),
|
|
7066
|
+
"git.undoCommit": (params) => undoCommit(path.join(projectPath(params.projectId), params.repo)),
|
|
7067
|
+
"git.run": async (params) => {
|
|
7068
|
+
await runAction(path.join(projectPath(params.projectId), params.repo), params.action);
|
|
7069
|
+
return null;
|
|
7070
|
+
},
|
|
6643
7071
|
"git.push": async (params) => {
|
|
6644
7072
|
await push$1(path.join(projectPath(params.projectId), params.repo));
|
|
6645
7073
|
return null;
|
|
@@ -6967,7 +7395,6 @@ function serveMedia(token, request, response) {
|
|
|
6967
7395
|
}
|
|
6968
7396
|
response.setHeader("access-control-allow-origin", "*");
|
|
6969
7397
|
response.setHeader("content-type", link.mediaType);
|
|
6970
|
-
if (link.download !== null) response.setHeader("content-disposition", `attachment; filename*=UTF-8''${encodeURIComponent(link.download)}`);
|
|
6971
7398
|
response.setHeader("cache-control", "private, max-age=3600");
|
|
6972
7399
|
response.setHeader("content-security-policy", "sandbox");
|
|
6973
7400
|
response.setHeader("x-content-type-options", "nosniff");
|