offsite 0.0.46 → 0.0.48

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.
Files changed (126) hide show
  1. package/README.md +1 -10
  2. package/apps/server/dist/main.mjs +609 -200
  3. package/apps/server/dist/main.mjs.map +1 -1
  4. package/apps/web/dist/assets/abap-08VXUWAP.js +1 -0
  5. package/apps/web/dist/assets/accounts-C2CqWFbM.js +513 -0
  6. package/apps/web/dist/assets/apex-BWPQTe0t.js +1 -0
  7. package/apps/web/dist/assets/audio-player-BE-oXEaT.js +1 -0
  8. package/apps/web/dist/assets/azcli-Bc_sGQ0U.js +1 -0
  9. package/apps/web/dist/assets/bat-i0X4ZdIN.js +1 -0
  10. package/apps/web/dist/assets/bicep-B5-_aFwp.js +2 -0
  11. package/apps/web/dist/assets/button-Dhs1rqob.js +1 -0
  12. package/apps/web/dist/assets/cameligo-DMUM7wLl.js +1 -0
  13. package/apps/web/dist/assets/clojure-Cm7r79vr.js +1 -0
  14. package/apps/web/dist/assets/codicon-DF1abBS2.ttf +0 -0
  15. package/apps/web/dist/assets/coffee-Ba7i2nA0.js +1 -0
  16. package/apps/web/dist/assets/cpp-C7h46wYY.js +1 -0
  17. package/apps/web/dist/assets/csharp-BKxtCVv1.js +1 -0
  18. package/apps/web/dist/assets/csp-bTuwJoIa.js +1 -0
  19. package/apps/web/dist/assets/css-DIMkf-bt.js +3 -0
  20. package/apps/web/dist/assets/cypher-CVaqCwHa.js +1 -0
  21. package/apps/web/dist/assets/dart-onAF5SnQ.js +1 -0
  22. package/apps/web/dist/assets/{preview-dialog-jk_R2034.js → dialog-Bnoy9Qj1.js} +12 -12
  23. package/apps/web/dist/assets/dist-BrDshb6Z.js +153 -0
  24. package/apps/web/dist/assets/dist-DUvKG9KH.js +1 -0
  25. package/apps/web/dist/assets/dockerfile-DZFCIeNp.js +1 -0
  26. package/apps/web/dist/assets/ecl-D05T4iGw.js +1 -0
  27. package/apps/web/dist/assets/editor-DjpsE8to.css +1 -0
  28. package/apps/web/dist/assets/editor-dialog-BNFI__d8.css +1 -0
  29. package/apps/web/dist/assets/editor-dialog-DEjErrRg.js +52 -0
  30. package/apps/web/dist/assets/editor.api-4LH2ijIL.js +908 -0
  31. package/apps/web/dist/assets/editor.worker-BmdWdrHZ.js +33 -0
  32. package/apps/web/dist/assets/editor.worker-D2ouzCu8.js +1 -0
  33. package/apps/web/dist/assets/elixir-6RTg0lbw.js +1 -0
  34. package/apps/web/dist/assets/flow9-C5_-GSwl.js +1 -0
  35. package/apps/web/dist/assets/freemarker2-6NYS9HqW.js +3 -0
  36. package/apps/web/dist/assets/fsharp-C8Ef5oNN.js +1 -0
  37. package/apps/web/dist/assets/go-C-y9NEjX.js +1 -0
  38. package/apps/web/dist/assets/graphql-fmXr3nnJ.js +1 -0
  39. package/apps/web/dist/assets/handlebars-DOeNnhTE.js +1 -0
  40. package/apps/web/dist/assets/hcl-CpzslTdj.js +1 -0
  41. package/apps/web/dist/assets/html-BwGTVsqZ.js +1 -0
  42. package/apps/web/dist/assets/image-viewer-DHukGxB1.js +1 -0
  43. package/apps/web/dist/assets/index-1yFC_FF_.js +9 -0
  44. package/apps/web/dist/assets/index-cgL3vsYO.css +2 -0
  45. package/apps/web/dist/assets/ini-sBoK_t0W.js +1 -0
  46. package/apps/web/dist/assets/java-BEtHBSE6.js +1 -0
  47. package/apps/web/dist/assets/javascript-Dj-Vzbp-.js +1 -0
  48. package/apps/web/dist/assets/julia-Bri6UV-V.js +1 -0
  49. package/apps/web/dist/assets/kotlin-BOotOW0E.js +1 -0
  50. package/apps/web/dist/assets/less-B9JPFI3C.js +2 -0
  51. package/apps/web/dist/assets/lexon-CfSJPG6W.js +1 -0
  52. package/apps/web/dist/assets/liquid-DDSU3ltH.js +1 -0
  53. package/apps/web/dist/assets/lua-CsQS60Ue.js +1 -0
  54. package/apps/web/dist/assets/m3-D-oSqn_W.js +1 -0
  55. package/apps/web/dist/assets/markdown-Cimd5fb3.js +1 -0
  56. package/apps/web/dist/assets/mdx-YllfWysm.js +1 -0
  57. package/apps/web/dist/assets/mips-CIPQ_RoX.js +1 -0
  58. package/apps/web/dist/assets/msdax-DauUninz.js +1 -0
  59. package/apps/web/dist/assets/mysql-SOo6toE5.js +1 -0
  60. package/apps/web/dist/assets/objective-c-FvmIjYaQ.js +1 -0
  61. package/apps/web/dist/assets/pascal-DrH0SRf2.js +1 -0
  62. package/apps/web/dist/assets/pascaligo-D-ptJ9y-.js +1 -0
  63. package/apps/web/dist/assets/perl-oz_6vUea.js +1 -0
  64. package/apps/web/dist/assets/pgsql-DTj74zXo.js +1 -0
  65. package/apps/web/dist/assets/php-nr791fC2.js +1 -0
  66. package/apps/web/dist/assets/pla-CopQ2nXW.js +1 -0
  67. package/apps/web/dist/assets/postiats-43DmfD33.js +1 -0
  68. package/apps/web/dist/assets/powerquery-D3hlyOfw.js +1 -0
  69. package/apps/web/dist/assets/powershell-DmHpPYUd.js +1 -0
  70. package/apps/web/dist/assets/preview-dialog-dl8oUCEx.js +1 -0
  71. package/apps/web/dist/assets/protobuf-C531GsRP.js +2 -0
  72. package/apps/web/dist/assets/pug-Z5eAx3Zn.js +1 -0
  73. package/apps/web/dist/assets/python-Bsfjs4Fs.js +1 -0
  74. package/apps/web/dist/assets/qsharp-DkqhCAOL.js +1 -0
  75. package/apps/web/dist/assets/r-BwWrilGY.js +1 -0
  76. package/apps/web/dist/assets/razor-3MUz77_n.js +1 -0
  77. package/apps/web/dist/assets/{react-99wk3OSj.js → react-DHkIXOR_.js} +1 -1
  78. package/apps/web/dist/assets/redis-ClamHrr6.js +1 -0
  79. package/apps/web/dist/assets/redshift-DT7zqm-g.js +1 -0
  80. package/apps/web/dist/assets/restructuredtext-BYgofb2h.js +1 -0
  81. package/apps/web/dist/assets/ruby-DezsRK8O.js +1 -0
  82. package/apps/web/dist/assets/rust-DdL9SqIa.js +1 -0
  83. package/apps/web/dist/assets/sb-CcwsVR0C.js +1 -0
  84. package/apps/web/dist/assets/scala-DHpiXF5c.js +1 -0
  85. package/apps/web/dist/assets/scheme-BeGwcela.js +1 -0
  86. package/apps/web/dist/assets/scss-gp-XZpBa.js +3 -0
  87. package/apps/web/dist/assets/shell-CC2rA5mh.js +1 -0
  88. package/apps/web/dist/assets/solidity-BEEn4gHE.js +1 -0
  89. package/apps/web/dist/assets/sophia-CRfGWb83.js +1 -0
  90. package/apps/web/dist/assets/sparql-D_Lu-MrJ.js +1 -0
  91. package/apps/web/dist/assets/sql-NEE52Syq.js +1 -0
  92. package/apps/web/dist/assets/st-DbInun42.js +1 -0
  93. package/apps/web/dist/assets/swift-Bxkupp3x.js +1 -0
  94. package/apps/web/dist/assets/systemverilog-Bz4Y3fRF.js +1 -0
  95. package/apps/web/dist/assets/tcl-DISqw1ZD.js +1 -0
  96. package/apps/web/dist/assets/tooltip-CGSEdyeY.js +1 -0
  97. package/apps/web/dist/assets/twig-De2hgUGE.js +1 -0
  98. package/apps/web/dist/assets/typescript-CfCJo9p3.js +1 -0
  99. package/apps/web/dist/assets/typescript-fNZ3iur6.js +1 -0
  100. package/apps/web/dist/assets/typespec-B8J7ngcE.js +1 -0
  101. package/apps/web/dist/assets/utils-KVq36YOZ.js +1 -0
  102. package/apps/web/dist/assets/vb-DV3o63ZY.js +1 -0
  103. package/apps/web/dist/assets/video-viewer-DIbmaY7P.js +1 -0
  104. package/apps/web/dist/assets/wgsl-DpFanUEy.js +298 -0
  105. package/apps/web/dist/assets/xml-S-oLW5B5.js +1 -0
  106. package/apps/web/dist/assets/yaml-DlmqY7sN.js +1 -0
  107. package/apps/web/dist/assets/zoomable-image-DzvW3FEl.js +1 -0
  108. package/apps/web/dist/assets/zoomable-image-HHk3X4rG.js +41 -0
  109. package/apps/web/dist/index.html +8 -8
  110. package/apps/web/dist/sw.js +1 -1
  111. package/package.json +2 -1
  112. package/apps/web/dist/assets/audio-player-BBuXiV2_.js +0 -1
  113. package/apps/web/dist/assets/button-BF3wjXXJ.js +0 -1
  114. package/apps/web/dist/assets/dist-CkDQKgh1.js +0 -153
  115. package/apps/web/dist/assets/dist-gQuykix2.js +0 -1
  116. package/apps/web/dist/assets/image-viewer-4LhMaTO_.js +0 -1
  117. package/apps/web/dist/assets/index-CixqWNf5.js +0 -782
  118. package/apps/web/dist/assets/index-D3NpzVu2.css +0 -2
  119. package/apps/web/dist/assets/jsx-runtime-DKj5X9O4.js +0 -1
  120. package/apps/web/dist/assets/tooltip-CiWlkHa6.js +0 -1
  121. package/apps/web/dist/assets/utils-PtDt7m8t.js +0 -1
  122. package/apps/web/dist/assets/video-player-Bcow2OC5.js +0 -1
  123. package/apps/web/dist/assets/video-player-hDoAwJRk.js +0 -1
  124. package/apps/web/dist/assets/video-viewer-CJ129jI3.js +0 -1
  125. package/apps/web/dist/assets/zoomable-image-CZlWBm_z.js +0 -41
  126. 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: [
@@ -2599,6 +2618,7 @@ const sessions$1 = snakeCase.table("sessions", {
2599
2618
  status: text({ enum: [
2600
2619
  "active",
2601
2620
  "pinned",
2621
+ "snoozed",
2602
2622
  "minimized"
2603
2623
  ] }).notNull()
2604
2624
  });
@@ -2624,13 +2644,201 @@ const pushSubscriptions = snakeCase.table("push_subscriptions", {
2624
2644
  server: text().notNull()
2625
2645
  });
2626
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
2627
2835
  //#region src/services/git.ts
2628
2836
  const DIFF_LIMIT = 8e4;
2629
2837
  const UNTRACKED_LIMIT = 100;
2630
2838
  const NEW_FILE_LIMIT = 262144;
2631
2839
  const REPO_DEPTH = 3;
2632
2840
  const FETCH_TIMEOUT = 6e4;
2633
- const run$1 = promisify(execFile);
2841
+ const run$2 = promisify(execFile);
2634
2842
  const PULL_REQUEST_INSTRUCTIONS = `You write GitHub pull requests as Markdown.
2635
2843
 
2636
2844
  ## TITLE RULES
@@ -2666,7 +2874,7 @@ async function gitRepos(cwd) {
2666
2874
  })));
2667
2875
  }
2668
2876
  async function commit(cwd, message, andPush) {
2669
- const text = message.trim() === "" ? await commitMessage(cwd) : message;
2877
+ const text = message.trim() === "" ? await commitMessage(cwd, false) : message;
2670
2878
  await git(cwd, ["add", "--all"]);
2671
2879
  await git(cwd, [
2672
2880
  "commit",
@@ -2717,7 +2925,7 @@ async function sync(cwd) {
2717
2925
  async function fetchProject(cwd) {
2718
2926
  await Promise.all((await projectRepos(cwd)).map(async (repo) => {
2719
2927
  try {
2720
- await run$1("git", ["fetch"], {
2928
+ await run$2("git", ["fetch"], {
2721
2929
  cwd: path.join(cwd, repo),
2722
2930
  env: {
2723
2931
  ...process.env,
@@ -2748,7 +2956,7 @@ async function switchBranch(cwd, branch, create) {
2748
2956
  async function pullRequestBranches(cwd) {
2749
2957
  const head = (await gitStatus(cwd)).branch;
2750
2958
  if (head === null) throw new Error("Not on a branch");
2751
- const base = (await run$1("gh", [
2959
+ const base = (await run$2("gh", [
2752
2960
  "repo",
2753
2961
  "view",
2754
2962
  "--json",
@@ -2773,7 +2981,7 @@ async function preparePullRequest(cwd, base, commitChanges) {
2773
2981
  if (commitChanges) await commit(cwd, "", true);
2774
2982
  const status = await gitStatus(cwd);
2775
2983
  if (status.upstream === null || status.ahead > 0) await push$1(cwd);
2776
- const open = (await run$1("gh", [
2984
+ const open = (await run$2("gh", [
2777
2985
  "pr",
2778
2986
  "list",
2779
2987
  "--head",
@@ -2820,7 +3028,7 @@ async function preparePullRequest(cwd, base, commitChanges) {
2820
3028
  };
2821
3029
  }
2822
3030
  async function createPullRequest(cwd, request) {
2823
- const url = (await run$1("gh", [
3031
+ const url = (await run$2("gh", [
2824
3032
  "pr",
2825
3033
  "create",
2826
3034
  "--title",
@@ -2837,8 +3045,13 @@ async function createPullRequest(cwd, request) {
2837
3045
  if (url === void 0) throw new Error("gh did not return a pull request URL");
2838
3046
  return { url };
2839
3047
  }
2840
- async function commitMessage(cwd) {
2841
- const diff = (await repoDiff(cwd, "")).slice(0, DIFF_LIMIT);
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);
2842
3055
  if (diff === "") throw new Error("There is nothing to commit");
2843
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}`);
2844
3057
  }
