diffing 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -4
- package/dist/cli-agent-BLPX3oOP.mjs +443 -0
- package/dist/cli-agent-BNwY5lZE.mjs +446 -0
- package/dist/cli-agent-CbzXDSSF.mjs +446 -0
- package/dist/cli-agent-DsqbS3n7.mjs +443 -0
- package/dist/cli-agent-ZAkixf4c.mjs +446 -0
- package/dist/cli.mjs +539 -33
- package/dist/client/assets/{angular-html-fdYbuxHF.js → angular-html-DsssUIMZ.js} +1 -1
- package/dist/client/assets/{angular-ts-D3iFqR-R.js → angular-ts-dTg-sJ77.js} +1 -1
- package/dist/client/assets/{apl-MnBV-iQN.js → apl-C1yUKbqz.js} +1 -1
- package/dist/client/assets/{astro-BrpK01E7.js → astro-BI07ha2y.js} +1 -1
- package/dist/client/assets/{blade-CGmyBuwD.js → blade-DkzUGffS.js} +1 -1
- package/dist/client/assets/{c-xQnwtG9Y.js → c-KkuqqOvW.js} +1 -1
- package/dist/client/assets/{cobol-ypVzOIQu.js → cobol-DRh7-jLi.js} +1 -1
- package/dist/client/assets/{coffee-CrtE1uj2.js → coffee-wR_ZAh9a.js} +1 -1
- package/dist/client/assets/{cpp-DLd4XEKA.js → cpp-OD_qyywn.js} +1 -1
- package/dist/client/assets/{crystal-CJYvpLt_.js → crystal-E4ahAddE.js} +1 -1
- package/dist/client/assets/{css-DrX2B9os.js → css-2NgE_uxz.js} +1 -1
- package/dist/client/assets/{edge-BsQYk9OW.js → edge-8mbscxxD.js} +1 -1
- package/dist/client/assets/{elixir-DU_VfGYR.js → elixir-CJv1b3Q6.js} +1 -1
- package/dist/client/assets/{elm-W4eOLEDv.js → elm-DUPzqZ9H.js} +1 -1
- package/dist/client/assets/{erb-D9FdIJ2e.js → erb-ssvEF26z.js} +1 -1
- package/dist/client/assets/{git-rebase-BtPCXjbS.js → git-rebase-DiWIK1Oa.js} +1 -1
- package/dist/client/assets/{glimmer-js-BVfUGMUb.js → glimmer-js-DrHg6AuA.js} +1 -1
- package/dist/client/assets/{glimmer-ts-BZEaHDZL.js → glimmer-ts-wQSGOa8A.js} +1 -1
- package/dist/client/assets/{glsl-CSiNDuwt.js → glsl-OY27QGY0.js} +1 -1
- package/dist/client/assets/{graphql-Crf08Rhb.js → graphql-CSWSqe6f.js} +1 -1
- package/dist/client/assets/{hack-DUiQuO20.js → hack-Bi1v624Q.js} +1 -1
- package/dist/client/assets/{haml-BtvVTDRG.js → haml-CX94zV9m.js} +1 -1
- package/dist/client/assets/{handlebars-Bu_cdZA_.js → handlebars-C1j6wnjG.js} +1 -1
- package/dist/client/assets/{html-C4cun3zD.js → html-BcAjMlB7.js} +1 -1
- package/dist/client/assets/{html-derivative-CQnD80KV.js → html-derivative-DcEpOzdx.js} +1 -1
- package/dist/client/assets/{http-DuOJF9Kg.js → http-BBxxSc6S.js} +1 -1
- package/dist/client/assets/{hurl-DgrdYsYh.js → hurl-CpKRNTz4.js} +1 -1
- package/dist/client/assets/{index-CZZd7iTh.js → index-Bwv7BWUp.js} +156 -84
- package/dist/client/assets/index-CoZyJYkw.css +1 -0
- package/dist/client/assets/{java-D-vtFiv8.js → java-CFUlLvAA.js} +1 -1
- package/dist/client/assets/{javascript-CFYFXvdN.js → javascript-C-HnT3sE.js} +1 -1
- package/dist/client/assets/{jinja-fZvIg2_s.js → jinja-CPU8RwNP.js} +1 -1
- package/dist/client/assets/{jison-DEzxLq5g.js → jison-CW0TBQOs.js} +1 -1
- package/dist/client/assets/{json-QM4VBqMC.js → json-qUjlNuEJ.js} +1 -1
- package/dist/client/assets/{jsx-DLzUM_cC.js → jsx-KWeo60kf.js} +1 -1
- package/dist/client/assets/{julia-Dwt1SIpn.js → julia-CnJB8y2e.js} +1 -1
- package/dist/client/assets/{just-Dwp-hLcL.js → just-Do22d9PP.js} +1 -1
- package/dist/client/assets/{latex-CMF1JViz.js → latex-CCPgzvKn.js} +1 -1
- package/dist/client/assets/{liquid-czkpOZWY.js → liquid-B9btdZoI.js} +1 -1
- package/dist/client/assets/{lua-DE5PD0Vh.js → lua-IFRifurW.js} +1 -1
- package/dist/client/assets/{marko-DHeEvZiW.js → marko-CCaVBFq1.js} +1 -1
- package/dist/client/assets/{mdc-B941liUQ.js → mdc-BtbnOLUc.js} +1 -1
- package/dist/client/assets/{nginx-Pb9t8c-r.js → nginx-BkbFZHVt.js} +1 -1
- package/dist/client/assets/{nim-MMUvW_R1.js → nim-2uCd5j65.js} +1 -1
- package/dist/client/assets/{perl-DXwXRmuC.js → perl-C4ixqSNT.js} +1 -1
- package/dist/client/assets/{php-D42kv59r.js → php-DJ8WOtcI.js} +1 -1
- package/dist/client/assets/{pug-v0iesski.js → pug-BOaafhT5.js} +1 -1
- package/dist/client/assets/{qml-spxQQ-ei.js → qml-CURE-1y4.js} +1 -1
- package/dist/client/assets/{r-CobWTBG9.js → r-CPWmgzo3.js} +1 -1
- package/dist/client/assets/{razor-B-_Ww3MB.js → razor-DH83VP_d.js} +1 -1
- package/dist/client/assets/{regexp-BJMVs8R0.js → regexp-BXVWi-t_.js} +1 -1
- package/dist/client/assets/{rst-BHcaWsY0.js → rst-Bbxxs6E2.js} +1 -1
- package/dist/client/assets/{ruby-BCMT6kog.js → ruby-C-TuWES4.js} +1 -1
- package/dist/client/assets/{sas-DHfZnd3R.js → sas-njzNYNOq.js} +1 -1
- package/dist/client/assets/{scss-DjDsJvDO.js → scss-D2i6rHB8.js} +1 -1
- package/dist/client/assets/{shellscript-B6qaIiU-.js → shellscript-D6Bxi40C.js} +1 -1
- package/dist/client/assets/{shellsession-ByCeir2b.js → shellsession-Defxq-I6.js} +1 -1
- package/dist/client/assets/{soy-C6WpnMSF.js → soy-D0GKXrtT.js} +1 -1
- package/dist/client/assets/{sql-BIvSNLV_.js → sql-BRW_ADkU.js} +1 -1
- package/dist/client/assets/{stata-PG9iZ--I.js → stata-w7w59F48.js} +1 -1
- package/dist/client/assets/{surrealql-CmvCzG09.js → surrealql-O-lLuqz7.js} +1 -1
- package/dist/client/assets/{svelte-ChumG88s.js → svelte-DN3DR5-t.js} +1 -1
- package/dist/client/assets/{templ-CmzuRY_a.js → templ-CJ2JFoia.js} +1 -1
- package/dist/client/assets/{tex-DL8t-_eJ.js → tex-CpWrf5g9.js} +1 -1
- package/dist/client/assets/{ts-tags-BFfE0Egm.js → ts-tags-CZ6Af6Pe.js} +1 -1
- package/dist/client/assets/{tsx-C1bFyHcE.js → tsx-gIUSpRih.js} +1 -1
- package/dist/client/assets/{twig-DkIluZEQ.js → twig-DwZINhmw.js} +1 -1
- package/dist/client/assets/{typescript-Gc86Bc3j.js → typescript-Dj2aMECR.js} +1 -1
- package/dist/client/assets/{vue-C88JmL53.js → vue-B5K0aWrP.js} +1 -1
- package/dist/client/assets/{vue-html-Cry4aksC.js → vue-html-DKGgrypd.js} +1 -1
- package/dist/client/assets/{vue-vine-zlDkA3Ui.js → vue-vine-B5iDYTCh.js} +1 -1
- package/dist/client/assets/{xml-NvwpIFDy.js → xml-CbTzxfc-.js} +1 -1
- package/dist/client/assets/{xsl-CDjXvY9w.js → xsl-Dj-Be6nx.js} +1 -1
- package/dist/client/assets/{yaml-Bh-LPjcH.js → yaml-rTK8uEjh.js} +1 -1
- package/dist/client/index.html +2 -5
- package/dist/mcp-CE77qfjx.mjs +254 -0
- package/dist/mcp-CFeE13n-.mjs +254 -0
- package/dist/mcp-SGCrfYGo.mjs +254 -0
- package/dist/server-lock-B4crz9-_.mjs +708 -0
- package/dist/server-lock-DpCrvL-Z.mjs +714 -0
- package/dist/update-check-DJuFE9w6.mjs +128 -0
- package/dist/update-check-DSC02MWQ.mjs +129 -0
- package/dist/update-check-KL-2sGxE.mjs +129 -0
- package/package.json +12 -14
- package/dist/client/assets/index-CkC-EvWo.css +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as revertHunk, S as listRepoFiles, _ as getRepoRoot, a as extractPlanLines, b as gitAddFile, c as formatComments, d as getFileContent, f as getGitDiffAsync, g as getRepoName, h as getProjectStorageDir, i as writeServerLock, l as getBranchName, m as getMergeStatus, o as formatPlanReview, p as getHunkHistory, r as removeServerLock, s as sectionTitleForLine, u as getCustomGitDiffAsync, v as getTabSizeForFiles, w as isSafePath, x as isGitRepo, y as getUntrackedFilePathsAsync } from "./server-lock-
|
|
2
|
+
import { C as revertHunk, S as listRepoFiles, T as toSafeRelativePath, _ as getRepoRoot, a as extractPlanLines, b as gitAddFile, c as formatComments, d as getFileContent, f as getGitDiffAsync, g as getRepoName, h as getProjectStorageDir, i as writeServerLock, l as getBranchName, m as getMergeStatus, o as formatPlanReview, p as getHunkHistory, r as removeServerLock, s as sectionTitleForLine, u as getCustomGitDiffAsync, v as getTabSizeForFiles, w as isSafePath, x as isGitRepo, y as getUntrackedFilePathsAsync } from "./server-lock-DpCrvL-Z.mjs";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { dirname, extname, join, resolve } from "node:path";
|
|
5
5
|
import { existsSync, mkdirSync, readFileSync, watch, writeFileSync } from "node:fs";
|
|
@@ -11,6 +11,7 @@ import { homedir } from "node:os";
|
|
|
11
11
|
import { Hono } from "hono";
|
|
12
12
|
import { streamSSE } from "hono/streaming";
|
|
13
13
|
import { serve } from "@hono/node-server";
|
|
14
|
+
import { stdout } from "node:process";
|
|
14
15
|
//#region src/lib/diff-options.ts
|
|
15
16
|
const DEFAULTS$1 = {
|
|
16
17
|
revisions: [],
|
|
@@ -1112,7 +1113,10 @@ const DEFAULTS = {
|
|
|
1112
1113
|
expandContextByDefault: false,
|
|
1113
1114
|
collapsedContextThreshold: 10,
|
|
1114
1115
|
expansionLineCount: 20,
|
|
1115
|
-
haptics: true
|
|
1116
|
+
haptics: true,
|
|
1117
|
+
sounds: true,
|
|
1118
|
+
uiFont: null,
|
|
1119
|
+
monoFont: null
|
|
1116
1120
|
};
|
|
1117
1121
|
function loadSettings() {
|
|
1118
1122
|
try {
|
|
@@ -1139,9 +1143,15 @@ function saveSettings(settings) {
|
|
|
1139
1143
|
var FileCommentStore = class {
|
|
1140
1144
|
dirPath;
|
|
1141
1145
|
filePath;
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1146
|
+
/**
|
|
1147
|
+
* @param storageDir Absolute directory to persist `comments.json` in.
|
|
1148
|
+
* Defaults to the per-repo storage dir under `~/.diffing` — comments are
|
|
1149
|
+
* NEVER written inside the reviewed (consumer) repo, so a consumer project
|
|
1150
|
+
* stays free of any diffing-specific artifacts. The override exists only so
|
|
1151
|
+
* tests can point at a throwaway temp dir.
|
|
1152
|
+
*/
|
|
1153
|
+
constructor(storageDir) {
|
|
1154
|
+
this.dirPath = storageDir ?? getProjectStorageDir();
|
|
1145
1155
|
this.filePath = join(this.dirPath, "comments.json");
|
|
1146
1156
|
}
|
|
1147
1157
|
async getAll() {
|
|
@@ -1261,9 +1271,15 @@ function applyUpsert(plans, input, now) {
|
|
|
1261
1271
|
var FilePlanStore = class {
|
|
1262
1272
|
dirPath;
|
|
1263
1273
|
filePath;
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1274
|
+
/**
|
|
1275
|
+
* @param storageDir Absolute directory to persist `plans.json` in. Defaults
|
|
1276
|
+
* to the per-repo storage dir under `~/.diffing`, the same place comments
|
|
1277
|
+
* and media live — plans are NEVER written inside the reviewed (consumer)
|
|
1278
|
+
* repo, so a consumer project stays free of any diffing-specific artifacts.
|
|
1279
|
+
* The override exists only so tests can point at a throwaway temp dir.
|
|
1280
|
+
*/
|
|
1281
|
+
constructor(storageDir) {
|
|
1282
|
+
this.dirPath = storageDir ?? getProjectStorageDir();
|
|
1267
1283
|
this.filePath = join(this.dirPath, "plans.json");
|
|
1268
1284
|
}
|
|
1269
1285
|
async getAll() {
|
|
@@ -1389,6 +1405,39 @@ var FilePlanStore = class {
|
|
|
1389
1405
|
}
|
|
1390
1406
|
};
|
|
1391
1407
|
//#endregion
|
|
1408
|
+
//#region src/lib/state.ts
|
|
1409
|
+
var FileUiStateStore = class {
|
|
1410
|
+
dirPath;
|
|
1411
|
+
filePath;
|
|
1412
|
+
constructor(storageDir) {
|
|
1413
|
+
this.dirPath = storageDir ?? getProjectStorageDir();
|
|
1414
|
+
this.filePath = join(this.dirPath, "ui-state.json");
|
|
1415
|
+
}
|
|
1416
|
+
async getAll() {
|
|
1417
|
+
try {
|
|
1418
|
+
const data = await readFile(this.filePath, "utf-8");
|
|
1419
|
+
return JSON.parse(data);
|
|
1420
|
+
} catch {
|
|
1421
|
+
return {};
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
async setAll(state) {
|
|
1425
|
+
await this.save(state);
|
|
1426
|
+
}
|
|
1427
|
+
async save(state) {
|
|
1428
|
+
try {
|
|
1429
|
+
await mkdir(this.dirPath, { recursive: true });
|
|
1430
|
+
try {
|
|
1431
|
+
const repoRoot = getRepoRoot();
|
|
1432
|
+
await writeFile(join(this.dirPath, "repo_path.txt"), repoRoot, "utf-8");
|
|
1433
|
+
} catch {}
|
|
1434
|
+
await writeFile(this.filePath, JSON.stringify(state, null, 2), "utf-8");
|
|
1435
|
+
} catch (err) {
|
|
1436
|
+
console.error("Failed to save UI state to file:", err);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
};
|
|
1440
|
+
//#endregion
|
|
1392
1441
|
//#region src/lib/review-session.ts
|
|
1393
1442
|
var ReviewSession = class {
|
|
1394
1443
|
round = 0;
|
|
@@ -1452,7 +1501,8 @@ var ReviewSession = class {
|
|
|
1452
1501
|
sentAt: input.sentAt,
|
|
1453
1502
|
commentXml: input.commentXml,
|
|
1454
1503
|
openCount: input.openCount,
|
|
1455
|
-
comments: input.comments
|
|
1504
|
+
comments: input.comments,
|
|
1505
|
+
decision: input.decision
|
|
1456
1506
|
};
|
|
1457
1507
|
this.lastPayload = payload;
|
|
1458
1508
|
const waiters = [...this.waiters];
|
|
@@ -1604,6 +1654,7 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
|
|
|
1604
1654
|
const customMode = isCustomMode(diffOpts);
|
|
1605
1655
|
const store = commentStore ?? new FileCommentStore();
|
|
1606
1656
|
const plans = planStore ?? new FilePlanStore();
|
|
1657
|
+
const uiStateStore = new FileUiStateStore();
|
|
1607
1658
|
const viewedFiles = /* @__PURE__ */ new Set();
|
|
1608
1659
|
const activeClients = /* @__PURE__ */ new Set();
|
|
1609
1660
|
const broadcast = (event, data) => {
|
|
@@ -1737,8 +1788,9 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
|
|
|
1737
1788
|
if (!filePath) return c.json({ error: "filePath is required" }, 400);
|
|
1738
1789
|
try {
|
|
1739
1790
|
const root = getRepoRoot();
|
|
1740
|
-
|
|
1741
|
-
|
|
1791
|
+
const relPath = toSafeRelativePath(filePath, root);
|
|
1792
|
+
if (!relPath) return c.json({ error: "Forbidden file path" }, 403);
|
|
1793
|
+
const absolutePath = resolve(root, relPath);
|
|
1742
1794
|
const { exec } = await import("node:child_process");
|
|
1743
1795
|
if (editor === "vscode") exec(`code "${absolutePath}"`);
|
|
1744
1796
|
else if (editor === "zed") exec(`zed "${absolutePath}"`);
|
|
@@ -1812,8 +1864,9 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
|
|
|
1812
1864
|
const { filePath, hunkIndex } = await c.req.json();
|
|
1813
1865
|
if (!filePath || typeof hunkIndex !== "number") return c.json({ error: "Missing filePath or hunkIndex" }, 400);
|
|
1814
1866
|
try {
|
|
1815
|
-
|
|
1816
|
-
|
|
1867
|
+
const relPath = toSafeRelativePath(filePath, getRepoRoot());
|
|
1868
|
+
if (!relPath) return c.json({ error: "Forbidden file path" }, 403);
|
|
1869
|
+
revertHunk(relPath, hunkIndex);
|
|
1817
1870
|
return c.json({ ok: true });
|
|
1818
1871
|
} catch (err) {
|
|
1819
1872
|
const stderr = typeof err?.stderr === "string" ? err.stderr : err?.stderr instanceof Buffer ? err.stderr.toString("utf-8") : "";
|
|
@@ -1826,8 +1879,9 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
|
|
|
1826
1879
|
const deletionCount = Number(c.req.query("deletionCount"));
|
|
1827
1880
|
if (!filePath || Number.isNaN(deletionStart) || Number.isNaN(deletionCount)) return c.json({ error: "Missing or invalid parameters" }, 400);
|
|
1828
1881
|
try {
|
|
1829
|
-
|
|
1830
|
-
|
|
1882
|
+
const relPath = toSafeRelativePath(filePath, getRepoRoot());
|
|
1883
|
+
if (!relPath) return c.json({ error: "Forbidden file path" }, 403);
|
|
1884
|
+
const history = getHunkHistory(relPath, deletionStart, deletionCount, diffOpts.revisions[0] || "HEAD");
|
|
1831
1885
|
return c.json(history);
|
|
1832
1886
|
} catch (err) {
|
|
1833
1887
|
return c.json({ error: err.message || "Failed to fetch hunk history" }, 500);
|
|
@@ -1838,10 +1892,11 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
|
|
|
1838
1892
|
if (!filePath || typeof content !== "string") return c.json({ error: "Missing filePath or content" }, 400);
|
|
1839
1893
|
try {
|
|
1840
1894
|
const root = getRepoRoot();
|
|
1841
|
-
|
|
1842
|
-
|
|
1895
|
+
const relPath = toSafeRelativePath(filePath, root);
|
|
1896
|
+
if (!relPath) return c.json({ error: "Forbidden file path" }, 403);
|
|
1897
|
+
await writeFile(resolve(root, relPath), content, "utf-8");
|
|
1843
1898
|
if (gitAdd) try {
|
|
1844
|
-
gitAddFile(
|
|
1899
|
+
gitAddFile(relPath);
|
|
1845
1900
|
} catch (err) {
|
|
1846
1901
|
return c.json({
|
|
1847
1902
|
ok: true,
|
|
@@ -1860,6 +1915,19 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
|
|
|
1860
1915
|
const settings = saveSettings(await c.req.json());
|
|
1861
1916
|
return c.json(settings);
|
|
1862
1917
|
});
|
|
1918
|
+
app.get("/api/ui-state", async (c) => {
|
|
1919
|
+
return c.json(await uiStateStore.getAll());
|
|
1920
|
+
});
|
|
1921
|
+
app.put("/api/ui-state", async (c) => {
|
|
1922
|
+
const body = await c.req.json();
|
|
1923
|
+
const merged = {
|
|
1924
|
+
...await uiStateStore.getAll(),
|
|
1925
|
+
...body
|
|
1926
|
+
};
|
|
1927
|
+
for (const key of Object.keys(body)) if (body[key] === null || body[key] === void 0) delete merged[key];
|
|
1928
|
+
await uiStateStore.setAll(merged);
|
|
1929
|
+
return c.json(merged);
|
|
1930
|
+
});
|
|
1863
1931
|
app.get("/api/viewed", (c) => {
|
|
1864
1932
|
return c.json([...viewedFiles]);
|
|
1865
1933
|
});
|
|
@@ -1940,8 +2008,9 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
|
|
|
1940
2008
|
const suggestion = match[1];
|
|
1941
2009
|
try {
|
|
1942
2010
|
const root = getRepoRoot();
|
|
1943
|
-
|
|
1944
|
-
|
|
2011
|
+
const relPath = toSafeRelativePath(comment.filePath, root);
|
|
2012
|
+
if (!relPath) return c.json({ error: "Forbidden file path" }, 403);
|
|
2013
|
+
const absolutePath = resolve(root, relPath);
|
|
1945
2014
|
const lines = (await readFile(absolutePath, "utf-8")).split(/\r?\n/);
|
|
1946
2015
|
const lineIdx = comment.lineNumber - 1;
|
|
1947
2016
|
if (lineIdx < 0 || lineIdx >= lines.length) return c.json({ error: "Comment line number out of range" }, 400);
|
|
@@ -1961,18 +2030,21 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
|
|
|
1961
2030
|
app.post("/api/review/send", async (c) => {
|
|
1962
2031
|
const body = await c.req.json().catch(() => ({}));
|
|
1963
2032
|
const generalComment = typeof body?.generalComment === "string" ? body.generalComment : void 0;
|
|
2033
|
+
const decision = body?.decision === "approved" || body?.decision === "changes-requested" || body?.decision === "rejected" ? body.decision : void 0;
|
|
1964
2034
|
const all = await store.getAll();
|
|
1965
2035
|
const openCount = all.filter((x) => x.status === "open").length;
|
|
1966
2036
|
const payload = reviewSession.send({
|
|
1967
2037
|
sentAt: Date.now(),
|
|
1968
|
-
commentXml: formatComments(all, generalComment),
|
|
2038
|
+
commentXml: formatComments(all, generalComment, decision),
|
|
1969
2039
|
openCount,
|
|
1970
|
-
comments: all
|
|
2040
|
+
comments: all,
|
|
2041
|
+
decision
|
|
1971
2042
|
});
|
|
1972
2043
|
return c.json({
|
|
1973
2044
|
ok: true,
|
|
1974
2045
|
round: payload.round,
|
|
1975
2046
|
openCount: payload.openCount,
|
|
2047
|
+
decision: payload.decision,
|
|
1976
2048
|
waiters: reviewSession.snapshot().waiters
|
|
1977
2049
|
});
|
|
1978
2050
|
});
|
|
@@ -2182,6 +2254,40 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
|
|
|
2182
2254
|
});
|
|
2183
2255
|
return app;
|
|
2184
2256
|
}
|
|
2257
|
+
/**
|
|
2258
|
+
* Newest mtime (epoch ms) across everything that counts as project activity:
|
|
2259
|
+
* comments.json, plans.json, and any attachment (media) file. Plans and media
|
|
2260
|
+
* extend a project's life exactly like comments do, so retention is uniform
|
|
2261
|
+
* across all three. Returns null when none of them exist yet.
|
|
2262
|
+
*/
|
|
2263
|
+
async function newestActivityMs(projectDir) {
|
|
2264
|
+
const candidates = [];
|
|
2265
|
+
const safeStat = async (p) => {
|
|
2266
|
+
try {
|
|
2267
|
+
return (await stat(p)).mtimeMs;
|
|
2268
|
+
} catch {
|
|
2269
|
+
return null;
|
|
2270
|
+
}
|
|
2271
|
+
};
|
|
2272
|
+
for (const name of ["comments.json", "plans.json"]) {
|
|
2273
|
+
const file = join(projectDir, name);
|
|
2274
|
+
if (!existsSync(file)) continue;
|
|
2275
|
+
const m = await safeStat(file);
|
|
2276
|
+
if (m !== null) candidates.push(m);
|
|
2277
|
+
}
|
|
2278
|
+
const attachmentsDir = join(projectDir, "attachments");
|
|
2279
|
+
if (existsSync(attachmentsDir)) {
|
|
2280
|
+
const dirM = await safeStat(attachmentsDir);
|
|
2281
|
+
if (dirM !== null) candidates.push(dirM);
|
|
2282
|
+
try {
|
|
2283
|
+
for (const file of await readdir(attachmentsDir)) {
|
|
2284
|
+
const m = await safeStat(join(attachmentsDir, file));
|
|
2285
|
+
if (m !== null) candidates.push(m);
|
|
2286
|
+
}
|
|
2287
|
+
} catch {}
|
|
2288
|
+
}
|
|
2289
|
+
return candidates.length ? Math.max(...candidates) : null;
|
|
2290
|
+
}
|
|
2185
2291
|
async function cleanupStaleProjects() {
|
|
2186
2292
|
const baseDir = join(homedir(), ".diffing");
|
|
2187
2293
|
if (!existsSync(baseDir)) return;
|
|
@@ -2192,15 +2298,15 @@ async function cleanupStaleProjects() {
|
|
|
2192
2298
|
for (const entry of entries) if (entry.isDirectory()) {
|
|
2193
2299
|
const projectDir = join(baseDir, entry.name);
|
|
2194
2300
|
const repoPathFile = join(projectDir, "repo_path.txt");
|
|
2195
|
-
const commentsFile = join(projectDir, "comments.json");
|
|
2196
2301
|
let shouldDelete = false;
|
|
2197
2302
|
if (existsSync(repoPathFile)) try {
|
|
2198
2303
|
const repoPath = (await readFile(repoPathFile, "utf-8")).trim();
|
|
2199
2304
|
if (!repoPath || !existsSync(repoPath)) shouldDelete = true;
|
|
2200
2305
|
} catch {}
|
|
2201
|
-
if (!shouldDelete
|
|
2202
|
-
|
|
2203
|
-
|
|
2306
|
+
if (!shouldDelete) {
|
|
2307
|
+
const newest = await newestActivityMs(projectDir);
|
|
2308
|
+
if (newest !== null && now - newest > STALE_TIME) shouldDelete = true;
|
|
2309
|
+
}
|
|
2204
2310
|
if (shouldDelete) await rm(projectDir, {
|
|
2205
2311
|
recursive: true,
|
|
2206
2312
|
force: true
|
|
@@ -2226,6 +2332,389 @@ function startServer(options) {
|
|
|
2226
2332
|
});
|
|
2227
2333
|
}
|
|
2228
2334
|
//#endregion
|
|
2335
|
+
//#region src/lib/startup-display.ts
|
|
2336
|
+
const R = "\x1B[0m";
|
|
2337
|
+
const B = "\x1B[1m";
|
|
2338
|
+
const HIDE = "\x1B[?25l";
|
|
2339
|
+
const SHOW = "\x1B[?25h";
|
|
2340
|
+
const CLR = "\x1B[2K\r";
|
|
2341
|
+
const GLITCH_CHARS = "░▒▓█▄▀■□▸▹◆◇";
|
|
2342
|
+
const c = (n) => `\x1b[38;5;${n}m`;
|
|
2343
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2344
|
+
const up = (n) => `\x1b[${n}A`;
|
|
2345
|
+
const PALETTES = [
|
|
2346
|
+
{
|
|
2347
|
+
base: c(51),
|
|
2348
|
+
text: c(159),
|
|
2349
|
+
faint: c(30),
|
|
2350
|
+
glow: B + c(87),
|
|
2351
|
+
rain: c(37)
|
|
2352
|
+
},
|
|
2353
|
+
{
|
|
2354
|
+
base: c(82),
|
|
2355
|
+
text: c(155),
|
|
2356
|
+
faint: c(22),
|
|
2357
|
+
glow: B + c(118),
|
|
2358
|
+
rain: c(34)
|
|
2359
|
+
},
|
|
2360
|
+
{
|
|
2361
|
+
base: c(207),
|
|
2362
|
+
text: c(219),
|
|
2363
|
+
faint: c(96),
|
|
2364
|
+
glow: B + c(213),
|
|
2365
|
+
rain: c(128)
|
|
2366
|
+
},
|
|
2367
|
+
{
|
|
2368
|
+
base: c(220),
|
|
2369
|
+
text: c(229),
|
|
2370
|
+
faint: c(136),
|
|
2371
|
+
glow: B + c(226),
|
|
2372
|
+
rain: c(178)
|
|
2373
|
+
},
|
|
2374
|
+
{
|
|
2375
|
+
base: c(75),
|
|
2376
|
+
text: c(153),
|
|
2377
|
+
faint: c(25),
|
|
2378
|
+
glow: B + c(81),
|
|
2379
|
+
rain: c(39)
|
|
2380
|
+
},
|
|
2381
|
+
{
|
|
2382
|
+
base: c(214),
|
|
2383
|
+
text: c(223),
|
|
2384
|
+
faint: c(130),
|
|
2385
|
+
glow: B + c(208),
|
|
2386
|
+
rain: c(172)
|
|
2387
|
+
}
|
|
2388
|
+
];
|
|
2389
|
+
const QUOTES = [
|
|
2390
|
+
{
|
|
2391
|
+
text: "The best code is no code at all.",
|
|
2392
|
+
author: "Jeff Atwood"
|
|
2393
|
+
},
|
|
2394
|
+
{
|
|
2395
|
+
text: "Programs must be written for people to read, and only incidentally for machines to execute.",
|
|
2396
|
+
author: "Abelson & Sussman"
|
|
2397
|
+
},
|
|
2398
|
+
{
|
|
2399
|
+
text: "Make it work, make it right, make it fast.",
|
|
2400
|
+
author: "Kent Beck"
|
|
2401
|
+
},
|
|
2402
|
+
{
|
|
2403
|
+
text: "Simplicity is the soul of efficiency.",
|
|
2404
|
+
author: "Austin Freeman"
|
|
2405
|
+
},
|
|
2406
|
+
{
|
|
2407
|
+
text: "Any fool can write code that a computer can understand. Good programmers write code that humans can understand.",
|
|
2408
|
+
author: "Martin Fowler"
|
|
2409
|
+
},
|
|
2410
|
+
{
|
|
2411
|
+
text: "First, solve the problem. Then, write the code.",
|
|
2412
|
+
author: "John Johnson"
|
|
2413
|
+
},
|
|
2414
|
+
{
|
|
2415
|
+
text: "Code is like humor. When you have to explain it, it's bad.",
|
|
2416
|
+
author: "Cory House"
|
|
2417
|
+
},
|
|
2418
|
+
{
|
|
2419
|
+
text: "Before software can be reusable it first has to be usable.",
|
|
2420
|
+
author: "Ralph Johnson"
|
|
2421
|
+
},
|
|
2422
|
+
{
|
|
2423
|
+
text: "Clean code always looks like it was written by someone who cares.",
|
|
2424
|
+
author: "Robert C. Martin"
|
|
2425
|
+
},
|
|
2426
|
+
{
|
|
2427
|
+
text: "The most dangerous phrase is \"we've always done it this way\".",
|
|
2428
|
+
author: "Grace Hopper"
|
|
2429
|
+
},
|
|
2430
|
+
{
|
|
2431
|
+
text: "Architecture is the decisions you wish you could get right early in a project.",
|
|
2432
|
+
author: "Martin Fowler"
|
|
2433
|
+
},
|
|
2434
|
+
{
|
|
2435
|
+
text: "The best error message is the one that never shows up.",
|
|
2436
|
+
author: "Thomas Fuchs"
|
|
2437
|
+
},
|
|
2438
|
+
{
|
|
2439
|
+
text: "It works on my machine.",
|
|
2440
|
+
author: "Every developer, always"
|
|
2441
|
+
},
|
|
2442
|
+
{
|
|
2443
|
+
text: "git blame yourself.",
|
|
2444
|
+
author: "The terminal"
|
|
2445
|
+
},
|
|
2446
|
+
{
|
|
2447
|
+
text: "There are only two hard things in CS: cache invalidation, naming things, and off-by-one errors.",
|
|
2448
|
+
author: "Unknown"
|
|
2449
|
+
},
|
|
2450
|
+
{
|
|
2451
|
+
text: "Debugging: being the detective in a crime movie where you're also the murderer.",
|
|
2452
|
+
author: "Unknown"
|
|
2453
|
+
},
|
|
2454
|
+
{
|
|
2455
|
+
text: "The code you wrote six months ago was written by an idiot.",
|
|
2456
|
+
author: "Unknown"
|
|
2457
|
+
},
|
|
2458
|
+
{
|
|
2459
|
+
text: "sudo make me a sandwich.",
|
|
2460
|
+
author: "xkcd"
|
|
2461
|
+
},
|
|
2462
|
+
{
|
|
2463
|
+
text: "I don't always test my code, but when I do, I do it in production.",
|
|
2464
|
+
author: "Unknown"
|
|
2465
|
+
},
|
|
2466
|
+
{
|
|
2467
|
+
text: "A QA engineer walks into a bar. Orders 0 beers. Orders 999999 beers. Orders NULL beers. Walks in through the window.",
|
|
2468
|
+
author: "Unknown"
|
|
2469
|
+
},
|
|
2470
|
+
{
|
|
2471
|
+
text: "To understand recursion, you must first understand recursion.",
|
|
2472
|
+
author: "Unknown"
|
|
2473
|
+
},
|
|
2474
|
+
{
|
|
2475
|
+
text: "The code review is the product.",
|
|
2476
|
+
author: "diffing"
|
|
2477
|
+
},
|
|
2478
|
+
{
|
|
2479
|
+
text: "Your abstractions are just someone else's bugs.",
|
|
2480
|
+
author: "Unknown"
|
|
2481
|
+
},
|
|
2482
|
+
{
|
|
2483
|
+
text: "Every rewrite is a confession that you didn't understand the problem the first time.",
|
|
2484
|
+
author: "Unknown"
|
|
2485
|
+
},
|
|
2486
|
+
{
|
|
2487
|
+
text: "The best PR is no PR — ship it in the design.",
|
|
2488
|
+
author: "Unknown"
|
|
2489
|
+
},
|
|
2490
|
+
{
|
|
2491
|
+
text: "You don't own your code. Your code owns you.",
|
|
2492
|
+
author: "Unknown"
|
|
2493
|
+
},
|
|
2494
|
+
{
|
|
2495
|
+
text: "A diff is a conversation. What story does yours tell?",
|
|
2496
|
+
author: "Unknown"
|
|
2497
|
+
},
|
|
2498
|
+
{
|
|
2499
|
+
text: "The senior engineer's superpower: knowing which shortcuts will haunt you.",
|
|
2500
|
+
author: "Unknown"
|
|
2501
|
+
},
|
|
2502
|
+
{
|
|
2503
|
+
text: "Production is the only real test environment.",
|
|
2504
|
+
author: "Site Reliability truth"
|
|
2505
|
+
},
|
|
2506
|
+
{
|
|
2507
|
+
text: "Tech debt is just deferred thinking.",
|
|
2508
|
+
author: "Unknown"
|
|
2509
|
+
},
|
|
2510
|
+
{
|
|
2511
|
+
text: "Comments lie. Code never does.",
|
|
2512
|
+
author: "Ron Jeffries"
|
|
2513
|
+
}
|
|
2514
|
+
];
|
|
2515
|
+
function wrapText(text, width) {
|
|
2516
|
+
const words = text.split(" ");
|
|
2517
|
+
const lines = [];
|
|
2518
|
+
let cur = "";
|
|
2519
|
+
for (const word of words) {
|
|
2520
|
+
if (!cur) {
|
|
2521
|
+
cur = word;
|
|
2522
|
+
continue;
|
|
2523
|
+
}
|
|
2524
|
+
if (cur.length + 1 + word.length <= width) cur += " " + word;
|
|
2525
|
+
else {
|
|
2526
|
+
lines.push(cur);
|
|
2527
|
+
cur = word;
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
if (cur) lines.push(cur);
|
|
2531
|
+
return lines;
|
|
2532
|
+
}
|
|
2533
|
+
function makeBox(quote, pal) {
|
|
2534
|
+
const termWidth = stdout.columns ?? 80;
|
|
2535
|
+
const maxContent = Math.min(64, termWidth - 8);
|
|
2536
|
+
const fullText = `"${quote.text}"`;
|
|
2537
|
+
const authorStr = `— ${quote.author}`;
|
|
2538
|
+
const textLines = wrapText(fullText, maxContent);
|
|
2539
|
+
return {
|
|
2540
|
+
textLines,
|
|
2541
|
+
authorStr,
|
|
2542
|
+
innerWidth: Math.max(...textLines.map((l) => l.length), authorStr.length, 28) + 4,
|
|
2543
|
+
pal
|
|
2544
|
+
};
|
|
2545
|
+
}
|
|
2546
|
+
function topBorder(b, borderCol = b.pal.base) {
|
|
2547
|
+
return `${borderCol}╭${"─".repeat(b.innerWidth)}╮${R}`;
|
|
2548
|
+
}
|
|
2549
|
+
function bottomBorder(b, borderCol = b.pal.base) {
|
|
2550
|
+
return `${borderCol}╰${"─".repeat(b.innerWidth)}╯${R}`;
|
|
2551
|
+
}
|
|
2552
|
+
function contentRow(b, text, borderCol = b.pal.base, textCol = b.pal.text) {
|
|
2553
|
+
return `${borderCol}│${R} ${textCol}${text}${R}${" ".repeat(b.innerWidth - 4 - text.length)} ${borderCol}│${R}`;
|
|
2554
|
+
}
|
|
2555
|
+
function authorRow(b, borderCol = b.pal.base) {
|
|
2556
|
+
const leading = " ".repeat(b.innerWidth - 4 - b.authorStr.length);
|
|
2557
|
+
return `${borderCol}│${R} ${b.pal.faint}${leading}${b.authorStr}${R} ${borderCol}│${R}`;
|
|
2558
|
+
}
|
|
2559
|
+
function allLines(b, borderCol = b.pal.base) {
|
|
2560
|
+
return [
|
|
2561
|
+
topBorder(b, borderCol),
|
|
2562
|
+
...b.textLines.map((l) => contentRow(b, l, borderCol)),
|
|
2563
|
+
authorRow(b, borderCol),
|
|
2564
|
+
bottomBorder(b, borderCol)
|
|
2565
|
+
];
|
|
2566
|
+
}
|
|
2567
|
+
function printBox(b) {
|
|
2568
|
+
for (const line of allLines(b)) stdout.write(line + "\n");
|
|
2569
|
+
}
|
|
2570
|
+
const animTypewriter = async (b) => {
|
|
2571
|
+
stdout.write(topBorder(b) + "\n");
|
|
2572
|
+
for (const line of b.textLines) {
|
|
2573
|
+
stdout.write(`${b.pal.base}│${R} `);
|
|
2574
|
+
for (let i = 0; i < line.length; i++) {
|
|
2575
|
+
const ch = line[i];
|
|
2576
|
+
stdout.write(`${b.pal.text}${ch}${b.pal.glow}█${R}`);
|
|
2577
|
+
await sleep(25);
|
|
2578
|
+
stdout.write("\b \b");
|
|
2579
|
+
}
|
|
2580
|
+
for (let blink = 0; blink < 2; blink++) {
|
|
2581
|
+
stdout.write(`${b.pal.glow}█${R}`);
|
|
2582
|
+
await sleep(100);
|
|
2583
|
+
stdout.write("\b \b");
|
|
2584
|
+
await sleep(100);
|
|
2585
|
+
}
|
|
2586
|
+
stdout.write(" ".repeat(b.innerWidth - 4 - line.length) + ` ${b.pal.base}│${R}\n`);
|
|
2587
|
+
}
|
|
2588
|
+
stdout.write(authorRow(b) + "\n");
|
|
2589
|
+
stdout.write(bottomBorder(b) + "\n");
|
|
2590
|
+
};
|
|
2591
|
+
const animWaveReveal = async (b) => {
|
|
2592
|
+
stdout.write(topBorder(b) + "\n");
|
|
2593
|
+
for (const line of b.textLines) {
|
|
2594
|
+
const half = Math.ceil(line.length / 2);
|
|
2595
|
+
stdout.write(contentRow(b, line.slice(0, half).padEnd(line.length), b.pal.base, b.pal.faint) + "\n");
|
|
2596
|
+
await sleep(130);
|
|
2597
|
+
stdout.write(up(1) + CLR + contentRow(b, line) + "\n");
|
|
2598
|
+
await sleep(60);
|
|
2599
|
+
}
|
|
2600
|
+
stdout.write(authorRow(b) + "\n");
|
|
2601
|
+
stdout.write(bottomBorder(b) + "\n");
|
|
2602
|
+
};
|
|
2603
|
+
const animSlide = async (b) => {
|
|
2604
|
+
const lines = allLines(b);
|
|
2605
|
+
for (let i = 0; i < lines.length; i++) stdout.write("\n");
|
|
2606
|
+
stdout.write(up(lines.length));
|
|
2607
|
+
for (let step = 0; step < 5; step++) {
|
|
2608
|
+
const pad = " ".repeat(Math.floor(12 * (1 - step / 4)));
|
|
2609
|
+
for (const line of lines) stdout.write(CLR + pad + line + "\n");
|
|
2610
|
+
if (step < 4) {
|
|
2611
|
+
stdout.write(up(lines.length));
|
|
2612
|
+
await sleep(80);
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
};
|
|
2616
|
+
const animPulseBorder = async (b) => {
|
|
2617
|
+
const stages = [
|
|
2618
|
+
allLines(b, b.pal.faint),
|
|
2619
|
+
allLines(b, b.pal.base),
|
|
2620
|
+
allLines(b, b.pal.glow),
|
|
2621
|
+
allLines(b, b.pal.base)
|
|
2622
|
+
];
|
|
2623
|
+
for (let s = 0; s < stages.length; s++) {
|
|
2624
|
+
if (s > 0) stdout.write(up(stages[0].length));
|
|
2625
|
+
for (const line of stages[s]) stdout.write((s > 0 ? CLR : "") + line + "\n");
|
|
2626
|
+
if (s < stages.length - 1) await sleep(180);
|
|
2627
|
+
}
|
|
2628
|
+
};
|
|
2629
|
+
const animGlitch = async (b) => {
|
|
2630
|
+
const noiseRates = [
|
|
2631
|
+
.25,
|
|
2632
|
+
.12,
|
|
2633
|
+
.04,
|
|
2634
|
+
0
|
|
2635
|
+
];
|
|
2636
|
+
const totalLines = b.textLines.length + 3;
|
|
2637
|
+
for (let f = 0; f < noiseRates.length; f++) {
|
|
2638
|
+
if (f > 0) stdout.write(up(totalLines));
|
|
2639
|
+
const rate = noiseRates[f];
|
|
2640
|
+
const clr = f > 0 ? CLR : "";
|
|
2641
|
+
const jitter = rate > 0 ? Math.floor(Math.random() * 3) : 0;
|
|
2642
|
+
const pad = " ".repeat(jitter);
|
|
2643
|
+
stdout.write(clr + pad + topBorder(b) + "\n");
|
|
2644
|
+
for (const line of b.textLines) if (rate === 0) stdout.write(clr + contentRow(b, line) + "\n");
|
|
2645
|
+
else {
|
|
2646
|
+
let inner = "";
|
|
2647
|
+
for (const ch of line) if (Math.random() < rate) {
|
|
2648
|
+
const charColor = Math.random() < .3 ? b.pal.glow : b.pal.base;
|
|
2649
|
+
inner += `${charColor}${GLITCH_CHARS[Math.floor(Math.random() * 12)]}${b.pal.text}`;
|
|
2650
|
+
} else inner += ch;
|
|
2651
|
+
const fill = " ".repeat(b.innerWidth - 4 - line.length);
|
|
2652
|
+
stdout.write(clr + pad + `${b.pal.base}│${R} ${b.pal.text}${inner}${R}${fill} ${b.pal.base}│${R}\n`);
|
|
2653
|
+
}
|
|
2654
|
+
stdout.write(clr + pad + authorRow(b) + "\n");
|
|
2655
|
+
stdout.write(clr + pad + bottomBorder(b) + "\n");
|
|
2656
|
+
if (f < noiseRates.length - 1) await sleep(120);
|
|
2657
|
+
}
|
|
2658
|
+
};
|
|
2659
|
+
const animMatrixRain = async (b) => {
|
|
2660
|
+
const width = b.innerWidth + 2;
|
|
2661
|
+
const RAIN_HEIGHT = 6;
|
|
2662
|
+
const streams = Array.from({ length: RAIN_HEIGHT }, () => Array(width).fill(" "));
|
|
2663
|
+
const chars = "日ハミヒーウシナモニサワツオリアホマケメエカキムユラセネスタヌヘ0123456789";
|
|
2664
|
+
for (let frame = 0; frame < 9; frame++) {
|
|
2665
|
+
for (let y = RAIN_HEIGHT - 1; y > 0; y--) streams[y] = [...streams[y - 1]];
|
|
2666
|
+
streams[0] = Array(width).fill(" ").map(() => {
|
|
2667
|
+
if (Math.random() < .25) return { char: chars[Math.floor(Math.random() * 42)] };
|
|
2668
|
+
return " ";
|
|
2669
|
+
});
|
|
2670
|
+
if (frame > 0) stdout.write(up(RAIN_HEIGHT));
|
|
2671
|
+
for (let y = 0; y < RAIN_HEIGHT; y++) {
|
|
2672
|
+
let line = "";
|
|
2673
|
+
for (let x = 0; x < width; x++) {
|
|
2674
|
+
const cell = streams[y][x];
|
|
2675
|
+
if (cell === " ") line += " ";
|
|
2676
|
+
else {
|
|
2677
|
+
const typedCell = cell;
|
|
2678
|
+
const color = y === 0 ? b.pal.glow : y <= 2 ? b.pal.text : y <= 4 ? b.pal.base : b.pal.faint;
|
|
2679
|
+
line += `${color}${typedCell.char}${R}`;
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
stdout.write((frame > 0 ? CLR : "") + line + "\n");
|
|
2683
|
+
}
|
|
2684
|
+
await sleep(90);
|
|
2685
|
+
}
|
|
2686
|
+
stdout.write(up(RAIN_HEIGHT));
|
|
2687
|
+
for (let y = 0; y < RAIN_HEIGHT; y++) stdout.write(CLR + "\n");
|
|
2688
|
+
stdout.write(up(RAIN_HEIGHT));
|
|
2689
|
+
printBox(b);
|
|
2690
|
+
};
|
|
2691
|
+
const ANIMS = [
|
|
2692
|
+
animTypewriter,
|
|
2693
|
+
animWaveReveal,
|
|
2694
|
+
animSlide,
|
|
2695
|
+
animPulseBorder,
|
|
2696
|
+
animGlitch,
|
|
2697
|
+
animMatrixRain
|
|
2698
|
+
];
|
|
2699
|
+
function playStartupDisplay() {
|
|
2700
|
+
if (!stdout.isTTY) return Promise.resolve();
|
|
2701
|
+
return (async () => {
|
|
2702
|
+
try {
|
|
2703
|
+
const quote = QUOTES[Math.floor(Math.random() * QUOTES.length)];
|
|
2704
|
+
const pal = PALETTES[Math.floor(Math.random() * PALETTES.length)];
|
|
2705
|
+
const b = makeBox(quote, pal);
|
|
2706
|
+
const anim = ANIMS[Math.floor(Math.random() * ANIMS.length)];
|
|
2707
|
+
stdout.write("\n" + HIDE);
|
|
2708
|
+
await anim(b);
|
|
2709
|
+
stdout.write(SHOW);
|
|
2710
|
+
} catch {
|
|
2711
|
+
try {
|
|
2712
|
+
stdout.write(SHOW);
|
|
2713
|
+
} catch {}
|
|
2714
|
+
}
|
|
2715
|
+
})();
|
|
2716
|
+
}
|
|
2717
|
+
//#endregion
|
|
2229
2718
|
//#region src/cli.ts
|
|
2230
2719
|
const args = process.argv.slice(2);
|
|
2231
2720
|
if (new Set([
|
|
@@ -2235,14 +2724,15 @@ if (new Set([
|
|
|
2235
2724
|
"comments",
|
|
2236
2725
|
"url",
|
|
2237
2726
|
"mcp",
|
|
2238
|
-
"plan"
|
|
2727
|
+
"plan",
|
|
2728
|
+
"update"
|
|
2239
2729
|
]).has(args[0])) {
|
|
2240
2730
|
if (args[0] === "mcp") {
|
|
2241
|
-
const { startMcpServer } = await import("./mcp-
|
|
2731
|
+
const { startMcpServer } = await import("./mcp-CFeE13n-.mjs");
|
|
2242
2732
|
await startMcpServer();
|
|
2243
2733
|
await new Promise(() => {});
|
|
2244
2734
|
}
|
|
2245
|
-
const { runSubcommand } = await import("./cli-agent-
|
|
2735
|
+
const { runSubcommand } = await import("./cli-agent-BNwY5lZE.mjs");
|
|
2246
2736
|
process.exit(await runSubcommand(args[0], args.slice(1)));
|
|
2247
2737
|
}
|
|
2248
2738
|
const opts = parseDiffOptions(args);
|
|
@@ -2265,9 +2755,19 @@ if (opts.outputMode === "terminal") {
|
|
|
2265
2755
|
const exitCode = runTerminalDiff(opts);
|
|
2266
2756
|
process.exit(exitCode);
|
|
2267
2757
|
}
|
|
2758
|
+
const __pkgDir = dirname(fileURLToPath(import.meta.url));
|
|
2759
|
+
const currentVersion = JSON.parse(readFileSync(resolve(__pkgDir, "..", "package.json"), "utf-8")).version;
|
|
2760
|
+
const updateCheckPromise = (async () => {
|
|
2761
|
+
try {
|
|
2762
|
+
const { checkForUpdates } = await import("./update-check-KL-2sGxE.mjs");
|
|
2763
|
+
return await checkForUpdates(currentVersion);
|
|
2764
|
+
} catch {
|
|
2765
|
+
return null;
|
|
2766
|
+
}
|
|
2767
|
+
})();
|
|
2268
2768
|
const port = await getPort(opts.port ? { port: opts.port } : void 0);
|
|
2269
2769
|
const host = opts.host;
|
|
2270
|
-
const clientDir = resolve(
|
|
2770
|
+
const clientDir = resolve(__pkgDir, "client");
|
|
2271
2771
|
const { existsSync: existsSync$1 } = await import("node:fs");
|
|
2272
2772
|
const { port: actualPort } = await startServer({
|
|
2273
2773
|
port,
|
|
@@ -2283,18 +2783,24 @@ try {
|
|
|
2283
2783
|
} catch {
|
|
2284
2784
|
repoRoot = process.cwd();
|
|
2285
2785
|
}
|
|
2286
|
-
const __pkgDir = dirname(fileURLToPath(import.meta.url));
|
|
2287
|
-
const version = JSON.parse(readFileSync(resolve(__pkgDir, "..", "package.json"), "utf-8")).version;
|
|
2288
2786
|
writeServerLock({
|
|
2289
2787
|
port: actualPort,
|
|
2290
2788
|
host,
|
|
2291
2789
|
pid: process.pid,
|
|
2292
2790
|
repoRoot,
|
|
2293
2791
|
startedAt: Date.now(),
|
|
2294
|
-
version
|
|
2792
|
+
version: currentVersion
|
|
2295
2793
|
});
|
|
2296
2794
|
} catch {}
|
|
2297
2795
|
console.log(`diffing server running at ${localUrl}`);
|
|
2796
|
+
await playStartupDisplay();
|
|
2797
|
+
try {
|
|
2798
|
+
const updateInfo = await updateCheckPromise;
|
|
2799
|
+
if (updateInfo?.hasUpdate) {
|
|
2800
|
+
const { printUpdateDisclaimer } = await import("./update-check-KL-2sGxE.mjs");
|
|
2801
|
+
printUpdateDisclaimer(currentVersion, updateInfo.latestVersion);
|
|
2802
|
+
}
|
|
2803
|
+
} catch {}
|
|
2298
2804
|
if (!opts.noOpen) {
|
|
2299
2805
|
const settings = loadSettings();
|
|
2300
2806
|
const openUrl = `http://${host === "0.0.0.0" ? "127.0.0.1" : host}:${actualPort}`;
|