@peaske7/readit 0.3.3-rc.0 → 0.3.3-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +35 -0
  2. package/dist/.vite/manifest.json +299 -299
  3. package/dist/assets/architecture-7EHR7CIX-C6aVFpcC.js +1 -0
  4. package/dist/assets/{architectureDiagram-3BPJPVTR-Ddrreb7r.js → architectureDiagram-3BPJPVTR-BnFwHRzX.js} +1 -1
  5. package/dist/assets/{blockDiagram-GPEHLZMM-BFFGlMYO.js → blockDiagram-GPEHLZMM-BzltrQkw.js} +1 -1
  6. package/dist/assets/{c4Diagram-AAUBKEIU-CWogf-8r.js → c4Diagram-AAUBKEIU-DsKlwzbq.js} +1 -1
  7. package/dist/assets/channel-DwF9gOa0.js +1 -0
  8. package/dist/assets/{chunk-2J33WTMH-GPyqmEV5.js → chunk-2J33WTMH-DlR0lkg7.js} +1 -1
  9. package/dist/assets/{chunk-3OPIFGDE-r2W8W5PA.js → chunk-3OPIFGDE-B4i_9YMq.js} +1 -1
  10. package/dist/assets/{chunk-5ZQYHXKU-UoHcLARP.js → chunk-5ZQYHXKU-DiFHCDuS.js} +1 -1
  11. package/dist/assets/{chunk-727SXJPM-Bv1oT9p1.js → chunk-727SXJPM-Ccgt5DVc.js} +1 -1
  12. package/dist/assets/{chunk-AQP2D5EJ-DnAslEq1.js → chunk-AQP2D5EJ-DUVRq64G.js} +1 -1
  13. package/dist/assets/{chunk-CSCIHK7Q-ffSW1NCL.js → chunk-CSCIHK7Q-Bols53SR.js} +1 -1
  14. package/dist/assets/{chunk-KSCS5N6A-CE53o1tB.js → chunk-KSCS5N6A-DEquGVPq.js} +1 -1
  15. package/dist/assets/{chunk-L5ZTLDWV-mRjE7igo.js → chunk-L5ZTLDWV-D0w1hqa9.js} +1 -1
  16. package/dist/assets/{chunk-LZXEDZCA-QExQhMNO.js → chunk-LZXEDZCA-DGjw50MI.js} +2 -2
  17. package/dist/assets/{chunk-ND2GUHAM-BLy5GjxM.js → chunk-ND2GUHAM-BnlX4qwJ.js} +1 -1
  18. package/dist/assets/{chunk-NZK2D7GU-BnFrdGvb.js → chunk-NZK2D7GU-D7zfqAxt.js} +1 -1
  19. package/dist/assets/{chunk-O5CBEL6O-DyVzTnqZ.js → chunk-O5CBEL6O-CBsS0HhM.js} +1 -1
  20. package/dist/assets/{chunk-WU5MYG2G-BbV4WCtd.js → chunk-WU5MYG2G-DklWbZ0S.js} +1 -1
  21. package/dist/assets/classDiagram-4FO5ZUOK-ddqYo6tm.js +1 -0
  22. package/dist/assets/classDiagram-v2-Q7XG4LA2-BnSzO7kG.js +1 -0
  23. package/dist/assets/{dagre-BM42HDAG-BtTbiJso.js → dagre-BM42HDAG-DWKOo9Dm.js} +1 -1
  24. package/dist/assets/{diagram-2AECGRRQ-49ek8zwY.js → diagram-2AECGRRQ-C3ULiKlm.js} +1 -1
  25. package/dist/assets/{diagram-5GNKFQAL-Nm6OZeKK.js → diagram-5GNKFQAL-BNVJh51_.js} +1 -1
  26. package/dist/assets/{diagram-KO2AKTUF-DOovW7jv.js → diagram-KO2AKTUF-CJ6E-xGX.js} +1 -1
  27. package/dist/assets/{diagram-LMA3HP47-D0cmK1UR.js → diagram-LMA3HP47-TREhl0_2.js} +1 -1
  28. package/dist/assets/{diagram-OG6HWLK6-BGh2LD9b.js → diagram-OG6HWLK6-_e2zFr4W.js} +1 -1
  29. package/dist/assets/{erDiagram-TEJ5UH35-5o2G4LLm.js → erDiagram-TEJ5UH35-Acelnzcb.js} +1 -1
  30. package/dist/assets/eventmodeling-FCH6USID-CQ7pK5Lp.js +1 -0
  31. package/dist/assets/{flowDiagram-I6XJVG4X-DhNppdRP.js → flowDiagram-I6XJVG4X-CT3PIL07.js} +1 -1
  32. package/dist/assets/{ganttDiagram-6RSMTGT7-DoKRBamY.js → ganttDiagram-6RSMTGT7-7h2KrQV6.js} +1 -1
  33. package/dist/assets/gitGraph-WXDBUCRP-CZ5QxFLq.js +1 -0
  34. package/dist/assets/{gitGraphDiagram-PVQCEYII-CBXOzCnM.js → gitGraphDiagram-PVQCEYII-nOuCMsdP.js} +1 -1
  35. package/dist/assets/index-BIXi49eg.css +2 -0
  36. package/dist/assets/index-Cxt6stf4.js +19 -0
  37. package/dist/assets/info-J43DQDTF-BSDVbABg.js +1 -0
  38. package/dist/assets/{infoDiagram-5YYISTIA-hBmoMgcp.js → infoDiagram-5YYISTIA-B9Hpo9ZB.js} +1 -1
  39. package/dist/assets/{ishikawaDiagram-YF4QCWOH-BXOUNChz.js → ishikawaDiagram-YF4QCWOH-BIw3VR84.js} +1 -1
  40. package/dist/assets/{journeyDiagram-JHISSGLW-CNB1a7Sl.js → journeyDiagram-JHISSGLW-D08N4vDz.js} +1 -1
  41. package/dist/assets/{kanban-definition-UN3LZRKU-CCNy8rLY.js → kanban-definition-UN3LZRKU-ZTonMe4i.js} +1 -1
  42. package/dist/assets/{line-CLKizxwC.js → line-CIBehtvT.js} +1 -1
  43. package/dist/assets/{mermaid-parser.core-C3XUs-_r.js → mermaid-parser.core-CtLS-ArF.js} +2 -2
  44. package/dist/assets/{mermaid.core-Bo2kUpZ-.js → mermaid.core-DYEq-RpL.js} +3 -3
  45. package/dist/assets/{mindmap-definition-RKZ34NQL-DHiw23VC.js → mindmap-definition-RKZ34NQL-DfK-b2Bm.js} +1 -1
  46. package/dist/assets/packet-YPE3B663-CXr_Zgl2.js +1 -0
  47. package/dist/assets/pie-LRSECV5Y-DF7fE8SS.js +1 -0
  48. package/dist/assets/{pieDiagram-4H26LBE5-CE8yBQXF.js → pieDiagram-4H26LBE5-YUDjA-Gg.js} +1 -1
  49. package/dist/assets/{quadrantDiagram-W4KKPZXB-C0UKCNZE.js → quadrantDiagram-W4KKPZXB-CuU8wWOu.js} +1 -1
  50. package/dist/assets/radar-GUYGQ44K-Dtz8_iCC.js +1 -0
  51. package/dist/assets/{requirementDiagram-4Y6WPE33-uTe99qJP.js → requirementDiagram-4Y6WPE33-De7k0Vra.js} +1 -1
  52. package/dist/assets/{sankeyDiagram-5OEKKPKP-Dd0SQY-f.js → sankeyDiagram-5OEKKPKP-DS-aJKEo.js} +1 -1
  53. package/dist/assets/{sequenceDiagram-3UESZ5HK-B4rU9H--.js → sequenceDiagram-3UESZ5HK-Dmc_jDz2.js} +1 -1
  54. package/dist/assets/{stateDiagram-AJRCARHV-BcFS7x4m.js → stateDiagram-AJRCARHV-iOPTbvVX.js} +1 -1
  55. package/dist/assets/stateDiagram-v2-BHNVJYJU-DP0E8avC.js +1 -0
  56. package/dist/assets/{timeline-definition-PNZ67QCA-08jUgNTq.js → timeline-definition-PNZ67QCA-CyWfqXuh.js} +1 -1
  57. package/dist/assets/treeView-BLDUP644-CBL7tFCk.js +1 -0
  58. package/dist/assets/treemap-LRROVOQU-BDyAuqPY.js +1 -0
  59. package/dist/assets/{vennDiagram-CIIHVFJN-DHxgFjs1.js → vennDiagram-CIIHVFJN-BIIy5gX3.js} +1 -1
  60. package/dist/assets/wardley-L42UT6IY-DF5LQREn.js +1 -0
  61. package/dist/assets/{wardleyDiagram-YWT4CUSO-BG4FGTur.js → wardleyDiagram-YWT4CUSO-CeWvU48V.js} +1 -1
  62. package/dist/assets/{xychartDiagram-2RQKCTM6-lsa4wca0.js → xychartDiagram-2RQKCTM6-B2xj7O2L.js} +1 -1
  63. package/dist/index.html +2 -2
  64. package/dist/index.js +425 -62
  65. package/package.json +5 -5
  66. package/shell/_readit +13 -0
  67. package/dist/assets/architecture-7EHR7CIX-D7ZF1SFX.js +0 -1
  68. package/dist/assets/channel-BvST7Rz7.js +0 -1
  69. package/dist/assets/classDiagram-4FO5ZUOK-BnzSVgs3.js +0 -1
  70. package/dist/assets/classDiagram-v2-Q7XG4LA2-Bexsxn93.js +0 -1
  71. package/dist/assets/eventmodeling-FCH6USID-FeSGs1wm.js +0 -1
  72. package/dist/assets/gitGraph-WXDBUCRP-BJMkVS25.js +0 -1
  73. package/dist/assets/index-B5Jrpm7U.js +0 -19
  74. package/dist/assets/index-BWaCUzw6.css +0 -2
  75. package/dist/assets/info-J43DQDTF-v5CyB2Da.js +0 -1
  76. package/dist/assets/packet-YPE3B663-CiCOsVYy.js +0 -1
  77. package/dist/assets/pie-LRSECV5Y-jGfdEc2X.js +0 -1
  78. package/dist/assets/radar-GUYGQ44K-Z2xvXYoU.js +0 -1
  79. package/dist/assets/stateDiagram-v2-BHNVJYJU-DO5JDGBs.js +0 -1
  80. package/dist/assets/treeView-BLDUP644-DNKwycJU.js +0 -1
  81. package/dist/assets/treemap-LRROVOQU-D4CqU8rg.js +0 -1
  82. package/dist/assets/wardley-L42UT6IY-Dxj4bx5U.js +0 -1
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@
3
3
  var __require = import.meta.require;