@@ -2876,7 +3089,7 @@ async function projectDiff(cwd) {
2876
3089
  return (await Promise.all(repos.map((repo) => repoDiff(path.join(cwd, repo), repo === "" ? "" : `${repo}/`)))).join("");
2877
3090
  }
2878
3091
  function git(cwd, args) {
2879
- return run$1("git", args, {
3092
+ return run$2("git", args, {
2880
3093
  cwd,
2881
3094
  maxBuffer: 67108864
2882
3095
  });
@@ -3001,13 +3214,27 @@ function newFilePatch(cwd, file, prefix) {
3001
3214
  if (lines.at(-1) === "") lines.pop();
3002
3215
  return `${header}@@ -0,0 +1,${lines.length} @@\n${lines.map((line) => `+${line}\n`).join("")}`;
3003
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;
3004
3232
  const LOGO_FILES = /* @__PURE__ */ new Map([
3005
3233
  [".svg", "image/svg+xml"],
3006
3234
  [".png", "image/png"],
3007
3235
  [".webp", "image/webp"],
3008
3236
  [".jpg", "image/jpeg"],
3009
3237
  [".jpeg", "image/jpeg"],
3010
- [".gif", "image/gif"],
3011
3238
  [".ico", "image/x-icon"]
3012
3239
  ]);
3013
3240
  const PREFERRED = [
@@ -3030,11 +3257,42 @@ async function findLogo(folder) {
3030
3257
  for (const { file } of candidates) {
3031
3258
  const full = path.join(folder, file);
3032
3259
  if (!existsSync(full)) continue;
3033
- const logo = `data:${LOGO_FILES.get(path.extname(file).toLowerCase())};base64,${(await readFile(full)).toString("base64")}`;
3034
- if (logo.length <= 524288) return logo;
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
+ }
3035
3267
  }
3036
3268
  return null;
3037
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
+ }
3038
3296
  function logoRank(file) {
3039
3297
  const preferred = PREFERRED.indexOf(path.basename(file).toLowerCase());
3040
3298
  if (preferred !== -1) return preferred;
@@ -3046,15 +3304,6 @@ function logoRank(file) {
3046
3304
  }
3047
3305
  //#endregion
3048
3306
  //#region src/services/projects.ts
3049
- const LOGO_TYPES = [
3050
- "image/png",
3051
- "image/jpeg",
3052
- "image/gif",
3053
- "image/webp",
3054
- "image/svg+xml",
3055
- "image/x-icon",
3056
- "image/vnd.microsoft.icon"
3057
- ];
3058
3307
  function projects$1() {
3059
3308
  return db.select({
3060
3309
  id: projects$2.id,
@@ -3084,16 +3333,13 @@ async function addProject(input) {
3084
3333
  if (project === void 0) throw new Error(`Could not add ${folder}`);
3085
3334
  return project;
3086
3335
  }
3087
- function updateProject(id, patch) {
3336
+ async function updateProject(id, patch) {
3088
3337
  if (patch.name !== void 0) {
3089
3338
  const name = patch.name.trim();
3090
3339
  if (name === "") throw new Error("Project name cannot be empty");
3091
3340
  db.update(projects$2).set({ name }).where(eq(projects$2.id, id)).run();
3092
3341
  }
3093
- if (patch.logo !== void 0) {
3094
- if (patch.logo !== null) checkLogo(patch.logo);
3095
- db.update(projects$2).set({ logo: patch.logo }).where(eq(projects$2.id, id)).run();
3096
- }
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();
3097
3343
  publish$1();
3098
3344
  return null;
3099
3345
  }
@@ -3131,11 +3377,6 @@ function publish$1() {
3131
3377
  projects: projects$1()
3132
3378
  });
3133
3379
  }
3134
- function checkLogo(logo) {
3135
- const type = /^data:([^;,]+);base64,/.exec(logo)?.[1];
3136
- if (type === void 0 || !LOGO_TYPES.includes(type)) throw new Error("Use a PNG, JPEG, GIF, WebP, SVG or ICO image");
3137
- if (logo.length > 524288) throw new Error("Use an image smaller than 384 KB");
3138
- }
3139
3380
  function expandHome(input) {
3140
3381
  if (input === "~") return os.homedir();
3141
3382
  if (input.startsWith("~/")) return path.join(os.homedir(), input.slice(2));
@@ -3224,21 +3465,6 @@ async function find(provider, paths) {
3224
3465
  });
3225
3466
  }
3226
3467
  }
3227
- function encodeFrame(kind, id, data) {
3228
- const frame = new Uint8Array(5 + data.length);
3229
- frame[0] = kind;
3230
- new DataView(frame.buffer).setUint32(1, id);
3231
- frame.set(data, 5);
3232
- return frame;
3233
- }
3234
- function decodeFrame(frame) {
3235
- if (frame.length < 5) throw new Error("terminal frame is too short");
3236
- return {
3237
- kind: frame[0],
3238
- id: new DataView(frame.buffer, frame.byteOffset, frame.byteLength).getUint32(1),
3239
- data: frame.subarray(5)
3240
- };
3241
- }
3242
3468
  //#endregion
3243
3469
  //#region src/services/push.ts
3244
3470
  const TITLE_LIMIT = 100;
@@ -3704,7 +3930,7 @@ function writeScript(target, source) {
3704
3930
  }
3705
3931
  //#endregion
3706
3932
  //#region src/services/terminal/process.ts
3707
- const run = promisify(execFile);
3933
+ const run$1 = promisify(execFile);
3708
3934
  const iterm2Version = "3.6.8";
3709
3935
  const ghosttyVersion = "1.3.1";
3710
3936
  const shellNames = process.platform === "win32" ? [
@@ -3765,7 +3991,7 @@ function startShell(cwd) {
3765
3991
  async function currentDirectory(pid, reported) {
3766
3992
  if (process.platform === "linux") return readlink(`/proc/${pid}/cwd`);
3767
3993
  if (process.platform === "darwin") {
3768
- const { stdout } = await run("lsof", [
3994
+ const { stdout } = await run$1("lsof", [
3769
3995
  "-a",
3770
3996
  "-d",
3771
3997
  "cwd",
@@ -3785,7 +4011,7 @@ async function currentDirectory(pid, reported) {
3785
4011
  }
3786
4012
  async function busyProgram(pid) {
3787
4013
  if (process.platform === "win32") {
3788
- const { stdout } = await run("powershell.exe", [
4014
+ const { stdout } = await run$1("powershell.exe", [
3789
4015
  "-NoProfile",
3790
4016
  "-Command",
3791
4017
  `Get-CimInstance Win32_Process -Filter "ParentProcessId=${pid}" | Select-Object -ExpandProperty Name`
@@ -3808,7 +4034,7 @@ async function foregroundGroup(pid) {
3808
4034
  const text = await readFile(`/proc/${pid}/stat`, "utf8");
3809
4035
  return Number(text.slice(text.lastIndexOf(")") + 2).split(" ")[5]);
3810
4036
  }
3811
- const { stdout } = await run("ps", [
4037
+ const { stdout } = await run$1("ps", [
3812
4038
  "-o",
3813
4039
  "tpgid=",
3814
4040
  "-p",
@@ -3818,7 +4044,7 @@ async function foregroundGroup(pid) {
3818
4044
  }
3819
4045
  async function programName(pid) {
3820
4046
  if (process.platform === "linux") return (await readFile(`/proc/${pid}/comm`, "utf8")).trimEnd();
3821
- const { stdout } = await run("ps", [
4047
+ const { stdout } = await run$1("ps", [
3822
4048
  "-o",
3823
4049
  "comm=",
3824
4050
  "-p",
@@ -3887,7 +4113,7 @@ function executable$1(name) {
3887
4113
  //#endregion
3888
4114
  //#region src/services/terminal/terminal.ts
3889
4115
  const HISTORY_LIMIT = 2097152;
3890
- const OUTPUT_LIMIT$1 = 1048576;
4116
+ const OUTPUT_LIMIT = 1048576;
3891
4117
  const STALL_LIMIT = 1e4;
3892
4118
  const SILENCE_LIMIT$1 = 9e4;
3893
4119
  const KILL_DELAY = 3e3;
@@ -4116,7 +4342,7 @@ function output(id, terminal, data) {
4116
4342
  }
4117
4343
  for (const connection of connections.values()) forward(connection, id, false, data);
4118
4344
  scan(id, terminal, data);
4119
- if ([...connections.values()].some((connection) => connection.client.socket.bufferedAmount > OUTPUT_LIMIT$1)) {
4345
+ if ([...connections.values()].some((connection) => connection.client.socket.bufferedAmount > OUTPUT_LIMIT)) {
4120
4346
  terminal.pty.pause();
4121
4347
  paused.add(terminal.pty);
4122
4348
  draining ??= drain();
@@ -4128,7 +4354,7 @@ function drain() {
4128
4354
  let full = false;
4129
4355
  for (const connection of connections.values()) {
4130
4356
  const queued = connection.client.socket.bufferedAmount;
4131
- if (queued <= OUTPUT_LIMIT$1) continue;
4357
+ if (queued <= OUTPUT_LIMIT) continue;
4132
4358
  const now = Date.now();
4133
4359
  const last = progress.get(connection) ?? {
4134
4360
  queued: Infinity,
@@ -4204,6 +4430,10 @@ function publish() {
4204
4430
  layout: layout()
4205
4431
  };
4206
4432
  for (const connection of connections.values()) send$1(connection, event);
4433
+ broadcast({
4434
+ type: "terminals",
4435
+ count: terminalCount(GLOBAL_TERMINALS)
4436
+ });
4207
4437
  }
4208
4438
  function forward(connection, id, replay, data) {
4209
4439
  connection.client.socket.send(encodeFrame(replay ? 3 : 2, id, data));
@@ -4295,7 +4525,7 @@ function withStatus(items) {
4295
4525
  return {
4296
4526
  ...item,
4297
4527
  updatedAt,
4298
- 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
4299
4529
  };
4300
4530
  });
4301
4531
  }
@@ -4325,7 +4555,7 @@ function recordInteraction(id) {
4325
4555
  target: sessions$1.sessionId,
4326
4556
  set: {
4327
4557
  interactedAt: Date.now(),
4328
- 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`
4329
4559
  }
4330
4560
  }).run();
4331
4561
  }
@@ -4454,7 +4684,7 @@ async function updateSession(id, patch) {
4454
4684
  const session = all.find((item) => item.id === id);
4455
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);
4456
4686
  } else if (patch.status !== void 0) {
4457
- const set = patch.status === "active" ? {
4687
+ const set = patch.status === "active" || patch.status === "snoozed" ? {
4458
4688
  status: patch.status,
4459
4689
  interactedAt: Date.now()
4460
4690
  } : { status: patch.status };
@@ -5583,7 +5813,7 @@ async function image(sessionId, uuid, index) {
5583
5813
  data: block.data
5584
5814
  };
5585
5815
  }
5586
- async function history(sessionId, cwd) {
5816
+ async function history$1(sessionId, cwd) {
5587
5817
  const found = runs.get(sessionId);
5588
5818
  const run = found?.provider === "claude" ? found : void 0;
5589
5819
  const [disk, session] = await Promise.all([getSessionMessages(sessionId, { dir: cwd }), loadSession(sessionId)]);
@@ -6014,142 +6244,6 @@ var Run = class {
6014
6244
  }
6015
6245
  };
6016
6246
  //#endregion
6017
- //#region src/services/files.ts
6018
- const FILE_LIMIT = 2e4;
6019
- const OUTPUT_LIMIT = 262144;
6020
- const READ_LIMIT = 1048576;
6021
- const MEDIA_LINK_LIFETIME = 36e5;
6022
- const browseToken = randomBytes(32).toString("base64url");
6023
- const mediaLinks = /* @__PURE__ */ new Map();
6024
- const skipped = /* @__PURE__ */ new Set([
6025
- "node_modules",
6026
- ".git",
6027
- ".DS_Store",
6028
- "dist",
6029
- "target",
6030
- ".next",
6031
- "__pycache__",
6032
- ".venv"
6033
- ]);
6034
- async function listFiles(cwd) {
6035
- const files = [];
6036
- const pending = [""];
6037
- while (pending.length > 0 && files.length < FILE_LIMIT) {
6038
- const dir = pending.pop() ?? "";
6039
- let entries;
6040
- try {
6041
- entries = await readdir(path.join(cwd, dir), { withFileTypes: true });
6042
- } catch {
6043
- continue;
6044
- }
6045
- for (const entry of entries) {
6046
- if (skipped.has(entry.name)) continue;
6047
- const relative = dir === "" ? entry.name : `${dir}/${entry.name}`;
6048
- if (entry.isDirectory()) pending.push(relative);
6049
- if (entry.isFile()) files.push(relative);
6050
- if (entry.isSymbolicLink()) {
6051
- if ((await stat(path.join(cwd, relative)).catch(() => null))?.isFile() === true) files.push(relative);
6052
- }
6053
- }
6054
- }
6055
- return files.slice(0, FILE_LIMIT).sort();
6056
- }
6057
- function readProjectFile(cwd, file) {
6058
- const full = resolveProjectFile(cwd, file);
6059
- const stats = statSync(full);
6060
- if (!stats.isFile()) throw new Error(`${file} is not a file`);
6061
- if (stats.size > READ_LIMIT) return {
6062
- kind: "large",
6063
- size: stats.size,
6064
- limit: READ_LIMIT
6065
- };
6066
- const data = readFileSync(full);
6067
- const mediaType = mediaTypes.get(path.extname(full).toLowerCase());
6068
- if (mediaType?.startsWith("image/") === true) return {
6069
- kind: "image",
6070
- size: stats.size,
6071
- mediaType,
6072
- data: data.toString("base64")
6073
- };
6074
- if (data.includes(0)) return {
6075
- kind: "binary",
6076
- size: stats.size
6077
- };
6078
- return {
6079
- kind: "text",
6080
- size: stats.size,
6081
- content: data.toString("utf8")
6082
- };
6083
- }
6084
- function linkProjectMedia(cwd, file) {
6085
- const resolved = path.resolve(realpathSync(cwd), file);
6086
- if (!existsSync(resolved)) throw new Error(`${file} does not exist`);
6087
- const full = realpathSync(resolved);
6088
- const mediaType = mediaTypes.get(path.extname(full).toLowerCase());
6089
- if (mediaType === void 0) throw new Error(`${file} is not an image, video or audio file`);
6090
- return addLink(full, mediaType, null);
6091
- }
6092
- function linkProjectDownload(cwd, file) {
6093
- return addLink(resolveProjectFile(cwd, file), "application/octet-stream", path.basename(file));
6094
- }
6095
- function addLink(file, mediaType, download) {
6096
- for (const [token, link] of mediaLinks) if (link.expiresAt <= Date.now()) mediaLinks.delete(token);
6097
- const token = randomBytes(32).toString("base64url");
6098
- mediaLinks.set(token, {
6099
- file,
6100
- mediaType,
6101
- download,
6102
- expiresAt: Date.now() + MEDIA_LINK_LIFETIME
6103
- });
6104
- return token;
6105
- }
6106
- function findMediaLink(token) {
6107
- if (token === null) return null;
6108
- const link = mediaLinks.get(token);
6109
- if (link === void 0 || link.expiresAt <= Date.now()) return null;
6110
- return link;
6111
- }
6112
- function saveUpload(name, data) {
6113
- const dir = path.join(home, "uploads", randomUUID());
6114
- mkdirSync(dir, { recursive: true });
6115
- const file = path.join(dir, path.basename(name));
6116
- writeFileSync(file, data);
6117
- return file;
6118
- }
6119
- async function taskOutput(sessionId, file) {
6120
- const tasks = path.dirname(file);
6121
- 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");
6122
- let stats;
6123
- try {
6124
- stats = await lstat(file);
6125
- } catch (error) {
6126
- if (hasCode(error, "ENOENT")) return null;
6127
- throw error;
6128
- }
6129
- if (!stats.isFile()) throw new Error("Background task output is not a regular file");
6130
- const start = Math.max(0, stats.size - OUTPUT_LIMIT);
6131
- const handle = await open(file, "r");
6132
- try {
6133
- const buffer = Buffer.alloc(stats.size - start);
6134
- const read = await handle.read(buffer, 0, buffer.length, start);
6135
- return {
6136
- text: buffer.subarray(0, read.bytesRead).toString("utf8"),
6137
- truncated: start > 0
6138
- };
6139
- } finally {
6140
- await handle.close();
6141
- }
6142
- }
6143
- function resolveProjectFile(cwd, file) {
6144
- const root = realpathSync(cwd);
6145
- const resolved = path.resolve(root, file);
6146
- if (!existsSync(resolved)) throw new Error(`${file} does not exist`);
6147
- const full = realpathSync(resolved);
6148
- const relative = path.relative(root, full);
6149
- if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) throw new Error(`${file} is outside the project`);
6150
- return full;
6151
- }
6152
- //#endregion
6153
6247
  //#region src/services/proxy.ts
6154
6248
  const proxies = /* @__PURE__ */ new Map();
6155
6249
  const pageScriptPath = "/__offsite/browser.js";
@@ -6293,7 +6387,7 @@ function stopAll() {
6293
6387
  }
6294
6388
  function sessionHistory(sessionId, cwd) {
6295
6389
  switch (providerOf(sessionId)) {
6296
- case "claude": return history(sessionId, cwd);
6390
+ case "claude": return history$1(sessionId, cwd);
6297
6391
  case "codex": return codexHistory(sessionId);
6298
6392
  case "opencode": return openCodeHistory(sessionId, cwd);
6299
6393
  }
@@ -6345,7 +6439,7 @@ async function rewindNow(request) {
6345
6439
  resumeAt: disk[index - 1].uuid,
6346
6440
  settings: request.settings
6347
6441
  });
6348
- publish$2(sessionId, await history(sessionId, cwd));
6442
+ publish$2(sessionId, await history$1(sessionId, cwd));
6349
6443
  return result;
6350
6444
  }
6351
6445
  async function open$1(client, sessionId, projectId) {
@@ -6438,6 +6532,294 @@ async function changedElsewhere(run, cwd) {
6438
6532
  return last !== void 0 && !run.messages.some((item) => item.uuid === last);
6439
6533
  }
6440
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
6441
6823
  //#region src/services/terminal/paste.ts
6442
6824
  const pasteTypes = {
6443
6825
  "image/png": "png",
@@ -6528,7 +6910,8 @@ const handlers = {
6528
6910
  projects: projects$1(),
6529
6911
  sessions: sessions(),
6530
6912
  providers: providerStatuses(),
6531
- activity: activity()
6913
+ activity: activity(),
6914
+ terminals: terminalCount(GLOBAL_TERMINALS)
6532
6915
  };
6533
6916
  },
6534
6917
  "config.get": () => configInfo(),
@@ -6560,6 +6943,7 @@ const handlers = {
6560
6943
  return null;
6561
6944
  },
6562
6945
  "projects.reorder": (params) => reorderProjects(params.ids),
6946
+ "projects.logoFiles": (params) => logoFiles(projectPath(params.id)),
6563
6947
  "directories.list": (params) => directories(params.path),
6564
6948
  "sessions.update": async (params) => {
6565
6949
  try {
@@ -6648,9 +7032,25 @@ const handlers = {
6648
7032
  },
6649
7033
  capabilities: (params) => capabilities(params.provider, params.accountId, params.projectId === null ? os.homedir() : projectPath(params.projectId)),
6650
7034
  "files.list": (params) => listFiles(projectPath(params.projectId)),
7035
+ "files.entries": (params) => listEntries(projectPath(params.projectId)),
6651
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
+ },
6652
7053
  "files.media": (params) => linkProjectMedia(projectPath(params.projectId), params.path),
6653
- "files.download": (params) => linkProjectDownload(projectPath(params.projectId), params.path),
6654
7054
  "files.upload": (params) => saveUpload(params.name, Buffer.from(params.data, "base64")),
6655
7055
  "files.browse": () => browseToken,
6656
7056
  "browser.proxy": (params) => proxyPort(params.origin),
@@ -6658,6 +7058,16 @@ const handlers = {
6658
7058
  "git.status": (params) => gitRepos(projectPath(params.projectId)),
6659
7059
  "git.diff": (params) => projectDiff(projectPath(params.projectId)),
6660
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
+ },
6661
7071
  "git.push": async (params) => {
6662
7072
  await push$1(path.join(projectPath(params.projectId), params.repo));
6663
7073
  return null;
@@ -6985,7 +7395,6 @@ function serveMedia(token, request, response) {
6985
7395
  }
6986
7396
  response.setHeader("access-control-allow-origin", "*");
6987
7397
  response.setHeader("content-type", link.mediaType);
6988
- if (link.download !== null) response.setHeader("content-disposition", `attachment; filename*=UTF-8''${encodeURIComponent(link.download)}`);
6989
7398
  response.setHeader("cache-control", "private, max-age=3600");
6990
7399
  response.setHeader("content-security-policy", "sandbox");
6991
7400
  response.setHeader("x-content-type-options", "nosniff");