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.
@@ -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.3",
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 { readFile } from "fs/promises";
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
- await ensureDir(globalStateDir());
374
- await writeJsonFile(globalServerFile(), info);
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 path5 from "path";
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 path3 from "path";
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 = path3.extname(filePath).slice(1).toLowerCase();
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 path4 from "path";
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 rm2 } from "fs/promises";
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 rm2(artifactDir, { recursive: true, force: true });
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 ?? path4.join(meta.artifactDir, "feedback.json") : void 0,
1570
- markdownPath: feedback ? meta.markdownPath ?? path4.join(meta.artifactDir, "feedback.md") : void 0,
1571
- resolvedPath: resolution ? meta.resolvedPath ?? path4.join(meta.artifactDir, "resolved.json") : void 0,
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") || path5.isAbsolute(filePath)) {
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 = path5.resolve(existing.diff.cwd);
1870
- const requestedAbsolutePath = path5.resolve(repoRoot, filePath);
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 = path5.normalize(requestPath).replace(/^(\.\.(\/|\\|$))+/, "");
1987
- const assetPath = path5.join(webRoot, "assets", normalized);
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[path5.extname(assetPath)] ?? "application/octet-stream"
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(path5.join(webRoot, "index.html"));
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(path5.join(webRoot, fileName));
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 = path5.relative(parentPath, childPath);
2051
- return relative === "" || !relative.startsWith("..") && !path5.isAbsolute(relative);
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
- await rm3(globalServerFile(), { force: true });
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