4
4
 
5
5
  // src/cli.ts
6
+ import { spawn as spawn2 } from "child_process";
6
7
  import {
7
8
  existsSync,
8
9
  lstatSync,
@@ -19,7 +20,7 @@ import open2 from "open";
19
20
  // package.json
20
21
  var package_default = {
21
22
  name: "@peaske7/readit",
22
- version: "0.3.3-rc.0",
23
+ version: "0.3.3-rc.2",
23
24
  description: "A CLI tool to review Markdown documents with inline comments",
24
25
  author: "Jay Shimada <peaske@pm.me>",
25
26
  license: "MIT",
@@ -71,7 +72,7 @@ var package_default = {
71
72
  mermaid: "^11.15.0",
72
73
  open: "^11.0.0",
73
74
  jsdom: "^28.1.0",
74
- shiki: "^4.0.2",
75
+ shiki: "^4.1.0",
75
76
  "tailwind-merge": "^3.6.0"
76
77
  },
77
78
  devDependencies: {
@@ -83,9 +84,9 @@ var package_default = {
83
84
  "@types/bun": "^1.3.14",
84
85
  "@types/jsdom": "^28.0.3",
85
86
  "@types/markdown-it": "^14.1.2",
86
- "@types/node": "^25.8.0",
87
- lefthook: "^2.1.6",
88
- svelte: "^5.55.7",
87
+ "@types/node": "^25.9.0",
88
+ lefthook: "^2.1.7",
89
+ svelte: "^5.55.8",
89
90
  tailwindcss: "^4.3.0",
90
91
  typescript: "^5.9.3",
91
92
  vite: "^8.0.13",
@@ -1764,31 +1765,36 @@ Browser disconnected, shutting down...`);
1764
1765
  return null;
1765
1766
  }
1766
1767
  }
1767
- let pageCache = null;
1768
- let pageCacheGz = null;
1768
+ const pageCache = new Map;
1769
+ const pageCacheGz = new Map;
1769
1770
  function invalidatePageCache() {
1770
- pageCache = null;
1771
- pageCacheGz = null;
1771
+ pageCache.clear();
1772
+ pageCacheGz.clear();
1772
1773
  }
1773
1774
  async function serveAppPage(req) {
1774
1775
  const acceptGzip = req.headers.get("accept-encoding")?.includes("gzip") ?? false;
1776
+ const url = new URL(req.url);
1777
+ const requestedPath = url.searchParams.get("path");
1778
+ const activePath = requestedPath && fileMap.has(requestedPath) ? requestedPath : defaultPath;
1775
1779
  try {
1776
- if (pageCache) {
1777
- if (acceptGzip && pageCacheGz) {
1778
- return new Response(pageCacheGz, {
1780
+ const cachedPage = pageCache.get(activePath);
1781
+ if (cachedPage) {
1782
+ const cachedPageGz = pageCacheGz.get(activePath);
1783
+ if (acceptGzip && cachedPageGz) {
1784
+ return new Response(cachedPageGz, {
1779
1785
  headers: {
1780
1786
  "Content-Type": "text/html; charset=utf-8",
1781
1787
  "Content-Encoding": "gzip"
1782
1788
  }
1783
1789
  });
1784
1790
  }
1785
- return new Response(pageCache, {
1791
+ return new Response(cachedPage, {
1786
1792
  headers: { "Content-Type": "text/html; charset=utf-8" }
1787
1793
  });
1788
1794
  }
1789
- const { html, headings } = await ensureRenderedHtml(defaultPath);
1790
- const content = await ensureFileContent(defaultPath);
1791
- const comments = await readCommentsFromFile(defaultPath, content, html);
1795
+ const { html, headings } = await ensureRenderedHtml(activePath);
1796
+ const content = await ensureFileContent(activePath);
1797
+ const comments = await readCommentsFromFile(activePath, content, html);
1792
1798
  const settings = await readSettings();
1793
1799
  const files = fileOrder.map((fp) => ({
1794
1800
  path: fp,
@@ -1796,10 +1802,10 @@ Browser disconnected, shutting down...`);
1796
1802
  }));
