getgloss 0.8.3 → 0.8.4
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 +8 -0
- package/dist/cli/index.js +221 -86
- package/dist/cli/index.js.map +1 -1
- package/dist/server/daemon.js +93 -25
- package/dist/server/daemon.js.map +1 -1
- package/dist/web/setup.md +7 -2
- package/package.json +1 -1
- package/skill/SKILL.md +6 -0
package/dist/server/daemon.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// src/server/daemon.ts
|
|
2
|
-
import { rm as rm3 } from "fs/promises";
|
|
3
2
|
import { serve } from "@hono/node-server";
|
|
4
3
|
|
|
5
4
|
// src/shared/paths.ts
|
|
@@ -10,7 +9,7 @@ import path from "path";
|
|
|
10
9
|
// package.json
|
|
11
10
|
var package_default = {
|
|
12
11
|
name: "getgloss",
|
|
13
|
-
version: "0.8.
|
|
12
|
+
version: "0.8.4",
|
|
14
13
|
description: "Local browser-based diff review for coding-agent loops.",
|
|
15
14
|
type: "module",
|
|
16
15
|
packageManager: "pnpm@10.33.2",
|
|
@@ -153,7 +152,9 @@ async function ensureDir(dir) {
|
|
|
153
152
|
}
|
|
154
153
|
|
|
155
154
|
// src/shared/server-info.ts
|
|
156
|
-
import {
|
|
155
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
156
|
+
import { readFile, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
157
|
+
import path3 from "path";
|
|
157
158
|
|
|
158
159
|
// src/shared/errors.ts
|
|
159
160
|
function formatError(error) {
|
|
@@ -162,6 +163,9 @@ function formatError(error) {
|
|
|
162
163
|
function isFileNotFound(error) {
|
|
163
164
|
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
164
165
|
}
|
|
166
|
+
function isPermissionError(error) {
|
|
167
|
+
return error instanceof Error && "code" in error && (error.code === "EACCES" || error.code === "EPERM");
|
|
168
|
+
}
|
|
165
169
|
|
|
166
170
|
// src/shared/json.ts
|
|
167
171
|
import { randomUUID } from "crypto";
|
|
@@ -357,6 +361,9 @@ async function readServerInfo() {
|
|
|
357
361
|
if (isFileNotFound(error)) {
|
|
358
362
|
return null;
|
|
359
363
|
}
|
|
364
|
+
if (isPermissionError(error)) {
|
|
365
|
+
throw new Error(serverInfoPermissionMessage("read", error), { cause: error });
|
|
366
|
+
}
|
|
360
367
|
throw new Error(`Could not read server info at ${globalServerFile()}: ${formatError(error)}`, {
|
|
361
368
|
cause: error
|
|
362
369
|
});
|
|
@@ -370,13 +377,70 @@ async function readServerInfo() {
|
|
|
370
377
|
}
|
|
371
378
|
}
|
|
372
379
|
async function writeServerInfo(info) {
|
|
373
|
-
|
|
374
|
-
|
|
380
|
+
try {
|
|
381
|
+
await ensureDir(globalStateDir());
|
|
382
|
+
} catch (error) {
|
|
383
|
+
if (isPermissionError(error)) {
|
|
384
|
+
throw new Error(serverInfoPermissionMessage("create", error), { cause: error });
|
|
385
|
+
}
|
|
386
|
+
throw error;
|
|
387
|
+
}
|
|
388
|
+
try {
|
|
389
|
+
await writeJsonFile(globalServerFile(), info);
|
|
390
|
+
} catch (error) {
|
|
391
|
+
if (!isPermissionError(error)) {
|
|
392
|
+
throw error;
|
|
393
|
+
}
|
|
394
|
+
await assertStateDirWritable();
|
|
395
|
+
try {
|
|
396
|
+
await writeFile2(globalServerFile(), serializeServerInfo(info));
|
|
397
|
+
} catch (directWriteError) {
|
|
398
|
+
throw new Error(serverInfoPermissionMessage("write", directWriteError), {
|
|
399
|
+
cause: directWriteError
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
async function removeServerInfoFile() {
|
|
405
|
+
try {
|
|
406
|
+
await rm2(globalServerFile(), { force: true });
|
|
407
|
+
return null;
|
|
408
|
+
} catch (error) {
|
|
409
|
+
return serverInfoPermissionMessage("remove", error);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
function serverInfoPermissionMessage(action, error) {
|
|
413
|
+
const stateDir = globalStateDir();
|
|
414
|
+
const source = process.env.GLOSS_STATE_DIR ? `GLOSS_STATE_DIR=${stateDir}` : "GLOSS_STATE_DIR is not set; defaulting to ~/.gloss";
|
|
415
|
+
return [
|
|
416
|
+
`Could not ${action} Gloss server state at ${globalServerFile()}: ${formatError(error)}.`,
|
|
417
|
+
"`server.json` is not a review lock, so there is nothing to unlock after a review.",
|
|
418
|
+
`Check that ${stateDir} and ${globalServerFile()} are owned and writable by your user.`,
|
|
419
|
+
`On macOS, if the file is immutable, run \`chflags nouchg "${globalServerFile()}"\`.`,
|
|
420
|
+
`For sandboxed agents, set GLOSS_STATE_DIR to a writable directory. ${source}.`
|
|
421
|
+
].join(" ");
|
|
422
|
+
}
|
|
423
|
+
function serializeServerInfo(info) {
|
|
424
|
+
return `${JSON.stringify(info, null, 2)}
|
|
425
|
+
`;
|
|
426
|
+
}
|
|
427
|
+
async function assertStateDirWritable() {
|
|
428
|
+
const probePath = path3.join(
|
|
429
|
+
globalStateDir(),
|
|
430
|
+
`.server.json.${process.pid}.${randomUUID2()}.probe`
|
|
431
|
+
);
|
|
432
|
+
try {
|
|
433
|
+
await writeFile2(probePath, "");
|
|
434
|
+
await rm2(probePath, { force: true });
|
|
435
|
+
} catch (error) {
|
|
436
|
+
await rm2(probePath, { force: true }).catch(() => void 0);
|
|
437
|
+
throw new Error(serverInfoPermissionMessage("write", error), { cause: error });
|
|
438
|
+
}
|
|
375
439
|
}
|
|
376
440
|
|
|
377
441
|
// src/server/index.ts
|
|
378
442
|
import { readFile as readFile4, realpath, stat } from "fs/promises";
|
|
379
|
-
import
|
|
443
|
+
import path6 from "path";
|
|
380
444
|
import { fileURLToPath } from "url";
|
|
381
445
|
import { Hono } from "hono";
|
|
382
446
|
import { streamSSE } from "hono/streaming";
|
|
@@ -412,7 +476,7 @@ function resolutionCounts(feedback, resolvedComments = []) {
|
|
|
412
476
|
import { execa } from "execa";
|
|
413
477
|
|
|
414
478
|
// src/shared/language.ts
|
|
415
|
-
import
|
|
479
|
+
import path4 from "path";
|
|
416
480
|
var languageByExtension = {
|
|
417
481
|
cjs: "js",
|
|
418
482
|
css: "css",
|
|
@@ -434,7 +498,7 @@ var languageByExtension = {
|
|
|
434
498
|
yml: "yaml"
|
|
435
499
|
};
|
|
436
500
|
function languageForPath(filePath) {
|
|
437
|
-
const ext =
|
|
501
|
+
const ext = path4.extname(filePath).slice(1).toLowerCase();
|
|
438
502
|
if (!ext) {
|
|
439
503
|
return null;
|
|
440
504
|
}
|
|
@@ -611,11 +675,11 @@ async function openLocalPath(filePath) {
|
|
|
611
675
|
// src/server/store.ts
|
|
612
676
|
import { createHash } from "crypto";
|
|
613
677
|
import { readdir as readdir2, readFile as readFile3 } from "fs/promises";
|
|
614
|
-
import
|
|
678
|
+
import path5 from "path";
|
|
615
679
|
import { ulid } from "ulid";
|
|
616
680
|
|
|
617
681
|
// src/shared/cleanup.ts
|
|
618
|
-
import { readdir, readFile as readFile2, rm as
|
|
682
|
+
import { readdir, readFile as readFile2, rm as rm3 } from "fs/promises";
|
|
619
683
|
var DEFAULT_REVIEW_RETENTION_DAYS = 30;
|
|
620
684
|
var clearableStatuses = /* @__PURE__ */ new Set(["submitted", "resolved", "cancelled"]);
|
|
621
685
|
var millisecondsPerDay = 24 * 60 * 60 * 1e3;
|
|
@@ -659,7 +723,7 @@ async function clearReviewArtifacts(options = {}) {
|
|
|
659
723
|
}
|
|
660
724
|
candidates.push(candidate);
|
|
661
725
|
if (!dryRun) {
|
|
662
|
-
await
|
|
726
|
+
await rm3(artifactDir, { recursive: true, force: true });
|
|
663
727
|
deleted.push(candidate);
|
|
664
728
|
}
|
|
665
729
|
}
|
|
@@ -1566,9 +1630,9 @@ function reconcileTurn(meta, diff, feedback, resolution) {
|
|
|
1566
1630
|
status,
|
|
1567
1631
|
submittedAt: feedback?.timestamp ?? meta.submittedAt,
|
|
1568
1632
|
resolvedAt: status === "resolved" ? resolution?.resolvedAt ?? meta.resolvedAt : void 0,
|
|
1569
|
-
feedbackPath: feedback ? meta.feedbackPath ??
|
|
1570
|
-
markdownPath: feedback ? meta.markdownPath ??
|
|
1571
|
-
resolvedPath: resolution ? meta.resolvedPath ??
|
|
1633
|
+
feedbackPath: feedback ? meta.feedbackPath ?? path5.join(meta.artifactDir, "feedback.json") : void 0,
|
|
1634
|
+
markdownPath: feedback ? meta.markdownPath ?? path5.join(meta.artifactDir, "feedback.md") : void 0,
|
|
1635
|
+
resolvedPath: resolution ? meta.resolvedPath ?? path5.join(meta.artifactDir, "resolved.json") : void 0,
|
|
1572
1636
|
diff,
|
|
1573
1637
|
...feedback ? { feedback } : {},
|
|
1574
1638
|
...resolution ? { resolution } : {}
|
|
@@ -1863,11 +1927,11 @@ function createApp(origin2, options = {}) {
|
|
|
1863
1927
|
return parsed.response;
|
|
1864
1928
|
}
|
|
1865
1929
|
const { filePath, turnId } = parsed.body;
|
|
1866
|
-
if (!filePath || filePath.includes("\0") ||
|
|
1930
|
+
if (!filePath || filePath.includes("\0") || path6.isAbsolute(filePath)) {
|
|
1867
1931
|
return c.json({ error: "filePath must be a repo-relative path" }, 400);
|
|
1868
1932
|
}
|
|
1869
|
-
const repoRoot =
|
|
1870
|
-
const requestedAbsolutePath =
|
|
1933
|
+
const repoRoot = path6.resolve(existing.diff.cwd);
|
|
1934
|
+
const requestedAbsolutePath = path6.resolve(repoRoot, filePath);
|
|
1871
1935
|
if (!isPathWithin(repoRoot, requestedAbsolutePath)) {
|
|
1872
1936
|
return c.json({ error: "filePath must stay within the review cwd" }, 400);
|
|
1873
1937
|
}
|
|
@@ -1983,13 +2047,13 @@ function createApp(origin2, options = {}) {
|
|
|
1983
2047
|
}
|
|
1984
2048
|
async function serveAsset(c) {
|
|
1985
2049
|
const requestPath = new URL(c.req.url).pathname.replace(/^\/assets\//, "");
|
|
1986
|
-
const normalized =
|
|
1987
|
-
const assetPath =
|
|
2050
|
+
const normalized = path6.normalize(requestPath).replace(/^(\.\.(\/|\\|$))+/, "");
|
|
2051
|
+
const assetPath = path6.join(webRoot, "assets", normalized);
|
|
1988
2052
|
try {
|
|
1989
2053
|
const body = await readFile4(assetPath);
|
|
1990
2054
|
return new Response(body, {
|
|
1991
2055
|
headers: {
|
|
1992
|
-
"content-type": mimeTypes[
|
|
2056
|
+
"content-type": mimeTypes[path6.extname(assetPath)] ?? "application/octet-stream"
|
|
1993
2057
|
}
|
|
1994
2058
|
});
|
|
1995
2059
|
} catch (error) {
|
|
@@ -2001,7 +2065,7 @@ async function serveAsset(c) {
|
|
|
2001
2065
|
}
|
|
2002
2066
|
async function serveIndex() {
|
|
2003
2067
|
try {
|
|
2004
|
-
const body = await readFile4(
|
|
2068
|
+
const body = await readFile4(path6.join(webRoot, "index.html"));
|
|
2005
2069
|
return new Response(body, {
|
|
2006
2070
|
headers: { "content-type": "text/html; charset=utf-8" }
|
|
2007
2071
|
});
|
|
@@ -2015,7 +2079,7 @@ async function serveIndex() {
|
|
|
2015
2079
|
function serveRootFile(fileName, contentType) {
|
|
2016
2080
|
return async () => {
|
|
2017
2081
|
try {
|
|
2018
|
-
const body = await readFile4(
|
|
2082
|
+
const body = await readFile4(path6.join(webRoot, fileName));
|
|
2019
2083
|
return new Response(body, {
|
|
2020
2084
|
headers: { "content-type": contentType }
|
|
2021
2085
|
});
|
|
@@ -2047,8 +2111,8 @@ async function readJsonBody(c, guard, label) {
|
|
|
2047
2111
|
}
|
|
2048
2112
|
}
|
|
2049
2113
|
function isPathWithin(parentPath, childPath) {
|
|
2050
|
-
const relative =
|
|
2051
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
2114
|
+
const relative = path6.relative(parentPath, childPath);
|
|
2115
|
+
return relative === "" || !relative.startsWith("..") && !path6.isAbsolute(relative);
|
|
2052
2116
|
}
|
|
2053
2117
|
function activeTurnSummary(meta) {
|
|
2054
2118
|
if (!meta.activeTurnId) {
|
|
@@ -2180,7 +2244,11 @@ async function shutdown(exitCode) {
|
|
|
2180
2244
|
async function removeCurrentServerInfo() {
|
|
2181
2245
|
const info = await readServerInfo().catch(() => null);
|
|
2182
2246
|
if (!info || info.pid === process.pid) {
|
|
2183
|
-
|
|
2247
|
+
const warning = await removeServerInfoFile();
|
|
2248
|
+
if (warning) {
|
|
2249
|
+
process.stderr.write(`Warning: ${warning}
|
|
2250
|
+
`);
|
|
2251
|
+
}
|
|
2184
2252
|
}
|
|
2185
2253
|
}
|
|
2186
2254
|
//# sourceMappingURL=daemon.js.map
|