offsite 0.0.13 → 0.0.15
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/apps/server/dist/main.mjs +438 -252
- package/apps/server/dist/main.mjs.map +1 -1
- package/apps/web/dist/assets/{angular-html-CtL9u31h.js → angular-html-CG4HtNlx.js} +1 -1
- package/apps/web/dist/assets/{angular-ts-D4IFBcAh.js → angular-ts-CsUs7XSt.js} +1 -1
- package/apps/web/dist/assets/{apl-CqnJhScR.js → apl-BZNYboe1.js} +1 -1
- package/apps/web/dist/assets/{astro-Dn1_avL_.js → astro-BJJJ2TCY.js} +1 -1
- package/apps/web/dist/assets/{blade-BKSa6Bs3.js → blade-DPPHNv9A.js} +1 -1
- package/apps/web/dist/assets/{c-C8sbMt_8.js → c-DewL-caT.js} +1 -1
- package/apps/web/dist/assets/{chapel-BWxXvR2-.js → chapel-BC24MUCE.js} +1 -1
- package/apps/web/dist/assets/{cobol-DNoVp--S.js → cobol-CrP0JgFd.js} +1 -1
- package/apps/web/dist/assets/{coffee-m1P3e2U1.js → coffee-Do6yzljS.js} +1 -1
- package/apps/web/dist/assets/{cpp-CDvshXRJ.js → cpp-BkpYSJP8.js} +1 -1
- package/apps/web/dist/assets/{crystal-C4b65AQe.js → crystal-CeGzC-WW.js} +1 -1
- package/apps/web/dist/assets/{css-CqQV9eEE.js → css-Dw9LKU-N.js} +1 -1
- package/apps/web/dist/assets/{edge-CEdbZQ1U.js → edge-DOlsjMpU.js} +1 -1
- package/apps/web/dist/assets/{elixir-CYDX6OgA.js → elixir-Of98ZUP3.js} +1 -1
- package/apps/web/dist/assets/{elm-10RuU89D.js → elm-DcO8avoe.js} +1 -1
- package/apps/web/dist/assets/{erb-BiMfBUaM.js → erb-DGRCSrUQ.js} +1 -1
- package/apps/web/dist/assets/{git-rebase-D4Se-3SO.js → git-rebase-CF43k64m.js} +1 -1
- package/apps/web/dist/assets/{glimmer-js-sKOVTqrH.js → glimmer-js-DzTtVcUh.js} +1 -1
- package/apps/web/dist/assets/{glimmer-ts-DWMGXcHA.js → glimmer-ts-BGpaKkl4.js} +1 -1
- package/apps/web/dist/assets/{glsl-6ltM2JGZ.js → glsl-B6O-e93J.js} +1 -1
- package/apps/web/dist/assets/{graphql-DwyBbitO.js → graphql-8XdLbcFd.js} +1 -1
- package/apps/web/dist/assets/{hack-DKDbxv34.js → hack-DF9S8RPk.js} +1 -1
- package/apps/web/dist/assets/{haml-Bsfivw5f.js → haml-D7K9Vy7l.js} +1 -1
- package/apps/web/dist/assets/{handlebars-B3HW0h8N.js → handlebars-CodAKXOQ.js} +1 -1
- package/apps/web/dist/assets/{html-D9EhGmAq.js → html-DINw2T6o.js} +1 -1
- package/apps/web/dist/assets/{html-derivative-Cs1eIi3k.js → html-derivative-Bm8ARfNX.js} +1 -1
- package/apps/web/dist/assets/{http-BG4vxnc2.js → http-CzdVlK4g.js} +1 -1
- package/apps/web/dist/assets/{hurl-OIF0UEfX.js → hurl-DSRYfTy3.js} +1 -1
- package/apps/web/dist/assets/index-DKU43bgp.css +2 -0
- package/apps/web/dist/assets/index-_3a5liAb.js +1205 -0
- package/apps/web/dist/assets/{java-2iLSH1xc.js → java-CJrEr5nb.js} +1 -1
- package/apps/web/dist/assets/{javascript-Di7CdqTh.js → javascript-BRzSAlK0.js} +1 -1
- package/apps/web/dist/assets/{jinja-2mjIAN_z.js → jinja-BCkNTi07.js} +1 -1
- package/apps/web/dist/assets/{jison-Ciw1rqtP.js → jison-CuVfSCOU.js} +1 -1
- package/apps/web/dist/assets/{json-D7Dp9EFU.js → json-CivFBkRR.js} +1 -1
- package/apps/web/dist/assets/{jsx-WEelr3xJ.js → jsx-Bn8oTRCQ.js} +1 -1
- package/apps/web/dist/assets/{julia-DL3m48KZ.js → julia-S7B4dPpO.js} +1 -1
- package/apps/web/dist/assets/{just-B-5HaJEz.js → just-BkwYofB3.js} +1 -1
- package/apps/web/dist/assets/{latex-DKmfapvy.js → latex-CHtrQEQW.js} +1 -1
- package/apps/web/dist/assets/{liquid-Bqxp18sH.js → liquid-BiWMrFWE.js} +1 -1
- package/apps/web/dist/assets/{lua-BSLGBVbN.js → lua-DH2dy4b5.js} +1 -1
- package/apps/web/dist/assets/{marko-yLzCBGNR.js → marko-D1J2FQwH.js} +1 -1
- package/apps/web/dist/assets/{mdc-DSGSIXIk.js → mdc-DdU0BPrI.js} +1 -1
- package/apps/web/dist/assets/{nginx-BF1hRuVH.js → nginx-aVr0h4x0.js} +1 -1
- package/apps/web/dist/assets/{nim-kAH67IJv.js → nim-D3uKiFs9.js} +1 -1
- package/apps/web/dist/assets/{org-DCuW6Ito.js → org-Dlc-mmBJ.js} +1 -1
- package/apps/web/dist/assets/{perl-ChYkJruC.js → perl-Db2FD7eY.js} +1 -1
- package/apps/web/dist/assets/{php-Dy0o5CHL.js → php-BKnY42CF.js} +1 -1
- package/apps/web/dist/assets/{pug-C-vFGcyR.js → pug-Dv1XMHZo.js} +1 -1
- package/apps/web/dist/assets/{qml-Bbhsm3bz.js → qml-DLrUK6Mn.js} +1 -1
- package/apps/web/dist/assets/{r-D6AOSU_n.js → r-YY5yALTA.js} +1 -1
- package/apps/web/dist/assets/{razor-CsDTm1QE.js → razor-CHwb0eZk.js} +1 -1
- package/apps/web/dist/assets/{regexp-Cxw4Sy2X.js → regexp-BbKQ-CAd.js} +1 -1
- package/apps/web/dist/assets/{rst-BdEa7eTr.js → rst-CfgFwgqW.js} +1 -1
- package/apps/web/dist/assets/{ruby-CrX6uYJq.js → ruby-CWYxEIgK.js} +1 -1
- package/apps/web/dist/assets/{sas-Dq1DiCgf.js → sas-BIhkpR95.js} +1 -1
- package/apps/web/dist/assets/{scss-Cq-_Yezx.js → scss-CD48eC-9.js} +1 -1
- package/apps/web/dist/assets/{shellscript-N1TdW_Vo.js → shellscript-DQJbmpgk.js} +1 -1
- package/apps/web/dist/assets/{shellsession-DGy-5gMa.js → shellsession-BsbY07m-.js} +1 -1
- package/apps/web/dist/assets/{soy-BZmv_HHk.js → soy-2Y50DZFi.js} +1 -1
- package/apps/web/dist/assets/{sql-B0TfXN6P.js → sql-nKnfvr4s.js} +1 -1
- package/apps/web/dist/assets/{stata-D0cL92SE.js → stata-Dqj31JrM.js} +1 -1
- package/apps/web/dist/assets/{surrealql-B9RWrsWf.js → surrealql-DjKPQgK9.js} +1 -1
- package/apps/web/dist/assets/{svelte-Erfh9F_0.js → svelte-1HUOD4gz.js} +1 -1
- package/apps/web/dist/assets/{templ-C1hdk1C3.js → templ-CjuUG0Pz.js} +1 -1
- package/apps/web/dist/assets/{tex-B6y5Z0Oe.js → tex-DImgjGWJ.js} +1 -1
- package/apps/web/dist/assets/{ts-tags-B8Q3bBJx.js → ts-tags-uu3b2KWy.js} +1 -1
- package/apps/web/dist/assets/{tsx-CA2zlxVX.js → tsx-DLBT5mtF.js} +1 -1
- package/apps/web/dist/assets/{twig-xr1-p8qn.js → twig-CUqAREOD.js} +1 -1
- package/apps/web/dist/assets/{typescript-Bdd7ljtG.js → typescript-BJx9F5MO.js} +1 -1
- package/apps/web/dist/assets/{typst-BGU6c2s1.js → typst-DGjRmWJl.js} +1 -1
- package/apps/web/dist/assets/{vue-CxnbGcMK.js → vue-D4sa_D6w.js} +1 -1
- package/apps/web/dist/assets/{vue-html-D3pkBH4b.js → vue-html-D9bXwnxX.js} +1 -1
- package/apps/web/dist/assets/{vue-vine-0adNns8e.js → vue-vine-CltCK1Bg.js} +1 -1
- package/apps/web/dist/assets/{xml-Bf5yulh9.js → xml-BeUIBscE.js} +1 -1
- package/apps/web/dist/assets/{xsl-DnGCH2C6.js → xsl-Dp0u2Kgu.js} +1 -1
- package/apps/web/dist/assets/{yaml-ALFPjwWA.js → yaml-BSM_mxxD.js} +1 -1
- package/apps/web/dist/index.html +2 -2
- package/apps/web/dist/sw.js +1 -1
- package/package.json +1 -1
- package/apps/web/dist/assets/index-6ofGnhX4.css +0 -2
- package/apps/web/dist/assets/index-CEx2Hw3M.js +0 -1203
|
@@ -20,26 +20,9 @@ import { drizzle } from "drizzle-orm/node-sqlite";
|
|
|
20
20
|
import { migrate } from "drizzle-orm/node-sqlite/migrator";
|
|
21
21
|
import { integer, snakeCase, text } from "drizzle-orm/sqlite-core";
|
|
22
22
|
import { lstat, open, readFile, readdir, readlink, stat } from "node:fs/promises";
|
|
23
|
-
import { promisify } from "node:util";
|
|
24
23
|
import webPush from "web-push";
|
|
24
|
+
import { promisify } from "node:util";
|
|
25
25
|
import { spawn as spawn$1 } from "@lydell/node-pty";
|
|
26
|
-
//#region ../core/src/protocol.ts
|
|
27
|
-
function encodeFrame(kind, id, data) {
|
|
28
|
-
const frame = new Uint8Array(5 + data.length);
|
|
29
|
-
frame[0] = kind;
|
|
30
|
-
new DataView(frame.buffer).setUint32(1, id);
|
|
31
|
-
frame.set(data, 5);
|
|
32
|
-
return frame;
|
|
33
|
-
}
|
|
34
|
-
function decodeFrame(frame) {
|
|
35
|
-
if (frame.length < 5) throw new Error("terminal frame is too short");
|
|
36
|
-
return {
|
|
37
|
-
kind: frame[0],
|
|
38
|
-
id: new DataView(frame.buffer, frame.byteOffset, frame.byteLength).getUint32(1),
|
|
39
|
-
data: frame.subarray(5)
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
//#endregion
|
|
43
26
|
//#region src/constants.ts
|
|
44
27
|
const deprecatedModels = {
|
|
45
28
|
claude: [
|
|
@@ -56,8 +39,13 @@ const deprecatedModels = {
|
|
|
56
39
|
opencode: []
|
|
57
40
|
};
|
|
58
41
|
const defaultModels = {
|
|
42
|
+
codex: "gpt-6.1-sol",
|
|
59
43
|
claude: "claude-opus-5-5",
|
|
60
|
-
|
|
44
|
+
opencode: null
|
|
45
|
+
};
|
|
46
|
+
const defaultEfforts = {
|
|
47
|
+
codex: "high",
|
|
48
|
+
claude: "high",
|
|
61
49
|
opencode: null
|
|
62
50
|
};
|
|
63
51
|
//#endregion
|
|
@@ -255,7 +243,8 @@ const privateEntries = [
|
|
|
255
243
|
];
|
|
256
244
|
const windowNames = /* @__PURE__ */ new Map([[300, "five_hour"], [10080, "seven_day"]]);
|
|
257
245
|
async function codexAccount(account) {
|
|
258
|
-
const
|
|
246
|
+
const configDir = account.configDir ?? codexBaseDir;
|
|
247
|
+
const linked = isMirror(configDir);
|
|
259
248
|
const base = {
|
|
260
249
|
...account,
|
|
261
250
|
email: null,
|
|
@@ -264,11 +253,11 @@ async function codexAccount(account) {
|
|
|
264
253
|
linked
|
|
265
254
|
};
|
|
266
255
|
try {
|
|
267
|
-
const server = appServer(
|
|
256
|
+
const server = appServer(configDir);
|
|
268
257
|
const result = await server.request("account/read", {});
|
|
269
258
|
if (result.account === null) return {
|
|
270
259
|
...base,
|
|
271
|
-
error: `Not logged in. Run CODEX_HOME=${
|
|
260
|
+
error: `Not logged in. Run CODEX_HOME=${configDir} codex login in a terminal`,
|
|
272
261
|
valid: false
|
|
273
262
|
};
|
|
274
263
|
switch (result.account.type) {
|
|
@@ -302,7 +291,7 @@ async function codexAccount(account) {
|
|
|
302
291
|
}
|
|
303
292
|
}
|
|
304
293
|
async function codexCapabilities(account, cwd) {
|
|
305
|
-
const server = appServer(account.configDir);
|
|
294
|
+
const server = appServer(account.configDir ?? codexBaseDir);
|
|
306
295
|
const models = [];
|
|
307
296
|
let cursor = null;
|
|
308
297
|
do {
|
|
@@ -333,7 +322,7 @@ async function skills(server, cwd) {
|
|
|
333
322
|
return (await server.request("skills/list", { cwds: [cwd] })).data.flatMap((entry) => entry.skills.filter((skill) => skill.enabled));
|
|
334
323
|
}
|
|
335
324
|
function publishCodexLimits(home, snapshot) {
|
|
336
|
-
for (const account of storedAccounts()) if (account.provider === "codex" && account.configDir === home) broadcast({
|
|
325
|
+
for (const account of storedAccounts()) if (account.provider === "codex" && (account.configDir ?? codexBaseDir) === home) broadcast({
|
|
337
326
|
type: "limits",
|
|
338
327
|
accountId: account.id,
|
|
339
328
|
limits: toLimits(snapshot)
|
|
@@ -698,7 +687,7 @@ function record$1(value) {
|
|
|
698
687
|
//#region src/args.ts
|
|
699
688
|
let command = "web";
|
|
700
689
|
let name = null;
|
|
701
|
-
const program = new Command("offsite").description("Starts the server and the web app").option("--host <host>", "Address to listen on", "127.0.0.1").option("--port <port>", "Port to listen on", "47841").option("--dir <dir>", "Folder for settings and accounts", path.join(os.homedir(), ".offsite")).option("--no-auth", "Accept connections without pairing").option("--no-pair", "Don't create a pairing code or print pairing links").option("--pwa", "Serve the web app as an installable app that works offline");
|
|
690
|
+
const program = new Command("offsite").description("Starts the server and the web app").option("--host <host>", "Address to listen on", "127.0.0.1").option("--port <port>", "Port to listen on", "47841").option("--dir <dir>", "Folder for settings and accounts", path.join(os.homedir(), ".offsite")).option("--no-auth", "Accept connections without pairing").option("--no-pair", "Don't create a pairing code or print pairing links").option("--pwa", "Serve the web app as an installable app that works offline").configureHelp({ showGlobalOptions: true });
|
|
702
691
|
program.command("web", { isDefault: true }).description("Starts the server and the web app, same as offsite");
|
|
703
692
|
program.command("serve").description("Starts only the server, without the web app").action(() => {
|
|
704
693
|
command = "serve";
|
|
@@ -712,6 +701,7 @@ program.command("unpair").description("Picks paired browsers to unpair").action(
|
|
|
712
701
|
});
|
|
713
702
|
program.parse();
|
|
714
703
|
const options = program.opts();
|
|
704
|
+
if (!options.auth && !options.pair) program.error("error: --no-pair can't be used with --no-auth");
|
|
715
705
|
const args = {
|
|
716
706
|
...options,
|
|
717
707
|
dir: path.resolve(options.dir),
|
|
@@ -776,8 +766,13 @@ function readJson(name) {
|
|
|
776
766
|
}
|
|
777
767
|
}
|
|
778
768
|
function writeJson(name, value) {
|
|
779
|
-
|
|
769
|
+
let file = path.join(home, name);
|
|
780
770
|
mkdirSync(home, { recursive: true });
|
|
771
|
+
try {
|
|
772
|
+
file = realpathSync(file);
|
|
773
|
+
} catch (error) {
|
|
774
|
+
if (!hasCode(error, "ENOENT")) throw error;
|
|
775
|
+
}
|
|
781
776
|
writeFileSync(`${file}.tmp`, `${JSON.stringify(value, null, 2)}\n`, { mode: 384 });
|
|
782
777
|
renameSync(`${file}.tmp`, file);
|
|
783
778
|
}
|
|
@@ -1053,7 +1048,7 @@ const presets = /* @__PURE__ */ new Map([
|
|
|
1053
1048
|
}]
|
|
1054
1049
|
]);
|
|
1055
1050
|
function codexServer(settings) {
|
|
1056
|
-
return appServer(storedAccount("codex", settings.accountId).configDir);
|
|
1051
|
+
return appServer(storedAccount("codex", settings.accountId).configDir ?? codexBaseDir);
|
|
1057
1052
|
}
|
|
1058
1053
|
var CodexRun = class CodexRun {
|
|
1059
1054
|
provider = "codex";
|
|
@@ -1481,15 +1476,15 @@ async function revertThread(sessionId, messageId) {
|
|
|
1481
1476
|
beforeTurnId: turnOf(messageId)
|
|
1482
1477
|
});
|
|
1483
1478
|
}
|
|
1484
|
-
async function
|
|
1485
|
-
const server = appServer(storedAccount("codex", accountId).configDir);
|
|
1479
|
+
async function codexText(accountId, cwd, model, instructions, prompt) {
|
|
1480
|
+
const server = appServer(storedAccount("codex", accountId).configDir ?? codexBaseDir);
|
|
1486
1481
|
const threadId = (await server.request("thread/start", {
|
|
1487
1482
|
cwd,
|
|
1488
1483
|
model,
|
|
1489
1484
|
ephemeral: true,
|
|
1490
1485
|
approvalPolicy: "never",
|
|
1491
1486
|
sandbox: "read-only",
|
|
1492
|
-
developerInstructions:
|
|
1487
|
+
developerInstructions: instructions
|
|
1493
1488
|
})).thread.id;
|
|
1494
1489
|
const reply = Promise.withResolvers();
|
|
1495
1490
|
let text = "";
|
|
@@ -1517,7 +1512,7 @@ async function codexCommitMessage(accountId, cwd, model, prompt) {
|
|
|
1517
1512
|
}],
|
|
1518
1513
|
model
|
|
1519
1514
|
});
|
|
1520
|
-
return
|
|
1515
|
+
return await reply.promise;
|
|
1521
1516
|
} finally {
|
|
1522
1517
|
server.threads.delete(threadId);
|
|
1523
1518
|
await server.request("thread/unsubscribe", { threadId });
|
|
@@ -1802,6 +1797,26 @@ function record(value) {
|
|
|
1802
1797
|
return isRecord(value) ? value : {};
|
|
1803
1798
|
}
|
|
1804
1799
|
//#endregion
|
|
1800
|
+
//#region src/lib/opencode/requests.ts
|
|
1801
|
+
function permissionRequest(permission, item) {
|
|
1802
|
+
return {
|
|
1803
|
+
id: permission.id,
|
|
1804
|
+
kind: "approval",
|
|
1805
|
+
title: permission.message ?? `Allow ${permission.action} ${permission.resources.join(", ")}?`,
|
|
1806
|
+
canAlwaysAllow: (permission.save ?? []).length > 0,
|
|
1807
|
+
item: item ?? {
|
|
1808
|
+
id: permission.id,
|
|
1809
|
+
parent: null,
|
|
1810
|
+
time: Date.now(),
|
|
1811
|
+
kind: "tool",
|
|
1812
|
+
name: permission.action,
|
|
1813
|
+
input: { resources: permission.resources },
|
|
1814
|
+
output: "",
|
|
1815
|
+
status: "running"
|
|
1816
|
+
}
|
|
1817
|
+
};
|
|
1818
|
+
}
|
|
1819
|
+
//#endregion
|
|
1805
1820
|
//#region src/lib/opencode/sessions.ts
|
|
1806
1821
|
const PAGE = 100;
|
|
1807
1822
|
const COMMIT_LIMIT = 12e4;
|
|
@@ -1917,7 +1932,7 @@ async function rewindOpenCode(request) {
|
|
|
1917
1932
|
else await client.session.revert.commit({ sessionID });
|
|
1918
1933
|
return result;
|
|
1919
1934
|
}
|
|
1920
|
-
async function
|
|
1935
|
+
async function openCodeText(cwd, model, prompt) {
|
|
1921
1936
|
const client = await openCode();
|
|
1922
1937
|
const session = await client.session.create({
|
|
1923
1938
|
location: { directory: cwd },
|
|
@@ -1936,7 +1951,7 @@ async function openCodeCommitMessage(cwd, model, prompt) {
|
|
|
1936
1951
|
if (message.type === "idle" && message.outcome === "failed") throw new Error(message.error?.message ?? "OpenCode failed");
|
|
1937
1952
|
if (message.type !== "assistant") continue;
|
|
1938
1953
|
const text = message.content.flatMap((part) => part.type === "text" ? [part.text] : []).join("").trim();
|
|
1939
|
-
if (text !== "") return
|
|
1954
|
+
if (text !== "") return text;
|
|
1940
1955
|
}
|
|
1941
1956
|
throw new Error("OpenCode did not reply");
|
|
1942
1957
|
} finally {
|
|
@@ -1984,6 +1999,15 @@ function openCodeRun(sessionId) {
|
|
|
1984
1999
|
const run = runs.get(sessionId);
|
|
1985
2000
|
return run?.provider === "opencode" ? run : void 0;
|
|
1986
2001
|
}
|
|
2002
|
+
function sessionSettings(session, settings) {
|
|
2003
|
+
const variant = session.model?.variant;
|
|
2004
|
+
return {
|
|
2005
|
+
...settings,
|
|
2006
|
+
model: session.model === void 0 ? null : `${session.model.providerID}/${session.model.id}`,
|
|
2007
|
+
effort: variant === void 0 || variant === "default" ? null : variant,
|
|
2008
|
+
mode: session.agent ?? null
|
|
2009
|
+
};
|
|
2010
|
+
}
|
|
1987
2011
|
//#endregion
|
|
1988
2012
|
//#region src/lib/opencode/run.ts
|
|
1989
2013
|
const IDLE_LIMIT$1 = 18e5;
|
|
@@ -2260,22 +2284,7 @@ var OpenCodeRun = class OpenCodeRun {
|
|
|
2260
2284
|
const item = source === void 0 ? void 0 : this.items.get(`${source.messageID}:${source.id}`);
|
|
2261
2285
|
this.addPending({
|
|
2262
2286
|
kind: "permission",
|
|
2263
|
-
request:
|
|
2264
|
-
id: permission.id,
|
|
2265
|
-
kind: "approval",
|
|
2266
|
-
title: permission.message ?? `Allow ${permission.action} ${permission.resources.join(", ")}?`,
|
|
2267
|
-
canAlwaysAllow: (permission.save ?? []).length > 0,
|
|
2268
|
-
item: item ?? {
|
|
2269
|
-
id: permission.id,
|
|
2270
|
-
parent: null,
|
|
2271
|
-
time: Date.now(),
|
|
2272
|
-
kind: "tool",
|
|
2273
|
-
name: permission.action,
|
|
2274
|
-
input: { resources: permission.resources },
|
|
2275
|
-
output: "",
|
|
2276
|
-
status: "running"
|
|
2277
|
-
}
|
|
2278
|
-
}
|
|
2287
|
+
request: permissionRequest(permission, item)
|
|
2279
2288
|
});
|
|
2280
2289
|
}
|
|
2281
2290
|
askForm(form) {
|
|
@@ -2357,15 +2366,6 @@ var OpenCodeRun = class OpenCodeRun {
|
|
|
2357
2366
|
});
|
|
2358
2367
|
}
|
|
2359
2368
|
};
|
|
2360
|
-
function sessionSettings(session, settings) {
|
|
2361
|
-
const variant = session.model?.variant;
|
|
2362
|
-
return {
|
|
2363
|
-
...settings,
|
|
2364
|
-
model: session.model === void 0 ? null : `${session.model.providerID}/${session.model.id}`,
|
|
2365
|
-
effort: variant === void 0 || variant === "default" ? null : variant,
|
|
2366
|
-
mode: session.agent ?? null
|
|
2367
|
-
};
|
|
2368
|
-
}
|
|
2369
2369
|
function files(prompt) {
|
|
2370
2370
|
return prompt.images.map((image) => ({ uri: `data:${image.mediaType};base64,${image.data}` }));
|
|
2371
2371
|
}
|
|
@@ -2780,7 +2780,7 @@ const listChanges = /* @__PURE__ */ new Set([
|
|
|
2780
2780
|
"session.execution.failed",
|
|
2781
2781
|
"session.execution.interrupted"
|
|
2782
2782
|
]);
|
|
2783
|
-
const SILENCE_LIMIT$
|
|
2783
|
+
const SILENCE_LIMIT$2 = 45e3;
|
|
2784
2784
|
const RECONNECT_ATTEMPTS = 5;
|
|
2785
2785
|
const RECONNECT_DELAY = 2e3;
|
|
2786
2786
|
let connection;
|
|
@@ -2823,7 +2823,7 @@ async function connect() {
|
|
|
2823
2823
|
async function listen(client) {
|
|
2824
2824
|
const controller = new AbortController();
|
|
2825
2825
|
listener = controller;
|
|
2826
|
-
const silence = setTimeout(() => controller.abort(), SILENCE_LIMIT$
|
|
2826
|
+
const silence = setTimeout(() => controller.abort(), SILENCE_LIMIT$2);
|
|
2827
2827
|
try {
|
|
2828
2828
|
for await (const event of client.event.subscribe({
|
|
2829
2829
|
signal: controller.signal,
|
|
@@ -2832,7 +2832,7 @@ async function listen(client) {
|
|
|
2832
2832
|
console.warn("the OpenCode event stream ended");
|
|
2833
2833
|
} catch (error) {
|
|
2834
2834
|
if (listener !== controller) return;
|
|
2835
|
-
console.warn(controller.signal.aborted ? `the OpenCode event stream was silent for ${SILENCE_LIMIT$
|
|
2835
|
+
console.warn(controller.signal.aborted ? `the OpenCode event stream was silent for ${SILENCE_LIMIT$2 / 1e3} seconds` : `lost the OpenCode event stream: ${error}`);
|
|
2836
2836
|
} finally {
|
|
2837
2837
|
clearTimeout(silence);
|
|
2838
2838
|
}
|
|
@@ -2897,14 +2897,13 @@ function providerName(provider) {
|
|
|
2897
2897
|
}
|
|
2898
2898
|
function storedAccounts() {
|
|
2899
2899
|
const config = readConfig();
|
|
2900
|
-
return [...config.claudeAccounts
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
});
|
|
2900
|
+
return [...config.claudeAccounts.map((item) => ({
|
|
2901
|
+
...item,
|
|
2902
|
+
configDir: item.configDir[platform()]
|
|
2903
|
+
})), ...config.codexAccounts.map((item) => ({
|
|
2904
|
+
...item,
|
|
2905
|
+
configDir: item.configDir[platform()]
|
|
2906
|
+
}))];
|
|
2908
2907
|
}
|
|
2909
2908
|
function assertEnabled(provider, accountId) {
|
|
2910
2909
|
if (!readConfig().enabledProviders[provider]) throw new Error(`${providerName(provider)} is disabled in Settings`);
|
|
@@ -2925,7 +2924,7 @@ async function stopAccount(account) {
|
|
|
2925
2924
|
const stopping = [...runs.values()].filter((run) => run.settings.accountId === account.id);
|
|
2926
2925
|
for (const run of stopping) run.stop();
|
|
2927
2926
|
await Promise.all(stopping.map((run) => run.closed));
|
|
2928
|
-
if (account.provider === "codex") stopAppServer(account.configDir);
|
|
2927
|
+
if (account.provider === "codex") stopAppServer(account.configDir ?? codexBaseDir);
|
|
2929
2928
|
if (account.provider === "claude") forgetClaude(account.id);
|
|
2930
2929
|
}
|
|
2931
2930
|
function storedAccount(provider, accountId) {
|
|
@@ -2984,11 +2983,13 @@ async function capabilities(provider, accountId, cwd) {
|
|
|
2984
2983
|
hidden: deprecated,
|
|
2985
2984
|
effort: null
|
|
2986
2985
|
};
|
|
2986
|
+
const preferred = defaultEfforts[provider];
|
|
2987
|
+
const effort = preferred === null ? null : model.efforts.includes(preferred) ? preferred : model.efforts.at(-1) ?? null;
|
|
2987
2988
|
return {
|
|
2988
2989
|
...model,
|
|
2989
2990
|
deprecated,
|
|
2990
2991
|
hidden: overrides[model.value] ?? setting.hidden,
|
|
2991
|
-
effort: setting.effort
|
|
2992
|
+
effort: setting.effort ?? effort
|
|
2992
2993
|
};
|
|
2993
2994
|
})
|
|
2994
2995
|
};
|
|
@@ -3051,7 +3052,15 @@ function removeAccount(id) {
|
|
|
3051
3052
|
}
|
|
3052
3053
|
function validAccount(name, configDir) {
|
|
3053
3054
|
if (name.trim() === "") throw new Error("Give the account a name");
|
|
3055
|
+
if (configDir === null) return {
|
|
3056
|
+
name: name.trim(),
|
|
3057
|
+
configDir: null
|
|
3058
|
+
};
|
|
3054
3059
|
const dir = configDir.trim();
|
|
3060
|
+
if (dir === "") return {
|
|
3061
|
+
name: name.trim(),
|
|
3062
|
+
configDir: null
|
|
3063
|
+
};
|
|
3055
3064
|
if (dir === "~" || dir.startsWith("~/")) return {
|
|
3056
3065
|
name: name.trim(),
|
|
3057
3066
|
configDir: path.join(os.homedir(), dir.slice(1))
|
|
@@ -3063,6 +3072,117 @@ function validAccount(name, configDir) {
|
|
|
3063
3072
|
throw new Error("Use an absolute config directory or one starting with ~/");
|
|
3064
3073
|
}
|
|
3065
3074
|
//#endregion
|
|
3075
|
+
//#region src/services/push.ts
|
|
3076
|
+
const TITLE_LIMIT = 100;
|
|
3077
|
+
const BODY_LIMIT = 500;
|
|
3078
|
+
const TIME_TO_LIVE = 900;
|
|
3079
|
+
const SEND_TIMEOUT = 1e4;
|
|
3080
|
+
const SILENCE_LIMIT$1 = 9e4;
|
|
3081
|
+
const SUBJECT = "https://github.com/NazmusSayad/offsite";
|
|
3082
|
+
let server$1 = "";
|
|
3083
|
+
let notifier = null;
|
|
3084
|
+
function startPush(address) {
|
|
3085
|
+
server$1 = address;
|
|
3086
|
+
}
|
|
3087
|
+
function pushActive() {
|
|
3088
|
+
return targets().length > 0;
|
|
3089
|
+
}
|
|
3090
|
+
function notifierClient() {
|
|
3091
|
+
return notifier === null ? null : notifier.client;
|
|
3092
|
+
}
|
|
3093
|
+
function notifierConnected() {
|
|
3094
|
+
if (notifier === null || !clients.has(notifier.client)) return false;
|
|
3095
|
+
return Date.now() - notifier.client.heardAt < SILENCE_LIMIT$1;
|
|
3096
|
+
}
|
|
3097
|
+
function claimNotifier(client, endpoint) {
|
|
3098
|
+
notifier = {
|
|
3099
|
+
client,
|
|
3100
|
+
endpoint
|
|
3101
|
+
};
|
|
3102
|
+
for (const item of clients) item.send({
|
|
3103
|
+
type: "notifier",
|
|
3104
|
+
active: item === client
|
|
3105
|
+
});
|
|
3106
|
+
}
|
|
3107
|
+
function subscribe(registration, client) {
|
|
3108
|
+
if (new URL(registration.subscription.endpoint).protocol !== "https:") throw new Error("the endpoint must be an https URL");
|
|
3109
|
+
const values = {
|
|
3110
|
+
endpoint: registration.subscription.endpoint,
|
|
3111
|
+
p256dh: registration.subscription.keys.p256dh,
|
|
3112
|
+
auth: registration.subscription.keys.auth,
|
|
3113
|
+
publicKey: registration.publicKey,
|
|
3114
|
+
privateKey: registration.privateKey,
|
|
3115
|
+
machine: registration.machine,
|
|
3116
|
+
server: server$1
|
|
3117
|
+
};
|
|
3118
|
+
db.insert(pushSubscriptions).values(values).onConflictDoUpdate({
|
|
3119
|
+
target: pushSubscriptions.endpoint,
|
|
3120
|
+
set: values
|
|
3121
|
+
}).run();
|
|
3122
|
+
if (notifier !== null && notifier.client === client) notifier.endpoint = registration.subscription.endpoint;
|
|
3123
|
+
console.log("a browser subscribed to push notifications");
|
|
3124
|
+
}
|
|
3125
|
+
function unsubscribe(endpoint) {
|
|
3126
|
+
db.delete(pushSubscriptions).where(eq(pushSubscriptions.endpoint, endpoint)).run();
|
|
3127
|
+
if (notifier !== null && notifier.endpoint === endpoint) notifier.endpoint = null;
|
|
3128
|
+
console.log("a browser unsubscribed from push notifications");
|
|
3129
|
+
}
|
|
3130
|
+
async function sendPush(message) {
|
|
3131
|
+
const subscriptions = targets();
|
|
3132
|
+
if (subscriptions.length === 0) return false;
|
|
3133
|
+
const trimmed = {
|
|
3134
|
+
...message,
|
|
3135
|
+
title: [...message.title].slice(0, TITLE_LIMIT).join(""),
|
|
3136
|
+
body: [...message.body].slice(0, BODY_LIMIT).join("")
|
|
3137
|
+
};
|
|
3138
|
+
const gone = [];
|
|
3139
|
+
let sent = false;
|
|
3140
|
+
await Promise.all(subscriptions.map(async (subscription) => {
|
|
3141
|
+
try {
|
|
3142
|
+
await webPush.sendNotification({
|
|
3143
|
+
endpoint: subscription.endpoint,
|
|
3144
|
+
keys: {
|
|
3145
|
+
p256dh: subscription.p256dh,
|
|
3146
|
+
auth: subscription.auth
|
|
3147
|
+
}
|
|
3148
|
+
}, JSON.stringify({
|
|
3149
|
+
...trimmed,
|
|
3150
|
+
machineId: subscription.machine
|
|
3151
|
+
}), {
|
|
3152
|
+
vapidDetails: {
|
|
3153
|
+
subject: SUBJECT,
|
|
3154
|
+
publicKey: subscription.publicKey,
|
|
3155
|
+
privateKey: subscription.privateKey
|
|
3156
|
+
},
|
|
3157
|
+
TTL: TIME_TO_LIVE,
|
|
3158
|
+
urgency: "high",
|
|
3159
|
+
timeout: SEND_TIMEOUT
|
|
3160
|
+
});
|
|
3161
|
+
sent = true;
|
|
3162
|
+
} catch (error) {
|
|
3163
|
+
const status = error.statusCode;
|
|
3164
|
+
if (status !== void 0 && [
|
|
3165
|
+
401,
|
|
3166
|
+
403,
|
|
3167
|
+
404,
|
|
3168
|
+
410
|
|
3169
|
+
].includes(status)) {
|
|
3170
|
+
console.log(`push subscription ${subscription.endpoint} is gone or was made for another key, removing it`);
|
|
3171
|
+
gone.push(subscription.endpoint);
|
|
3172
|
+
return;
|
|
3173
|
+
}
|
|
3174
|
+
console.warn(`could not send a push to ${subscription.endpoint}: ${error}`);
|
|
3175
|
+
}
|
|
3176
|
+
}));
|
|
3177
|
+
if (gone.length === 0) return sent;
|
|
3178
|
+
db.delete(pushSubscriptions).where(and(eq(pushSubscriptions.server, server$1), inArray(pushSubscriptions.endpoint, gone))).run();
|
|
3179
|
+
return sent;
|
|
3180
|
+
}
|
|
3181
|
+
function targets() {
|
|
3182
|
+
if (notifier === null || notifier.endpoint === null) return [];
|
|
3183
|
+
return db.select().from(pushSubscriptions).where(and(eq(pushSubscriptions.server, server$1), eq(pushSubscriptions.endpoint, notifier.endpoint))).all();
|
|
3184
|
+
}
|
|
3185
|
+
//#endregion
|
|
3066
3186
|
//#region src/lib/claude/claude.ts
|
|
3067
3187
|
const efforts = [
|
|
3068
3188
|
"low",
|
|
@@ -3104,13 +3224,13 @@ function claudeMode(value) {
|
|
|
3104
3224
|
return mode;
|
|
3105
3225
|
}
|
|
3106
3226
|
function claudeOptions(accountId, cwd) {
|
|
3107
|
-
const
|
|
3227
|
+
const configDir = storedAccount("claude", accountId).configDir ?? claudeBaseDir();
|
|
3108
3228
|
const env = {
|
|
3109
3229
|
...process.env,
|
|
3110
3230
|
CLAUDE_CODE_ENTRYPOINT: "offsite",
|
|
3111
|
-
CLAUDE_CONFIG_DIR:
|
|
3231
|
+
CLAUDE_CONFIG_DIR: configDir
|
|
3112
3232
|
};
|
|
3113
|
-
if (path.resolve(
|
|
3233
|
+
if (path.resolve(configDir) === path.join(os.homedir(), ".claude")) delete env.CLAUDE_CONFIG_DIR;
|
|
3114
3234
|
return {
|
|
3115
3235
|
cwd,
|
|
3116
3236
|
env,
|
|
@@ -3123,23 +3243,56 @@ function claudeOptions(accountId, cwd) {
|
|
|
3123
3243
|
matcher: "PushNotification",
|
|
3124
3244
|
hooks: [async (input) => {
|
|
3125
3245
|
if (input.hook_event_name !== "PostToolUse" || !isRecord(input.tool_input) || typeof input.tool_input.message !== "string") throw new Error("Unexpected PushNotification hook input");
|
|
3246
|
+
const message = input.tool_input.message;
|
|
3126
3247
|
broadcast({
|
|
3127
3248
|
type: "notification",
|
|
3128
3249
|
sessionId: input.session_id,
|
|
3129
|
-
message
|
|
3250
|
+
message
|
|
3130
3251
|
});
|
|
3131
|
-
return { hookSpecificOutput: {
|
|
3252
|
+
if (notifierConnected()) return { hookSpecificOutput: {
|
|
3132
3253
|
hookEventName: "PostToolUse",
|
|
3133
3254
|
updatedToolOutput: {
|
|
3134
|
-
message
|
|
3255
|
+
message,
|
|
3135
3256
|
localSent: true,
|
|
3136
3257
|
disabledReason: "no_transport"
|
|
3137
3258
|
}
|
|
3138
3259
|
} };
|
|
3260
|
+
const session = sessions().find((item) => item.id === input.session_id);
|
|
3261
|
+
if (session === void 0) throw new Error(`Session ${input.session_id} is not listed`);
|
|
3262
|
+
const pushSent = await sendPush({
|
|
3263
|
+
kind: "session",
|
|
3264
|
+
title: session.title,
|
|
3265
|
+
body: message,
|
|
3266
|
+
projectId: session.projectId,
|
|
3267
|
+
sessionId: session.id
|
|
3268
|
+
});
|
|
3269
|
+
return { hookSpecificOutput: {
|
|
3270
|
+
hookEventName: "PostToolUse",
|
|
3271
|
+
updatedToolOutput: {
|
|
3272
|
+
message,
|
|
3273
|
+
localSent: false,
|
|
3274
|
+
pushSent,
|
|
3275
|
+
...pushSent ? {} : { disabledReason: "no_transport" }
|
|
3276
|
+
}
|
|
3277
|
+
} };
|
|
3139
3278
|
}]
|
|
3140
3279
|
}] }
|
|
3141
3280
|
};
|
|
3142
3281
|
}
|
|
3282
|
+
function runOptions(options) {
|
|
3283
|
+
const settings = options.settings;
|
|
3284
|
+
return {
|
|
3285
|
+
...claudeOptions(settings.accountId, options.cwd),
|
|
3286
|
+
...options.resume ? { resume: options.sessionId } : { sessionId: options.sessionId },
|
|
3287
|
+
...options.resumeAt === null ? {} : { resumeSessionAt: options.resumeAt },
|
|
3288
|
+
enableFileCheckpointing: true,
|
|
3289
|
+
...settings.mode === null ? {} : { permissionMode: claudeMode(settings.mode) },
|
|
3290
|
+
...settings.model === null ? {} : { model: settings.model },
|
|
3291
|
+
...settings.effort === null ? {} : { effort: claudeEffort(settings.effort) },
|
|
3292
|
+
allowDangerouslySkipPermissions: true,
|
|
3293
|
+
includePartialMessages: true
|
|
3294
|
+
};
|
|
3295
|
+
}
|
|
3143
3296
|
async function claudeCapabilities(account, cwd) {
|
|
3144
3297
|
const result = await initialization(account.id, cwd);
|
|
3145
3298
|
return {
|
|
@@ -3161,7 +3314,7 @@ async function claudeCapabilities(account, cwd) {
|
|
|
3161
3314
|
};
|
|
3162
3315
|
}
|
|
3163
3316
|
async function claudeAccount(account) {
|
|
3164
|
-
const linked = isLinked(account.configDir);
|
|
3317
|
+
const linked = isLinked(account.configDir ?? claudeBaseDir());
|
|
3165
3318
|
try {
|
|
3166
3319
|
const info = (await initialization(account.id, os.homedir())).account;
|
|
3167
3320
|
return {
|
|
@@ -3882,19 +4035,10 @@ var Run = class {
|
|
|
3882
4035
|
this.sessionId = options.sessionId;
|
|
3883
4036
|
this.resumeAt = options.resumeAt;
|
|
3884
4037
|
this.settings = options.settings;
|
|
3885
|
-
const settings = options.settings;
|
|
3886
4038
|
this.query = query({
|
|
3887
4039
|
prompt: this.input(),
|
|
3888
4040
|
options: {
|
|
3889
|
-
...
|
|
3890
|
-
...options.resume ? { resume: options.sessionId } : { sessionId: options.sessionId },
|
|
3891
|
-
...options.resumeAt === null ? {} : { resumeSessionAt: options.resumeAt },
|
|
3892
|
-
enableFileCheckpointing: true,
|
|
3893
|
-
...settings.mode === null ? {} : { permissionMode: claudeMode(settings.mode) },
|
|
3894
|
-
...settings.model === null ? {} : { model: settings.model },
|
|
3895
|
-
...settings.effort === null ? {} : { effort: claudeEffort(settings.effort) },
|
|
3896
|
-
allowDangerouslySkipPermissions: true,
|
|
3897
|
-
includePartialMessages: true,
|
|
4041
|
+
...runOptions(options),
|
|
3898
4042
|
canUseTool: (toolName, input, context) => this.ask({
|
|
3899
4043
|
toolName,
|
|
3900
4044
|
input,
|
|
@@ -4240,85 +4384,181 @@ const UNTRACKED_LIMIT = 100;
|
|
|
4240
4384
|
const NEW_FILE_LIMIT = 262144;
|
|
4241
4385
|
const REPO_DEPTH = 3;
|
|
4242
4386
|
const run$1 = promisify(execFile);
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4387
|
+
const PULL_REQUEST_INSTRUCTIONS = `You write GitHub pull requests as Markdown.
|
|
4388
|
+
|
|
4389
|
+
## TITLE RULES
|
|
4390
|
+
|
|
4391
|
+
- Use the conventional commits format: type(scope): subject
|
|
4392
|
+
- Types: feat, fix, docs, style, refactor, perf, test, chore, ci
|
|
4393
|
+
- Scope is optional; include it when the change targets a clear area
|
|
4394
|
+
- Subject: imperative mood, lowercase, no trailing period
|
|
4395
|
+
- Maximum 72 characters in total
|
|
4396
|
+
- Describe the overall change, not a single commit
|
|
4397
|
+
|
|
4398
|
+
## BODY RULES
|
|
4399
|
+
|
|
4400
|
+
- Use short Markdown sections with level-2 headings. Choose only sections that fit the change: \`New features\`, \`Improvements\`, \`Fixes\`, \`Before\` / \`After\`, or \`Breaking changes\`. Do not use all of them as a template.
|
|
4401
|
+
- Use bullet points under every section, never paragraphs. Each bullet describes one feature or change in one short sentence.
|
|
4402
|
+
- For a small change, use one section with one bullet. For larger changes, use only the sections needed, with at most three short bullets across the body.
|
|
4403
|
+
- Aim for 20-50 words; fewer is better when enough. Never exceed 80 words.
|
|
4404
|
+
- Describe the outcome, not the implementation walkthrough. Do not enumerate changed files, functions, commits, or incidental cleanup; reviewers can read the diff.
|
|
4405
|
+
- Add a \`Breaking changes\` section only for an actual compatibility break or required migration.
|
|
4406
|
+
- No generic \`Summary\` section, bold labels, checklists, boilerplate openings like "This pull request", or closing summaries.
|
|
4407
|
+
|
|
4408
|
+
## RULES
|
|
4409
|
+
|
|
4410
|
+
Base every statement on the provided commits and diff; do NOT invent details.
|
|
4411
|
+
|
|
4412
|
+
## OUTPUT
|
|
4413
|
+
|
|
4414
|
+
Return one Markdown document. The title is the level-1 heading (\`# <title>\`), and everything after it is the body.`;
|
|
4415
|
+
async function gitRepos(cwd) {
|
|
4416
|
+
return Promise.all((await projectRepos(cwd)).map(async (repo) => ({
|
|
4417
|
+
...await gitStatus(path.join(cwd, repo)),
|
|
4418
|
+
path: repo
|
|
4419
|
+
})));
|
|
4420
|
+
}
|
|
4421
|
+
async function commit(cwd, message, andPush) {
|
|
4422
|
+
const text = message.trim() === "" ? await commitMessage(cwd) : message;
|
|
4275
4423
|
await git(cwd, ["add", "--all"]);
|
|
4276
4424
|
await git(cwd, [
|
|
4277
4425
|
"commit",
|
|
4278
4426
|
"--message",
|
|
4279
|
-
|
|
4427
|
+
text
|
|
4280
4428
|
]);
|
|
4281
|
-
|
|
4429
|
+
if (andPush) try {
|
|
4430
|
+
await push$1(cwd);
|
|
4431
|
+
} catch (error) {
|
|
4432
|
+
throw new Error(`Committed, but push failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
4433
|
+
}
|
|
4434
|
+
return { message: text };
|
|
4282
4435
|
}
|
|
4283
4436
|
async function push$1(cwd) {
|
|
4284
4437
|
const status = await gitStatus(cwd);
|
|
4285
|
-
if (status === null) throw new Error(
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4438
|
+
if (status.branch === null) throw new Error("Cannot push a detached HEAD");
|
|
4439
|
+
try {
|
|
4440
|
+
if (status.upstream === null) await git(cwd, [
|
|
4441
|
+
"push",
|
|
4442
|
+
"--set-upstream",
|
|
4443
|
+
"origin",
|
|
4444
|
+
"HEAD"
|
|
4445
|
+
]);
|
|
4446
|
+
else await git(cwd, ["push"]);
|
|
4447
|
+
} catch (error) {
|
|
4448
|
+
throw new Error((error instanceof Error ? error.message : String(error)).split("\n").filter((line) => line.trim() !== "" && !line.startsWith("hint:") && !line.startsWith("Command failed:")).join("\n"), { cause: error });
|
|
4449
|
+
}
|
|
4450
|
+
}
|
|
4451
|
+
async function pullRequestBranches(cwd) {
|
|
4452
|
+
const head = (await gitStatus(cwd)).branch;
|
|
4453
|
+
if (head === null) throw new Error("Not on a branch");
|
|
4454
|
+
const base = (await run$1("gh", [
|
|
4455
|
+
"repo",
|
|
4456
|
+
"view",
|
|
4457
|
+
"--json",
|
|
4458
|
+
"defaultBranchRef",
|
|
4459
|
+
"--jq",
|
|
4460
|
+
".defaultBranchRef.name"
|
|
4461
|
+
], { cwd })).stdout.trim();
|
|
4462
|
+
if (base === head) throw new Error(`${head} is the default branch. Create a branch first.`);
|
|
4463
|
+
return {
|
|
4464
|
+
head,
|
|
4465
|
+
base,
|
|
4466
|
+
branches: (await git(cwd, [
|
|
4467
|
+
"ls-remote",
|
|
4468
|
+
"--heads",
|
|
4469
|
+
"origin"
|
|
4470
|
+
])).stdout.split("\n").filter(Boolean).map((line) => line.split("refs/heads/")[1]).filter((branch) => branch !== head)
|
|
4471
|
+
};
|
|
4472
|
+
}
|
|
4473
|
+
async function preparePullRequest(cwd, base, commitChanges) {
|
|
4474
|
+
const head = (await gitStatus(cwd)).branch;
|
|
4475
|
+
if (head === null) throw new Error("Not on a branch");
|
|
4476
|
+
if (commitChanges) await commit(cwd, "", true);
|
|
4477
|
+
const status = await gitStatus(cwd);
|
|
4478
|
+
if (status.upstream === null || status.ahead > 0) await push$1(cwd);
|
|
4479
|
+
const open = (await run$1("gh", [
|
|
4480
|
+
"pr",
|
|
4481
|
+
"list",
|
|
4482
|
+
"--head",
|
|
4483
|
+
head,
|
|
4484
|
+
"--base",
|
|
4485
|
+
base,
|
|
4486
|
+
"--state",
|
|
4487
|
+
"open",
|
|
4488
|
+
"--json",
|
|
4489
|
+
"url",
|
|
4490
|
+
"--jq",
|
|
4491
|
+
".[0].url // \"\""
|
|
4492
|
+
], { cwd })).stdout.trim();
|
|
4493
|
+
if (open !== "") return {
|
|
4494
|
+
kind: "open",
|
|
4495
|
+
url: open
|
|
4496
|
+
};
|
|
4497
|
+
await git(cwd, [
|
|
4498
|
+
"fetch",
|
|
4289
4499
|
"origin",
|
|
4290
|
-
|
|
4500
|
+
base
|
|
4291
4501
|
]);
|
|
4292
|
-
|
|
4293
|
-
|
|
4502
|
+
const commits = (await git(cwd, [
|
|
4503
|
+
"log",
|
|
4504
|
+
"--format=- %B",
|
|
4505
|
+
`origin/${base}..HEAD`
|
|
4506
|
+
])).stdout.trim();
|
|
4507
|
+
if (commits === "") throw new Error(`No commits between ${base} and ${head}`);
|
|
4508
|
+
const diff = (await git(cwd, [
|
|
4509
|
+
"diff",
|
|
4510
|
+
"--no-color",
|
|
4511
|
+
"--no-ext-diff",
|
|
4512
|
+
`origin/${base}...HEAD`
|
|
4513
|
+
])).stdout.slice(0, DIFF_LIMIT);
|
|
4514
|
+
const lines = (await generateText(cwd, PULL_REQUEST_INSTRUCTIONS, `Commits between base and head:\n${commits}\n\n${diff}\n\nGenerate the pull request Markdown document from the above.`)).split("\n");
|
|
4515
|
+
const title = lines[0].trim().replace(/^#{1,6}\s*/, "").replace(/^(\*\*|__|\*|_|`)+/, "").replace(/(\*\*|__|\*|_|`)+$/, "").trim();
|
|
4516
|
+
if (title === "") throw new Error("The model returned an empty title");
|
|
4517
|
+
return {
|
|
4518
|
+
kind: "draft",
|
|
4519
|
+
base,
|
|
4520
|
+
head,
|
|
4521
|
+
title,
|
|
4522
|
+
body: lines.slice(1).join("\n").trim()
|
|
4523
|
+
};
|
|
4294
4524
|
}
|
|
4295
|
-
async function createPullRequest(cwd) {
|
|
4525
|
+
async function createPullRequest(cwd, request) {
|
|
4296
4526
|
const url = (await run$1("gh", [
|
|
4297
4527
|
"pr",
|
|
4298
4528
|
"create",
|
|
4299
|
-
"--
|
|
4529
|
+
"--title",
|
|
4530
|
+
request.title,
|
|
4531
|
+
"--body",
|
|
4532
|
+
request.body,
|
|
4533
|
+
"--base",
|
|
4534
|
+
request.base,
|
|
4535
|
+
"--head",
|
|
4536
|
+
request.head,
|
|
4537
|
+
"--assignee",
|
|
4538
|
+
"@me"
|
|
4300
4539
|
], { cwd })).stdout.trim().split("\n").at(-1);
|
|
4301
4540
|
if (url === void 0) throw new Error("gh did not return a pull request URL");
|
|
4302
4541
|
return { url };
|
|
4303
4542
|
}
|
|
4304
|
-
async function
|
|
4543
|
+
async function commitMessage(cwd) {
|
|
4544
|
+
const diff = (await repoDiff(cwd, "")).slice(0, DIFF_LIMIT);
|
|
4545
|
+
if (diff === "") throw new Error("There is nothing to commit");
|
|
4546
|
+
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}`);
|
|
4547
|
+
}
|
|
4548
|
+
async function generateText(cwd, instructions, prompt) {
|
|
4305
4549
|
const settings = readConfig().commitMessage;
|
|
4306
4550
|
const provider = settings.provider;
|
|
4307
4551
|
const accountId = settings.accountId;
|
|
4308
4552
|
const model = settings.model;
|
|
4309
|
-
if (provider === null || model === null) throw new Error("Choose a
|
|
4553
|
+
if (provider === null || model === null) throw new Error("Choose a model in Settings → Git");
|
|
4310
4554
|
assertEnabled(provider, accountId);
|
|
4311
|
-
if (
|
|
4312
|
-
|
|
4313
|
-
if (diff === "") throw new Error("There is nothing to commit");
|
|
4314
|
-
const prompt = `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}`;
|
|
4315
|
-
if (provider === "codex") return codexCommitMessage(accountId, cwd, model, prompt);
|
|
4316
|
-
if (provider === "opencode") return openCodeCommitMessage(cwd, model, prompt);
|
|
4555
|
+
if (provider === "codex") return codexText(accountId, cwd, model, instructions, prompt);
|
|
4556
|
+
if (provider === "opencode") return openCodeText(cwd, model, `${instructions}\n\n${prompt}`);
|
|
4317
4557
|
const conversation = query({
|
|
4318
4558
|
prompt,
|
|
4319
4559
|
options: {
|
|
4320
4560
|
...claudeOptions(accountId, cwd),
|
|
4321
|
-
systemPrompt:
|
|
4561
|
+
systemPrompt: instructions,
|
|
4322
4562
|
tools: [],
|
|
4323
4563
|
model,
|
|
4324
4564
|
maxTurns: 1,
|
|
@@ -4329,15 +4569,14 @@ async function suggestCommitMessage(cwd) {
|
|
|
4329
4569
|
if (message.type !== "result") continue;
|
|
4330
4570
|
if (message.subtype !== "success") throw new Error(message.errors.join("\n"));
|
|
4331
4571
|
if (message.is_error) throw new Error(message.result);
|
|
4332
|
-
return
|
|
4572
|
+
return message.result.trim();
|
|
4333
4573
|
}
|
|
4334
4574
|
throw new Error("Claude did not reply");
|
|
4335
4575
|
}
|
|
4336
4576
|
async function projectDiff(cwd) {
|
|
4337
|
-
|
|
4338
|
-
const repos = childRepos(cwd, "", REPO_DEPTH);
|
|
4577
|
+
const repos = await projectRepos(cwd);
|
|
4339
4578
|
if (repos.length === 0) throw new Error(`${cwd} is not a git repository and has none inside it`);
|
|
4340
|
-
return (await Promise.all(repos.map((repo) => repoDiff(path.join(cwd, repo), `${repo}/`)))).join("");
|
|
4579
|
+
return (await Promise.all(repos.map((repo) => repoDiff(path.join(cwd, repo), repo === "" ? "" : `${repo}/`)))).join("");
|
|
4341
4580
|
}
|
|
4342
4581
|
function git(cwd, args) {
|
|
4343
4582
|
return run$1("git", args, {
|
|
@@ -4345,6 +4584,34 @@ function git(cwd, args) {
|
|
|
4345
4584
|
maxBuffer: 67108864
|
|
4346
4585
|
});
|
|
4347
4586
|
}
|
|
4587
|
+
async function projectRepos(cwd) {
|
|
4588
|
+
return await isRepo(cwd) ? [""] : childRepos(cwd, "", REPO_DEPTH);
|
|
4589
|
+
}
|
|
4590
|
+
async function gitStatus(cwd) {
|
|
4591
|
+
const output = await git(cwd, [
|
|
4592
|
+
"status",
|
|
4593
|
+
"--porcelain=v2",
|
|
4594
|
+
"--branch"
|
|
4595
|
+
]);
|
|
4596
|
+
const status = {
|
|
4597
|
+
branch: null,
|
|
4598
|
+
upstream: null,
|
|
4599
|
+
ahead: 0,
|
|
4600
|
+
behind: 0,
|
|
4601
|
+
changed: 0
|
|
4602
|
+
};
|
|
4603
|
+
for (const line of output.stdout.split("\n").filter(Boolean)) {
|
|
4604
|
+
const [marker, key, ...rest] = line.split(" ");
|
|
4605
|
+
if (marker !== "#") status.changed++;
|
|
4606
|
+
else if (key === "branch.head") status.branch = rest[0] === "(detached)" ? null : rest[0];
|
|
4607
|
+
else if (key === "branch.upstream") status.upstream = rest[0];
|
|
4608
|
+
else if (key === "branch.ab") {
|
|
4609
|
+
status.ahead = Number(rest[0]);
|
|
4610
|
+
status.behind = -Number(rest[1]);
|
|
4611
|
+
}
|
|
4612
|
+
}
|
|
4613
|
+
return status;
|
|
4614
|
+
}
|
|
4348
4615
|
async function isRepo(cwd) {
|
|
4349
4616
|
try {
|
|
4350
4617
|
await git(cwd, ["rev-parse", "--is-inside-work-tree"]);
|
|
@@ -4526,109 +4793,6 @@ function walk(root) {
|
|
|
4526
4793
|
return files;
|
|
4527
4794
|
}
|
|
4528
4795
|
//#endregion
|
|
4529
|
-
//#region src/services/push.ts
|
|
4530
|
-
const TITLE_LIMIT = 100;
|
|
4531
|
-
const BODY_LIMIT = 500;
|
|
4532
|
-
const TIME_TO_LIVE = 900;
|
|
4533
|
-
const SEND_TIMEOUT = 1e4;
|
|
4534
|
-
const SUBJECT = "https://github.com/NazmusSayad/offsite";
|
|
4535
|
-
let server$1 = "";
|
|
4536
|
-
let notifier = null;
|
|
4537
|
-
function startPush(address) {
|
|
4538
|
-
server$1 = address;
|
|
4539
|
-
}
|
|
4540
|
-
function pushActive() {
|
|
4541
|
-
return targets().length > 0;
|
|
4542
|
-
}
|
|
4543
|
-
function notifierClient() {
|
|
4544
|
-
return notifier === null ? null : notifier.client;
|
|
4545
|
-
}
|
|
4546
|
-
function claimNotifier(client, endpoint) {
|
|
4547
|
-
notifier = {
|
|
4548
|
-
client,
|
|
4549
|
-
endpoint
|
|
4550
|
-
};
|
|
4551
|
-
for (const item of clients) item.send({
|
|
4552
|
-
type: "notifier",
|
|
4553
|
-
active: item === client
|
|
4554
|
-
});
|
|
4555
|
-
}
|
|
4556
|
-
function subscribe(registration, client) {
|
|
4557
|
-
if (new URL(registration.subscription.endpoint).protocol !== "https:") throw new Error("the endpoint must be an https URL");
|
|
4558
|
-
const values = {
|
|
4559
|
-
endpoint: registration.subscription.endpoint,
|
|
4560
|
-
p256dh: registration.subscription.keys.p256dh,
|
|
4561
|
-
auth: registration.subscription.keys.auth,
|
|
4562
|
-
publicKey: registration.publicKey,
|
|
4563
|
-
privateKey: registration.privateKey,
|
|
4564
|
-
machine: registration.machine,
|
|
4565
|
-
server: server$1
|
|
4566
|
-
};
|
|
4567
|
-
db.insert(pushSubscriptions).values(values).onConflictDoUpdate({
|
|
4568
|
-
target: pushSubscriptions.endpoint,
|
|
4569
|
-
set: values
|
|
4570
|
-
}).run();
|
|
4571
|
-
if (notifier !== null && notifier.client === client) notifier.endpoint = registration.subscription.endpoint;
|
|
4572
|
-
console.log("a browser subscribed to push notifications");
|
|
4573
|
-
}
|
|
4574
|
-
function unsubscribe(endpoint) {
|
|
4575
|
-
db.delete(pushSubscriptions).where(eq(pushSubscriptions.endpoint, endpoint)).run();
|
|
4576
|
-
if (notifier !== null && notifier.endpoint === endpoint) notifier.endpoint = null;
|
|
4577
|
-
console.log("a browser unsubscribed from push notifications");
|
|
4578
|
-
}
|
|
4579
|
-
async function sendPush(message) {
|
|
4580
|
-
const subscriptions = targets();
|
|
4581
|
-
if (subscriptions.length === 0) return;
|
|
4582
|
-
const trimmed = {
|
|
4583
|
-
...message,
|
|
4584
|
-
title: [...message.title].slice(0, TITLE_LIMIT).join(""),
|
|
4585
|
-
body: [...message.body].slice(0, BODY_LIMIT).join("")
|
|
4586
|
-
};
|
|
4587
|
-
const gone = [];
|
|
4588
|
-
await Promise.all(subscriptions.map(async (subscription) => {
|
|
4589
|
-
try {
|
|
4590
|
-
await webPush.sendNotification({
|
|
4591
|
-
endpoint: subscription.endpoint,
|
|
4592
|
-
keys: {
|
|
4593
|
-
p256dh: subscription.p256dh,
|
|
4594
|
-
auth: subscription.auth
|
|
4595
|
-
}
|
|
4596
|
-
}, JSON.stringify({
|
|
4597
|
-
...trimmed,
|
|
4598
|
-
machineId: subscription.machine
|
|
4599
|
-
}), {
|
|
4600
|
-
vapidDetails: {
|
|
4601
|
-
subject: SUBJECT,
|
|
4602
|
-
publicKey: subscription.publicKey,
|
|
4603
|
-
privateKey: subscription.privateKey
|
|
4604
|
-
},
|
|
4605
|
-
TTL: TIME_TO_LIVE,
|
|
4606
|
-
urgency: "high",
|
|
4607
|
-
timeout: SEND_TIMEOUT
|
|
4608
|
-
});
|
|
4609
|
-
} catch (error) {
|
|
4610
|
-
const status = error.statusCode;
|
|
4611
|
-
if (status !== void 0 && [
|
|
4612
|
-
401,
|
|
4613
|
-
403,
|
|
4614
|
-
404,
|
|
4615
|
-
410
|
|
4616
|
-
].includes(status)) {
|
|
4617
|
-
console.log(`push subscription ${subscription.endpoint} is gone or was made for another key, removing it`);
|
|
4618
|
-
gone.push(subscription.endpoint);
|
|
4619
|
-
return;
|
|
4620
|
-
}
|
|
4621
|
-
console.warn(`could not send a push to ${subscription.endpoint}: ${error}`);
|
|
4622
|
-
}
|
|
4623
|
-
}));
|
|
4624
|
-
if (gone.length === 0) return;
|
|
4625
|
-
db.delete(pushSubscriptions).where(and(eq(pushSubscriptions.server, server$1), inArray(pushSubscriptions.endpoint, gone))).run();
|
|
4626
|
-
}
|
|
4627
|
-
function targets() {
|
|
4628
|
-
if (notifier === null || notifier.endpoint === null) return [];
|
|
4629
|
-
return db.select().from(pushSubscriptions).where(and(eq(pushSubscriptions.server, server$1), eq(pushSubscriptions.endpoint, notifier.endpoint))).all();
|
|
4630
|
-
}
|
|
4631
|
-
//#endregion
|
|
4632
4796
|
//#region src/services/runs.ts
|
|
4633
4797
|
const locks = /* @__PURE__ */ new Map();
|
|
4634
4798
|
function stopAll() {
|
|
@@ -5144,6 +5308,23 @@ function executable(name) {
|
|
|
5144
5308
|
return process.platform === "win32" ? `${name}.exe` : name;
|
|
5145
5309
|
}
|
|
5146
5310
|
//#endregion
|
|
5311
|
+
//#region ../core/src/terminal.ts
|
|
5312
|
+
function encodeFrame(kind, id, data) {
|
|
5313
|
+
const frame = new Uint8Array(5 + data.length);
|
|
5314
|
+
frame[0] = kind;
|
|
5315
|
+
new DataView(frame.buffer).setUint32(1, id);
|
|
5316
|
+
frame.set(data, 5);
|
|
5317
|
+
return frame;
|
|
5318
|
+
}
|
|
5319
|
+
function decodeFrame(frame) {
|
|
5320
|
+
if (frame.length < 5) throw new Error("terminal frame is too short");
|
|
5321
|
+
return {
|
|
5322
|
+
kind: frame[0],
|
|
5323
|
+
id: new DataView(frame.buffer, frame.byteOffset, frame.byteLength).getUint32(1),
|
|
5324
|
+
data: frame.subarray(5)
|
|
5325
|
+
};
|
|
5326
|
+
}
|
|
5327
|
+
//#endregion
|
|
5147
5328
|
//#region src/services/terminal/alerts.ts
|
|
5148
5329
|
const BELL_GAP = 5e3;
|
|
5149
5330
|
const NOTIFICATION_GAP = 1e3;
|
|
@@ -5582,6 +5763,7 @@ function push(id, alert, windowTitle) {
|
|
|
5582
5763
|
const tab = findTab(id);
|
|
5583
5764
|
if (title === "") title = tab?.customName ?? (windowTitle || "Terminal");
|
|
5584
5765
|
sendPush({
|
|
5766
|
+
kind: "terminal",
|
|
5585
5767
|
title,
|
|
5586
5768
|
body,
|
|
5587
5769
|
projectId: entry[0],
|
|
@@ -5826,12 +6008,16 @@ const handlers = {
|
|
|
5826
6008
|
"files.read": (params) => readProjectFile(projectPath(params.projectId), params.path),
|
|
5827
6009
|
"files.upload": (params) => saveUpload(params.name, Buffer.from(params.data, "base64")),
|
|
5828
6010
|
"tasks.output": (params) => taskOutput(params.sessionId, params.path),
|
|
5829
|
-
"git.status": (params) =>
|
|
6011
|
+
"git.status": (params) => gitRepos(projectPath(params.projectId)),
|
|
5830
6012
|
"git.diff": (params) => projectDiff(projectPath(params.projectId)),
|
|
5831
|
-
"git.commit": (params) => commit(projectPath(params.projectId), params.message),
|
|
5832
|
-
"git.push": (params) =>
|
|
5833
|
-
|
|
5834
|
-
|
|
6013
|
+
"git.commit": (params) => commit(path.join(projectPath(params.projectId), params.repo), params.message, params.push),
|
|
6014
|
+
"git.push": async (params) => {
|
|
6015
|
+
await push$1(path.join(projectPath(params.projectId), params.repo));
|
|
6016
|
+
return null;
|
|
6017
|
+
},
|
|
6018
|
+
"git.branches": (params) => pullRequestBranches(path.join(projectPath(params.projectId), params.repo)),
|
|
6019
|
+
"git.pullRequest": (params) => preparePullRequest(path.join(projectPath(params.projectId), params.repo), params.base, params.commit),
|
|
6020
|
+
"git.createPullRequest": (params) => createPullRequest(path.join(projectPath(params.projectId), params.repo), params),
|
|
5835
6021
|
"push.subscribe": (params, client) => {
|
|
5836
6022
|
subscribe(params, client);
|
|
5837
6023
|
return null;
|