1797
1803
  const inlineData = {
1798
1804
  files,
1799
- activeFile: defaultPath,
1805
+ activeFile: activePath,
1800
1806
  settings,
1801
1807
  documents: {
1802
- [defaultPath]: {
1808
+ [activePath]: {
1803
1809
  headings,
1804
1810
  comments
1805
1811
  }
@@ -1820,7 +1826,7 @@ Browser disconnected, shutting down...`);
1820
1826
  }
1821
1827
  }
1822
1828
  const body = renderTemplate({
1823
- title: basename(defaultPath),
1829
+ title: basename(activePath),
1824
1830
  cssPath,
1825
1831
  jsPath,
1826
1832
  documentHtml: html,
@@ -1829,11 +1835,12 @@ Browser disconnected, shutting down...`);
1829
1835
  fontFamily: settings.fontFamily
1830
1836
  });
1831
1837
  if (!isDev) {
1832
- pageCache = body;
1833
- pageCacheGz = Bun.gzipSync(new TextEncoder().encode(body));
1838
+ const gz = Bun.gzipSync(new TextEncoder().encode(body));
1839
+ pageCache.set(activePath, body);
1840
+ pageCacheGz.set(activePath, gz);
1834
1841
  }
1835
1842
  if (acceptGzip) {
1836
- const gz = pageCacheGz ?? Bun.gzipSync(new TextEncoder().encode(body));
1843
+ const gz = pageCacheGz.get(activePath) ?? Bun.gzipSync(new TextEncoder().encode(body));
1837
1844
  return new Response(gz, {
1838
1845
  headers: {
1839
1846
  "Content-Type": "text/html; charset=utf-8",
@@ -2164,6 +2171,231 @@ async function startServer(options) {
2164
2171
  throw new Error(`No available port found starting from ${options.port}`);
2165
2172
  }
2166
2173
 
2174
+ // src/zed-lsp.ts
2175
+ import { spawn } from "child_process";
2176
+ import { fileURLToPath } from "url";
2177
+ var OPEN_PREVIEW_COMMAND = "readit.openPreview";
2178
+ var OPEN_PREVIEW_IN_BROWSER_COMMAND = "readit.openPreviewInBrowser";
2179
+ var METHOD_NOT_FOUND = -32601;
2180
+ var INVALID_PARAMS = -32602;
2181
+ var INTERNAL_ERROR = -32603;
2182
+ function filePathFromUri(uri) {
2183
+ if (!uri.startsWith("file://"))
2184
+ return null;
2185
+ try {
2186
+ return fileURLToPath(uri);
2187
+ } catch {
2188
+ return null;
2189
+ }
2190
+ }
2191
+ function isMarkdownUri(uri) {
2192
+ const filePath = filePathFromUri(uri);
2193
+ return filePath !== null && isMarkdownFile(filePath);
2194
+ }
2195
+ function hasId(message) {
2196
+ return Object.hasOwn(message, "id");
2197
+ }
2198
+ function send(message) {
2199
+ const body = JSON.stringify(message);
2200
+ process.stdout.write(`Content-Length: ${Buffer.byteLength(body, "utf8")}\r
2201
+ \r
2202
+ ${body}`);
2203
+ }
2204
+ function sendResponse(id, result) {
2205
+ send({ jsonrpc: "2.0", id, result });
2206
+ }
2207
+ function sendError(id, code, message) {
2208
+ send({
2209
+ jsonrpc: "2.0",
2210
+ id,
2211
+ error: { code, message }
2212
+ });
2213
+ }
2214
+ function getTextDocumentUri(params) {
2215
+ const p = params;
2216
+ const uri = p?.textDocument?.uri;
2217
+ return typeof uri === "string" ? uri : null;
2218
+ }
2219
+ function codeActionsFor(uri) {
2220
+ if (!isMarkdownUri(uri))
2221
+ return [];
2222
+ return [
2223
+ {
2224
+ title: "readit: Open Preview",
2225
+ kind: "source",
2226
+ command: {
2227
+ title: "readit: Open Preview",
2228
+ command: OPEN_PREVIEW_COMMAND,
2229
+ arguments: [uri]
2230
+ }
2231
+ },
2232
+ {
2233
+ title: "readit: Open Preview in Browser",
2234
+ kind: "source",
2235
+ command: {
2236
+ title: "readit: Open Preview in Browser",
2237
+ command: OPEN_PREVIEW_IN_BROWSER_COMMAND,
2238
+ arguments: [uri]
2239
+ }
2240
+ }
2241
+ ];
2242
+ }
2243
+ function cliInvocation(args) {
2244
+ if (!process.versions.bun) {
2245
+ throw new Error("Bun is required to run readit. Install Bun and retry.");
2246
+ }
2247
+ const entrypoint = process.argv[1];
2248
+ if (!entrypoint) {
2249
+ throw new Error("readit CLI entrypoint is unavailable");
2250
+ }
2251
+ return {
2252
+ command: process.execPath,
2253
+ args: [entrypoint, ...args]
2254
+ };
2255
+ }
2256
+ async function runReaditZedOpen(filePath) {
2257
+ const invocation = cliInvocation(["zed-open", filePath]);
2258
+ const child = spawn(invocation.command, invocation.args, {
2259
+ cwd: process.cwd(),
2260
+ env: { ...process.env },
2261
+ stdio: ["ignore", "ignore", "pipe"]
2262
+ });
2263
+ let stderr = "";
2264
+ child.stderr?.on("data", (chunk) => {
2265
+ stderr += chunk.toString("utf8");
2266
+ });
2267
+ const code = await new Promise((resolve3, reject) => {
2268
+ child.on("error", reject);
2269
+ child.on("exit", resolve3);
2270
+ });
2271
+ if (code !== 0) {
2272
+ throw new Error(stderr.trim() || `readit zed-open exited with status ${code}`);
2273
+ }
2274
+ }
2275
+ async function executeCommand(params) {
2276
+ const p = params;
2277
+ if (p.command !== OPEN_PREVIEW_COMMAND && p.command !== OPEN_PREVIEW_IN_BROWSER_COMMAND) {
2278
+ throw new Error(`unsupported command: ${String(p.command)}`);
2279
+ }
2280
+ const uri = p.arguments?.[0];
2281
+ if (typeof uri !== "string") {
2282
+ throw new Error("missing document URI");
2283
+ }
2284
+ const filePath = filePathFromUri(uri);
2285
+ if (!filePath) {
2286
+ throw new Error(`unsupported URI: ${uri}`);
2287
+ }
2288
+ if (!isMarkdownFile(filePath)) {
2289
+ throw new Error(`unsupported file type: ${filePath}`);
2290
+ }
2291
+ await runReaditZedOpen(filePath);
2292
+ }
2293
+ async function handleMessage(message, state) {
2294
+ if (typeof message.method !== "string") {
2295
+ if (hasId(message)) {
2296
+ sendError(message.id, INVALID_PARAMS, "missing method");
2297
+ }
2298
+ return;
2299
+ }
2300
+ const requestHasId = hasId(message);
2301
+ try {
2302
+ switch (message.method) {
2303
+ case "initialize":
2304
+ if (requestHasId) {
2305
+ sendResponse(message.id, {
2306
+ capabilities: {
2307
+ codeActionProvider: true,
2308
+ executeCommandProvider: {
2309
+ commands: [
2310
+ OPEN_PREVIEW_COMMAND,
2311
+ OPEN_PREVIEW_IN_BROWSER_COMMAND
2312
+ ]
2313
+ }
2314
+ },
2315
+ serverInfo: {
2316
+ name: "readit-zed-lsp",
2317
+ version: package_default.version
2318
+ }
2319
+ });
2320
+ }
2321
+ return;
2322
+ case "initialized":
2323
+ return;
2324
+ case "textDocument/codeAction": {
2325
+ const uri = getTextDocumentUri(message.params);
2326
+ if (requestHasId) {
2327
+ sendResponse(message.id, uri ? codeActionsFor(uri) : []);
2328
+ }
2329
+ return;
2330
+ }
2331
+ case "workspace/executeCommand":
2332
+ await executeCommand(message.params);
2333
+ if (requestHasId) {
2334
+ sendResponse(message.id, null);
2335
+ }
2336
+ return;
2337
+ case "shutdown":
2338
+ state.shuttingDown = true;
2339
+ if (requestHasId) {
2340
+ sendResponse(message.id, null);
2341
+ }
2342
+ return;
2343
+ case "exit":
2344
+ process.exit(state.shuttingDown ? 0 : 1);
2345
+ return;
2346
+ default:
2347
+ if (requestHasId) {
2348
+ sendError(message.id, METHOD_NOT_FOUND, `method not found: ${message.method}`);
2349
+ }
2350
+ }
2351
+ } catch (err) {
2352
+ if (requestHasId) {
2353
+ sendError(message.id, message.method === "workspace/executeCommand" ? INVALID_PARAMS : INTERNAL_ERROR, err instanceof Error ? err.message : String(err));
2354
+ }
2355
+ }
2356
+ }
2357
+ function processBuffer(buffer, onMessage) {
2358
+ let remaining = buffer;
2359
+ while (true) {
2360
+ const headerEnd = remaining.indexOf(`\r
2361
+ \r
2362
+ `);
2363
+ if (headerEnd === -1)
2364
+ return remaining;
2365
+ const header = remaining.subarray(0, headerEnd).toString("ascii");
2366
+ const lengthMatch = header.match(/content-length:\s*(\d+)/i);
2367
+ if (!lengthMatch) {
2368
+ remaining = remaining.subarray(headerEnd + 4);
2369
+ continue;
2370
+ }
2371
+ const contentLength = Number.parseInt(lengthMatch[1], 10);
2372
+ const bodyStart = headerEnd + 4;
2373
+ const bodyEnd = bodyStart + contentLength;
2374
+ if (remaining.length < bodyEnd)
2375
+ return remaining;
2376
+ const body = remaining.subarray(bodyStart, bodyEnd).toString("utf8");
2377
+ remaining = remaining.subarray(bodyEnd);
2378
+ try {
2379
+ onMessage(JSON.parse(body));
2380
+ } catch {
2381
+ sendError(null, INVALID_PARAMS, "invalid JSON-RPC payload");
2382
+ }
2383
+ }
2384
+ }
2385
+ function startZedLspServer() {
2386
+ let buffer = Buffer.alloc(0);
2387
+ const state = { shuttingDown: false };
2388
+ return new Promise((resolve3) => {
2389
+ process.stdin.on("data", (chunk) => {
2390
+ buffer = processBuffer(Buffer.concat([buffer, chunk]), (message) => {
2391
+ handleMessage(message, state);
2392
+ });
2393
+ });
2394
+ process.stdin.on("end", resolve3);
2395
+ process.stdin.resume();
2396
+ });
2397
+ }
2398
+
2167
2399
  // src/cli.ts
2168
2400
  var program = new Command;
2169
2401
  function isPermissionError(err) {
@@ -2175,6 +2407,8 @@ var SERVER_LOCK_PATH = join4(READIT_DIR, "server.lock");
2175
2407
  var SERVER_LOCK_MAX_AGE_MS = 30000;
2176
2408
  var SERVER_LOCK_TIMEOUT_MS = 1e4;
2177
2409
  var SERVER_LOCK_WAIT_MS = 100;
2410
+ var BACKGROUND_SERVER_TIMEOUT_MS = 1e4;
2411
+ var BACKGROUND_SERVER_WAIT_MS = 100;
2178
2412
  function isAlive(pid) {
2179
2413
  try {
2180
2414
  process.kill(pid, 0);
@@ -2189,6 +2423,52 @@ function getErrnoCode(err) {
2189
2423
  function sleep(ms) {
2190
2424
  return new Promise((resolve4) => setTimeout(resolve4, ms));
2191
2425
  }
2426
+ function parsePort(value) {
2427
+ const port = Number.parseInt(value, 10);
2428
+ if (Number.isNaN(port) || port < 1 || port > 65535) {
2429
+ throw new Error(`invalid port number: ${value}`);
2430
+ }
2431
+ return port;
2432
+ }
2433
+ function readRawOption(name, shortName) {
2434
+ const args = process.argv.slice(2);
2435
+ const longName = `--${name}`;
2436
+ for (let i = 0;i < args.length; i++) {
2437
+ const arg = args[i];
2438
+ if (arg === longName || shortName && arg === shortName) {
2439
+ const value = args[i + 1];
2440
+ return value && !value.startsWith("-") ? value : undefined;
2441
+ }
2442
+ if (arg.startsWith(`${longName}=`)) {
2443
+ return arg.slice(longName.length + 1);
2444
+ }
2445
+ }
2446
+ return;
2447
+ }
2448
+ function hasRawFlag(name) {
2449
+ return process.argv.slice(2).includes(`--${name}`);
2450
+ }
2451
+ function resolveExistingPath(arg) {
2452
+ const inputPath = resolve3(process.cwd(), arg);
2453
+ if (!existsSync(inputPath)) {
2454
+ console.error(`error: not found: ${inputPath}`);
2455
+ process.exit(1);
2456
+ }
2457
+ return realpathSync(inputPath);
2458
+ }
2459
+ function resolveMarkdownFile(arg) {
2460
+ const filePath = resolveExistingPath(arg);
2461
+ const stat3 = statSync(filePath);
2462
+ if (stat3.isDirectory()) {
2463
+ console.error(`error: expected a Markdown file, got directory: ${arg}`);
2464
+ process.exit(1);
2465
+ }
2466
+ if (!isMarkdownFile(filePath)) {
2467
+ console.error(`error: unsupported file type: ${arg} (expected .md or .markdown)`);
2468
+ process.exit(1);
2469
+ }
2470
+ return filePath;
2471
+ }
2192
2472
  async function clearStaleServerLock() {
2193
2473
  try {
2194
2474
  const [stats, content] = await Promise.all([
@@ -2259,31 +2539,82 @@ async function discoverServer() {
2259
2539
  return null;
2260
2540
  }
2261
2541
  }
2262
- async function attachFiles(server, files) {
2542
+ async function addDocumentToServer(server, file) {
2543
+ const res = await fetch(`http://127.0.0.1:${server.port}/api/documents`, {
2544
+ method: "POST",
2545
+ headers: { "Content-Type": "application/json" },
2546
+ body: JSON.stringify({ path: file.path })
2547
+ });
2548
+ const data = await res.json();
2549
+ if (!res.ok) {
2550
+ throw new Error(`failed to add ${file.path}: ${data.error ?? res.statusText}`);
2551
+ }
2552
+ return data;
2553
+ }
2554
+ async function attachFiles(server, files, opts = {}) {
2263
2555
  for (const file of files) {
2264
2556
  try {
2265
- const res = await fetch(`http://127.0.0.1:${server.port}/api/documents`, {
2266
- method: "POST",
2267
- headers: { "Content-Type": "application/json" },
2268
- body: JSON.stringify({ path: file.path })
2269
- });
2270
- if (!res.ok) {
2271
- const data2 = await res.json();
2272
- console.error(`error: failed to add ${file.path}: ${data2.error}`);
2273
- process.exit(1);
2274
- }
2275
- const data = await res.json();
2276
- if (data.status === "added") {
2277
- console.log(`Added: ${data.fileName}`);
2278
- } else {
2279
- console.log(`Present: ${data.fileName}`);
2557
+ const data = await addDocumentToServer(server, file);
2558
+ if (!opts.quiet) {
2559
+ if (data.status === "added") {
2560
+ console.log(`Added: ${data.fileName}`);
2561
+ } else {
2562
+ console.log(`Present: ${data.fileName}`);
2563
+ }
2280
2564
  }
2281
2565
  } catch (err) {
2282
- console.error("error: failed to connect to server:", err instanceof Error ? err.message : err);
2566
+ console.error("error: failed to add document:", err instanceof Error ? err.message : err);
2283
2567
  process.exit(1);
2284
2568
  }
2285
2569
  }
2286
2570
  }
2571
+ function readitUrlForFile(port, filePath) {
2572
+ return `http://127.0.0.1:${port}/?path=${encodeURIComponent(filePath)}`;
2573
+ }
2574
+ function getCurrentCliInvocation(args) {
2575
+ if (!process.versions.bun) {
2576
+ throw new Error("Bun is required to start readit. Install Bun and retry.");
2577
+ }
2578
+ const entrypoint = process.argv[1];
2579
+ if (!entrypoint) {
2580
+ throw new Error("failed to locate the readit CLI entrypoint");
2581
+ }
2582
+ return {
2583
+ command: process.execPath,
2584
+ args: [entrypoint, ...args]
2585
+ };
2586
+ }
2587
+ async function waitForBackgroundServer(pid) {
2588
+ const startedAt = Date.now();
2589
+ while (Date.now() - startedAt < BACKGROUND_SERVER_TIMEOUT_MS) {
2590
+ const server = await discoverServer();
2591
+ if (server)
2592
+ return server;
2593
+ if (pid !== undefined && !isAlive(pid)) {
2594
+ throw new Error("readit server process exited before becoming healthy");
2595
+ }
2596
+ await sleep(BACKGROUND_SERVER_WAIT_MS);
2597
+ }
2598
+ throw new Error("timed out waiting for readit server to start");
2599
+ }
2600
+ async function startBackgroundServer(files, port, host) {
2601
+ const invocation = getCurrentCliInvocation([
2602
+ "--port",
2603
+ String(port),
2604
+ "--host",
2605
+ host,
2606
+ "--no-open",
2607
+ ...files.map((file) => file.filePath)
2608
+ ]);
2609
+ const child = spawn2(invocation.command, invocation.args, {
2610
+ cwd: process.cwd(),
2611
+ detached: true,
2612
+ env: { ...process.env },
2613
+ stdio: "ignore"
2614
+ });
2615
+ child.unref();
2616
+ return waitForBackgroundServer(child.pid);
2617
+ }
2287
2618
  async function getServerTarget(files, port, host) {
2288
2619
  return withServerLock(async () => {
2289
2620
  const server = await discoverServer();
@@ -2365,12 +2696,7 @@ function resolveFiles(args) {
2365
2696
  const seen = new Set;
2366
2697
  const files = [];
2367
2698
  for (const arg of args) {
2368
- const inputPath = resolve3(process.cwd(), arg);
2369
- if (!existsSync(inputPath)) {
2370
- console.error(`error: not found: ${inputPath}`);
2371
- process.exit(1);
2372
- }
2373
- const filePath = realpathSync(inputPath);
2699
+ const filePath = resolveExistingPath(arg);
2374
2700
  const stat3 = statSync(filePath);
2375
2701
  if (stat3.isDirectory()) {
2376
2702
  const found = findReviewableFiles(filePath);
@@ -2567,9 +2893,11 @@ program.argument("[files...]", "Markdown files/directories to review").option("-
2567
2893
  process.exit(1);
2568
2894
  }
2569
2895
  }
2570
- const preferredPort = Number.parseInt(options.port, 10);
2571
- if (Number.isNaN(preferredPort) || preferredPort < 1 || preferredPort > 65535) {
2572
- console.error(`error: invalid port number: ${options.port}`);
2896
+ let preferredPort;
2897
+ try {
2898
+ preferredPort = parsePort(options.port);
2899
+ } catch (err) {
2900
+ console.error("error:", err instanceof Error ? err.message : String(err));
2573
2901
  process.exit(1);
2574
2902
  }
2575
2903
  let previousPort;
@@ -2631,25 +2959,55 @@ Shutting down...`);
2631
2959
  process.exit(1);
2632
2960
  }
2633
2961
  });
2962
+ program.command("zed-open").argument("<file>", "Markdown file to open in readit from Zed").description("Open a Markdown file in readit and return after launch").option("-p, --port <number>", "Port for new server (if starting)", "4567").option("--host <address>", "Host for new server (if starting)", "127.0.0.1").option("--no-open", "Don't automatically open browser").action(async (fileArg, options) => {
2963
+ const filePath = resolveMarkdownFile(fileArg);
2964
+ const host = readRawOption("host") ?? options.host;
2965
+ const shouldOpen = options.open && !hasRawFlag("no-open");
2966
+ let preferredPort;
2967
+ try {
2968
+ preferredPort = parsePort(readRawOption("port", "-p") ?? options.port);
2969
+ } catch (err) {
2970
+ console.error("error:", err instanceof Error ? err.message : String(err));
2971
+ process.exit(1);
2972
+ }
2973
+ try {
2974
+ const file = { path: filePath };
2975
+ const server = await withServerLock(async () => {
2976
+ const existing = await discoverServer();
2977
+ if (existing)
2978
+ return existing;
2979
+ return startBackgroundServer([{ filePath }], preferredPort, host);
2980
+ });
2981
+ await attachFiles(server, [file], { quiet: true });
2982
+ const url = readitUrlForFile(server.port, filePath);
2983
+ if (shouldOpen) {
2984
+ await open2(url);
2985
+ }
2986
+ console.log(`Opened: ${url}`);
2987
+ } catch (err) {
2988
+ console.error("error: failed to open readit from Zed:", err instanceof Error ? err.message : err);
2989
+ process.exit(1);
2990
+ }
2991
+ });
2992
+ program.command("zed-lsp").description("Run the readit Zed LSP bridge").action(async () => {
2993
+ await startZedLspServer();
2994
+ });
2634
2995
  program.command("open").argument("<files...>", "Markdown files to add to running server").description("Add files to a running readit server, or start a new one").option("-p, --port <number>", "Port for new server (if starting)", "4567").option("--host <address>", "Host for new server (if starting)", "127.0.0.1").action(async (fileArgs, options) => {
2635
2996
  const resolvedFiles = [];
2636
2997
  for (const arg of fileArgs) {
2637
- const inputPath = resolve3(process.cwd(), arg);
2638
- if (!existsSync(inputPath)) {
2639
- console.error(`error: not found: ${inputPath}`);
2640
- process.exit(1);
2641
- }
2642
- const filePath = realpathSync(inputPath);
2643
- if (!isMarkdownFile(filePath)) {
2644
- console.error(`error: unsupported file type: ${arg} (expected .md or .markdown)`);
2645
- process.exit(1);
2646
- }
2998
+ const filePath = resolveMarkdownFile(arg);
2647
2999
  resolvedFiles.push({ path: filePath });
2648
3000
  }
2649
3001
  const files = resolvedFiles.map((f) => ({
2650
3002
  filePath: f.path
2651
3003
  }));
2652
- const preferredPort = Number.parseInt(options.port, 10);
3004
+ let preferredPort;
3005
+ try {
3006
+ preferredPort = parsePort(options.port);
3007
+ } catch (err) {
3008
+ console.error("error:", err instanceof Error ? err.message : String(err));
3009
+ process.exit(1);
3010
+ }
2653
3011
  try {
2654
3012
  const target = await getServerTarget(files, preferredPort, options.host);
2655
3013
  if (target.kind === "existing") {
@@ -2749,6 +3107,8 @@ _readit() {
2749
3107
  cmd_or_files)
2750
3108
  local -a commands=(
2751
3109
  'open:Add files to running server'
3110
+ 'zed-open:Open a Markdown file from Zed'
3111
+ 'zed-lsp:Run the Zed LSP bridge'
2752
3112
  'list:List files with comments'
2753
3113
  'show:Show comments for a file'
2754
3114
  'completion:Output shell completion script'
@@ -2757,7 +3117,7 @@ _readit() {
2757
3117
  ;;
2758
3118
  args)
2759
3119
  case "\${line[1]}" in
2760
- open) _arguments '*:file:_readit_markdown_files' ;;
3120
+ open|zed-open) _arguments '*:file:_readit_markdown_files' ;;
2761
3121
  show) _arguments '1:file:_files -g "*.md *.markdown"' ;;
2762
3122
  *) _arguments '*:file:_readit_markdown_files' ;;
2763
3123
  esac
@@ -2777,7 +3137,7 @@ _readit_completions() {
2777
3137
  COMPREPLY=()
2778
3138
  cur="\${COMP_WORDS[COMP_CWORD]}"
2779
3139
  prev="\${COMP_WORDS[COMP_CWORD-1]}"
2780
- commands="open list show completion"
3140
+ commands="open zed-open zed-lsp list show completion"
2781
3141
 
2782
3142
  if [[ \${COMP_CWORD} -eq 1 ]]; then
2783
3143
  COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
@@ -2789,7 +3149,7 @@ _readit_completions() {
2789
3149
  fi
2790
3150
 
2791
3151
  case "\${COMP_WORDS[1]}" in
2792
- open|show)
3152
+ open|zed-open|show)
2793
3153
  local files=$(find . -type f \\( -name '*.md' -o -name '*.markdown' \\) \\
2794
3154
  -not -path '*/.*' -not -path '*/node_modules/*' 2>/dev/null | sed 's|^\\./||')
2795
3155
  COMPREPLY=( $(compgen -W "\${files}" -- "\${cur}") )
@@ -2814,6 +3174,8 @@ complete -c readit -f
2814
3174
 
2815
3175
  # Subcommands
2816
3176
  complete -c readit -n '__fish_use_subcommand' -a 'open' -d 'Add files to running server'
3177
+ complete -c readit -n '__fish_use_subcommand' -a 'zed-open' -d 'Open a Markdown file from Zed'
3178
+ complete -c readit -n '__fish_use_subcommand' -a 'zed-lsp' -d 'Run the Zed LSP bridge'
2817
3179
  complete -c readit -n '__fish_use_subcommand' -a 'list' -d 'List files with comments'
2818
3180
  complete -c readit -n '__fish_use_subcommand' -a 'show' -d 'Show comments for a file'
2819
3181
  complete -c readit -n '__fish_use_subcommand' -a 'completion' -d 'Output shell completion script'
@@ -2827,6 +3189,7 @@ complete -c readit -l clean -d 'Clear existing comments'
2827
3189
  # File arguments for default command and open
2828
3190
  complete -c readit -n '__fish_use_subcommand' -F -a '(find . -type f \\( -name "*.md" -o -name "*.markdown" \\) -not -path "*/.*" -not -path "*/node_modules/*" 2>/dev/null | sed "s|^\\./||")'
2829
3191
  complete -c readit -n '__fish_seen_subcommand_from open' -F -a '(find . -type f \\( -name "*.md" -o -name "*.markdown" \\) -not -path "*/.*" -not -path "*/node_modules/*" 2>/dev/null | sed "s|^\\./||")'
3192
+ complete -c readit -n '__fish_seen_subcommand_from zed-open' -F -a '(find . -type f \\( -name "*.md" -o -name "*.markdown" \\) -not -path "*/.*" -not -path "*/node_modules/*" 2>/dev/null | sed "s|^\\./||")'
2830
3193
  complete -c readit -n '__fish_seen_subcommand_from show' -F -a '(find . -type f \\( -name "*.md" -o -name "*.markdown" \\) -not -path "*/.*" -not -path "*/node_modules/*" 2>/dev/null | sed "s|^\\./||")'
2831
3194
 
2832
3195
  # Shell completions for completion subcommand
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peaske7/readit",
3
- "version": "0.3.3-rc.0",
3
+ "version": "0.3.3-rc.2",
4
4
  "description": "A CLI tool to review Markdown documents with inline comments",
5
5
  "author": "Jay Shimada <peaske@pm.me>",
6
6
  "license": "MIT",
@@ -52,7 +52,7 @@
52
52
  "mermaid": "^11.15.0",
53
53
  "open": "^11.0.0",
54
54
  "jsdom": "^28.1.0",
55
- "shiki": "^4.0.2",
55
+ "shiki": "^4.1.0",
56
56
  "tailwind-merge": "^3.6.0"
57
57
  },
58
58
  "devDependencies": {
@@ -64,9 +64,9 @@
64
64
  "@types/bun": "^1.3.14",
65
65
  "@types/jsdom": "^28.0.3",
66
66
  "@types/markdown-it": "^14.1.2",
67
- "@types/node": "^25.8.0",
68
- "lefthook": "^2.1.6",
69
- "svelte": "^5.55.7",
67
+ "@types/node": "^25.9.0",
68
+ "lefthook": "^2.1.7",
69
+ "svelte": "^5.55.8",
70
70
  "tailwindcss": "^4.3.0",
71
71
  "typescript": "^5.9.3",
72
72
  "vite": "^8.0.13",