@imenam/mcp-documentor 1.0.88 → 1.0.90

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 (60) hide show
  1. package/README.md +4 -2
  2. package/dist/src/core/blob-store.d.ts +5 -0
  3. package/dist/src/core/blob-store.js +49 -0
  4. package/dist/src/core/git-sync.js +12 -0
  5. package/dist/src/core/github-code-api.d.ts +5 -4
  6. package/dist/src/core/github-code-api.js +31 -9
  7. package/dist/src/index.js +2 -0
  8. package/gui/dist/assets/{arc-s4GL8QZ_.js → arc-VljQAUvA.js} +1 -1
  9. package/gui/dist/assets/{architectureDiagram-3BPJPVTR-CfDERg0r.js → architectureDiagram-3BPJPVTR-lbkjtvmN.js} +1 -1
  10. package/gui/dist/assets/{blockDiagram-GPEHLZMM-CjDoEG3-.js → blockDiagram-GPEHLZMM-G4A-rV4c.js} +1 -1
  11. package/gui/dist/assets/{c4Diagram-AAUBKEIU-9o7f2UhK.js → c4Diagram-AAUBKEIU-Dq3aMw8v.js} +1 -1
  12. package/gui/dist/assets/channel-DFvG2cZZ.js +1 -0
  13. package/gui/dist/assets/{chunk-2J33WTMH-B37ouRYO.js → chunk-2J33WTMH-VSzyIJHo.js} +1 -1
  14. package/gui/dist/assets/{chunk-4BX2VUAB-Dcq44Skv.js → chunk-4BX2VUAB-DER41q8w.js} +1 -1
  15. package/gui/dist/assets/{chunk-55IACEB6-CLyM-RQf.js → chunk-55IACEB6-BlOGGRk4.js} +1 -1
  16. package/gui/dist/assets/{chunk-727SXJPM-BjzEo2iz.js → chunk-727SXJPM-Dv3Ay2KV.js} +1 -1
  17. package/gui/dist/assets/{chunk-AQP2D5EJ-Co03OHPi.js → chunk-AQP2D5EJ-BycHHbwh.js} +1 -1
  18. package/gui/dist/assets/{chunk-FMBD7UC4-ClbF2rAp.js → chunk-FMBD7UC4-DBoCiqpY.js} +1 -1
  19. package/gui/dist/assets/{chunk-ND2GUHAM-CN5cTCWu.js → chunk-ND2GUHAM-DGKypjhb.js} +1 -1
  20. package/gui/dist/assets/{chunk-QZHKN3VN-Bg4-kEPK.js → chunk-QZHKN3VN-B8Gn_mBo.js} +1 -1
  21. package/gui/dist/assets/classDiagram-4FO5ZUOK-B8xEoAMI.js +1 -0
  22. package/gui/dist/assets/classDiagram-v2-Q7XG4LA2-B8xEoAMI.js +1 -0
  23. package/gui/dist/assets/{cose-bilkent-S5V4N54A-6Dkp59bE.js → cose-bilkent-S5V4N54A-DyamTuYj.js} +1 -1
  24. package/gui/dist/assets/{dagre-BM42HDAG-2dD-ID2T.js → dagre-BM42HDAG-BKC5D2WN.js} +1 -1
  25. package/gui/dist/assets/{diagram-2AECGRRQ-DBE2f2Lb.js → diagram-2AECGRRQ-BV2lBpwH.js} +1 -1
  26. package/gui/dist/assets/{diagram-5GNKFQAL-CkKPHiH9.js → diagram-5GNKFQAL-DGwbAj4W.js} +1 -1
  27. package/gui/dist/assets/{diagram-KO2AKTUF-uWtGAF2N.js → diagram-KO2AKTUF-CZAde8Sg.js} +1 -1
  28. package/gui/dist/assets/{diagram-LMA3HP47-DyQaWg_0.js → diagram-LMA3HP47-fQfo0z7i.js} +1 -1
  29. package/gui/dist/assets/{diagram-OG6HWLK6-H_DfuB5t.js → diagram-OG6HWLK6-BlD3VAFM.js} +1 -1
  30. package/gui/dist/assets/{erDiagram-TEJ5UH35-Cj9K5EXA.js → erDiagram-TEJ5UH35-DPUEmQfe.js} +1 -1
  31. package/gui/dist/assets/{flowDiagram-I6XJVG4X-BYF24Jvt.js → flowDiagram-I6XJVG4X-p2HfkBjW.js} +1 -1
  32. package/gui/dist/assets/{ganttDiagram-6RSMTGT7-D93LjmhI.js → ganttDiagram-6RSMTGT7-CWfFoy4g.js} +1 -1
  33. package/gui/dist/assets/{gitGraphDiagram-PVQCEYII-EVTcusDb.js → gitGraphDiagram-PVQCEYII-DkTKzmes.js} +1 -1
  34. package/gui/dist/assets/index-BJ4WCWig.css +1 -0
  35. package/gui/dist/assets/{index-DvVxuv5N.js → index-C8LVeAsA.js} +181 -181
  36. package/gui/dist/assets/{infoDiagram-5YYISTIA-DeGbaexj.js → infoDiagram-5YYISTIA-CXHTqZoU.js} +1 -1
  37. package/gui/dist/assets/{ishikawaDiagram-YF4QCWOH-DNdk7sPJ.js → ishikawaDiagram-YF4QCWOH-DZ61Cc8a.js} +1 -1
  38. package/gui/dist/assets/{journeyDiagram-JHISSGLW-yLLfyn3W.js → journeyDiagram-JHISSGLW-qYtEmFuB.js} +1 -1
  39. package/gui/dist/assets/{kanban-definition-UN3LZRKU-D8yvVgr8.js → kanban-definition-UN3LZRKU-Bs-W313s.js} +1 -1
  40. package/gui/dist/assets/{linear-j3lplktA.js → linear-CU6J91M5.js} +1 -1
  41. package/gui/dist/assets/{mindmap-definition-RKZ34NQL-t9adVeMH.js → mindmap-definition-RKZ34NQL-CvBqAH4c.js} +1 -1
  42. package/gui/dist/assets/{pieDiagram-4H26LBE5-6Yrvo8h8.js → pieDiagram-4H26LBE5-BD2UcQce.js} +1 -1
  43. package/gui/dist/assets/{quadrantDiagram-W4KKPZXB-C_3Su6rX.js → quadrantDiagram-W4KKPZXB-DEuynlP9.js} +1 -1
  44. package/gui/dist/assets/{requirementDiagram-4Y6WPE33-B1qYSC11.js → requirementDiagram-4Y6WPE33-DPxfzOF3.js} +1 -1
  45. package/gui/dist/assets/{sankeyDiagram-5OEKKPKP-D3Ervn-u.js → sankeyDiagram-5OEKKPKP-C70_cePR.js} +1 -1
  46. package/gui/dist/assets/{sequenceDiagram-3UESZ5HK-CKgTeWBS.js → sequenceDiagram-3UESZ5HK-FDb_1B1W.js} +1 -1
  47. package/gui/dist/assets/{stateDiagram-AJRCARHV-D_PjEdFH.js → stateDiagram-AJRCARHV-BH1bq2it.js} +1 -1
  48. package/gui/dist/assets/stateDiagram-v2-BHNVJYJU-BfpDu1Qi.js +1 -0
  49. package/gui/dist/assets/{timeline-definition-PNZ67QCA-HeWRiNHu.js → timeline-definition-PNZ67QCA-DNUx0JIp.js} +1 -1
  50. package/gui/dist/assets/{vennDiagram-CIIHVFJN-CREk7vzm.js → vennDiagram-CIIHVFJN-DEAfzbjb.js} +1 -1
  51. package/gui/dist/assets/{wardley-L42UT6IY-BWtRQ7ZH.js → wardley-L42UT6IY-_REPWPb-.js} +1 -1
  52. package/gui/dist/assets/{wardleyDiagram-YWT4CUSO-BvlqWWwp.js → wardleyDiagram-YWT4CUSO-CZFLXFE3.js} +1 -1
  53. package/gui/dist/assets/{xychartDiagram-2RQKCTM6-BCjv6pwc.js → xychartDiagram-2RQKCTM6-P2Nhif6M.js} +1 -1
  54. package/gui/dist/index.html +2 -2
  55. package/package.json +1 -1
  56. package/gui/dist/assets/channel-BsKHRH5P.js +0 -1
  57. package/gui/dist/assets/classDiagram-4FO5ZUOK-DphzXyc8.js +0 -1
  58. package/gui/dist/assets/classDiagram-v2-Q7XG4LA2-DphzXyc8.js +0 -1
  59. package/gui/dist/assets/index-CtPuh-0k.css +0 -1
  60. package/gui/dist/assets/stateDiagram-v2-BHNVJYJU-KPbsissC.js +0 -1
package/README.md CHANGED
@@ -12,7 +12,7 @@ Connect it to Cursor, Claude Desktop, or any MCP-compatible agent to let your AI
12
12
  - **Track a branch, not a checkout** — point a project at a GitHub branch from its settings in the GUI and freshness is measured against *published* code: switching branches or editing without committing no longer makes docs look stale, and a server with no copy of the codebase (an MCP gateway) can still verify them
13
13
  - **Semantic search** — find relevant docs by meaning (requires an OpenAI API key)
14
14
  - **Multi-project support** — organize docs into separate projects; every MCP tool names the project it targets, and the GUI's project selector is a per-browser view preference
15
- - **GUI** — built-in web interface, either behind the proxy (`PROXY_URL`) or served directly on a local port (`APP_PORT`); with neither, the GUI worker is not started
15
+ - **GUI** — built-in web interface, either behind the proxy (`PROXY_URL`) or served directly on a local port (`APP_PORT`); with neither, the GUI worker is not started. Navigation runs from an icon rail on the left: the current project's badge (click to switch projects or create one), then Documentation, Search (`Ctrl/⌘ + K`, current or all projects), Outdated, Recent, Export bundles and Logs, with Sync and Settings at the bottom. Each destination fills the panel next to the rail; the panel's hide button, or a second click on the active icon, folds it away. Drag the panel's right edge to resize it (double-click resets the width); the browser remembers the width. The details panel on the right of a document (`Ctrl/⌘ + \`) is open by default from 1280 px wide; showing or hiding it by hand is remembered across reloads until the window crosses that width. Badges show the outdated docs, pending commits and the day's failed GitHub calls
16
16
  - **Proxy-aware** — integrates with the [proxy-setup](https://github.com/Imena/proxy-setup) for GUI exposure
17
17
  - **Links & structure** — `check_links`, `get_doc_tree`, `move_doc`, and `git_diff_doc` for maintenance workflows
18
18
  - **Image support** — upload images to a project's assets via `upload_image` (local file path) or directly from the GUI editor; images are stored per-project and rendered inline in the viewer and in PDF exports
@@ -312,7 +312,9 @@ Freshness is **never computed in the background** — only when something asks f
312
312
 
313
313
  Within one such request, the branch head is resolved **once** and every tracked file is compared against that same commit — no mixed verdict if someone pushes mid-scan. The next request resolves it again, so a push is visible immediately: there is no staleness window to configure.
314
314
 
315
- Everything else is content-addressed and therefore cached for the life of the process: a commit's tree by its sha, a file's bytes by its blob sha. Each tracked file also records the blob sha it was snapshotted at, so an unchanged file is proven untouched without being downloaded at all. A full scan of an unchanged repository costs about two API calls — not one per file.
315
+ Resolving the branch head is a conditional request: while the branch has not moved, GitHub answers `304 Not Modified`, which does not count against the rate limit of an authenticated token.
316
+
317
+ Everything else is content-addressed: a commit's tree is cached by its sha for the life of the process, and a file's bytes by their blob sha on disk, in `<data dir>/blob-cache/` (machine-local, excluded from sync) — each version of a file is downloaded once per machine, across restarts. Each tracked file also records the blob sha it was snapshotted at, so an unchanged file is proven untouched without being downloaded at all. A full scan of an unchanged repository costs about two API calls — not one per file.
316
318
 
317
319
  ## Data Storage
318
320
 
@@ -0,0 +1,5 @@
1
+ export declare function setBlobStoreDir(dir: string): void;
2
+ /** A stored blob, or undefined when it is not on disk. An entry that does not hash to its sha is dropped. */
3
+ export declare function readStoredBlob(sha: string): Buffer | undefined;
4
+ /** Stores a blob. Written to a temporary file then renamed, so a reader never sees a partial one. */
5
+ export declare function storeBlob(sha: string, bytes: Buffer): void;
@@ -0,0 +1,49 @@
1
+ import crypto from "crypto";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import { createLogger } from "@imenam/mcp-gui-interface";
5
+ const logger = createLogger("BlobStore");
6
+ /**
7
+ * Git blobs downloaded from GitHub, kept on disk by sha. A blob's content is fixed by its sha, so
8
+ * an entry never goes stale: each version of a file is downloaded once per machine, whatever the
9
+ * number of restarts. Entries are checked against their sha when read.
10
+ */
11
+ let storeDir;
12
+ export function setBlobStoreDir(dir) {
13
+ storeDir = dir;
14
+ }
15
+ function blobPath(sha) {
16
+ if (!storeDir || !/^[0-9a-f]{40}$/.test(sha))
17
+ return undefined;
18
+ return path.join(storeDir, sha.slice(0, 2), sha);
19
+ }
20
+ /** The git blob sha of some bytes. */
21
+ function gitBlobSha(bytes) {
22
+ return crypto.createHash("sha1").update(`blob ${bytes.length}\0`).update(bytes).digest("hex");
23
+ }
24
+ /** A stored blob, or undefined when it is not on disk. An entry that does not hash to its sha is dropped. */
25
+ export function readStoredBlob(sha) {
26
+ const file = blobPath(sha);
27
+ if (!file || !fs.existsSync(file))
28
+ return undefined;
29
+ const bytes = fs.readFileSync(file);
30
+ if (gitBlobSha(bytes) === sha)
31
+ return bytes;
32
+ logger.warn(`Stored blob ${sha.slice(0, 7)} does not match its sha; removing it`);
33
+ fs.rmSync(file, { force: true });
34
+ return undefined;
35
+ }
36
+ /** Stores a blob. Written to a temporary file then renamed, so a reader never sees a partial one. */
37
+ export function storeBlob(sha, bytes) {
38
+ const file = blobPath(sha);
39
+ if (!file)
40
+ return;
41
+ if (gitBlobSha(bytes) !== sha) {
42
+ logger.warn(`Downloaded content of blob ${sha.slice(0, 7)} does not match its sha; not storing it`);
43
+ return;
44
+ }
45
+ fs.mkdirSync(path.dirname(file), { recursive: true });
46
+ const temp = `${file}.${process.pid}.tmp`;
47
+ fs.writeFileSync(temp, bytes);
48
+ fs.renameSync(temp, file);
49
+ }
@@ -21,6 +21,7 @@ const GITIGNORE_CONTENT = [
21
21
  "errors-tmp-files/",
22
22
  "export-bundles.json",
23
23
  "local-state.json",
24
+ "blob-cache/",
24
25
  "",
25
26
  ].join("\n");
26
27
  /**
@@ -143,10 +144,21 @@ export class SyncEngine {
143
144
  async ensureRepo() {
144
145
  const cfg = this.requireConfig();
145
146
  const authed = this.authedUrl(cfg);
147
+ // Entries a newer server added are appended to an existing file, so machine-local state it
148
+ // introduces is never staged in a repo initialized before it.
146
149
  const gitignorePath = path.join(this.dataDir, ".gitignore");
147
150
  if (!fs.existsSync(gitignorePath)) {
148
151
  fs.writeFileSync(gitignorePath, GITIGNORE_CONTENT, "utf-8");
149
152
  }
153
+ else {
154
+ const current = fs.readFileSync(gitignorePath, "utf-8");
155
+ const present = new Set(current.split(/\r?\n/).map((line) => line.trim()));
156
+ const missing = GITIGNORE_CONTENT.split("\n").filter((line) => line && !line.startsWith("#") && !present.has(line));
157
+ if (missing.length > 0) {
158
+ const separator = current === "" || current.endsWith("\n") ? "" : "\n";
159
+ fs.writeFileSync(gitignorePath, `${current}${separator}${missing.join("\n")}\n`, "utf-8");
160
+ }
161
+ }
150
162
  // Rewritten when it drifts, so an upgraded server updates the routing of
151
163
  // repos that were initialized by an older version.
152
164
  const attributesPath = path.join(this.dataDir, ".gitattributes");
@@ -59,8 +59,8 @@ export declare function searchRepositories(term: string, token: string | undefin
59
59
  * - the commit a ref points at — the only time-varying one, so it is resolved once per
60
60
  * operation and never carried across them (see `code-operation`);
61
61
  * - that commit's tree (path → blob sha) — immutable, cached per commit;
62
- * - a blob's bytes — immutable, cached per blob sha, so a file that survives a commit
63
- * untouched is never downloaded twice.
62
+ * - a blob's bytes — immutable, cached per blob sha in memory and on disk (see `blob-store`),
63
+ * so a given version of a file is downloaded once per machine.
64
64
  *
65
65
  * One instance per (repo, ref) pair; instances are shared across projects by `code-source`.
66
66
  */
@@ -78,7 +78,8 @@ export declare class GitHubCodeApi {
78
78
  * The commit the configured ref points at right now.
79
79
  *
80
80
  * Resolved afresh for each operation — so a push is visible to the very next call — but only
81
- * once within it, however many files that operation ends up reading.
81
+ * once within it, however many files that operation ends up reading. The request is conditional:
82
+ * while the branch has not moved, GitHub answers 304 and the call costs no rate limit.
82
83
  */
83
84
  resolveRef(): Promise<ResolvedRef>;
84
85
  /**
@@ -102,7 +103,7 @@ export declare class GitHubCodeApi {
102
103
  /** Blob sha for one path at one commit, or undefined if the file does not exist there. */
103
104
  getBlobSha(commitSha: string, repoPath: string): Promise<string | undefined>;
104
105
  /**
105
- * A blob's bytes. Blobs are immutable, so this is cached for the life of the process.
106
+ * A blob's bytes, read from the blob store when this machine already downloaded them.
106
107
  * `filePath` names the file in github.log.
107
108
  */
108
109
  getBlob(blobSha: string, filePath?: string): Promise<Buffer>;
@@ -1,6 +1,7 @@
1
1
  import { createLogger } from "@imenam/mcp-gui-interface";
2
2
  import { oncePerOperation } from "./code-operation.js";
3
3
  import { recordGitHubCall, redactSecrets } from "./github-log.js";
4
+ import { readStoredBlob, storeBlob } from "./blob-store.js";
4
5
  const logger = createLogger("GitHubCodeApi");
5
6
  const API_BASE = "https://api.github.com";
6
7
  export function formatRepoSpec(spec) {
@@ -40,6 +41,8 @@ export class GitHubApiError extends Error {
40
41
  this.name = "GitHubApiError";
41
42
  }
42
43
  }
44
+ /** Last answer to each revalidated path, under the token it was read with. */
45
+ const revalidated = new Map();
43
46
  function readRateLimit(response) {
44
47
  const remaining = Number(response.headers.get("x-ratelimit-remaining"));
45
48
  const limit = Number(response.headers.get("x-ratelimit-limit"));
@@ -61,6 +64,10 @@ async function githubRequest(apiPath, token, purpose) {
61
64
  };
62
65
  if (token)
63
66
  headers.Authorization = `Bearer ${token}`;
67
+ const cacheKey = `${token ?? ""} ${apiPath}`;
68
+ const previous = purpose.revalidate ? revalidated.get(cacheKey) : undefined;
69
+ if (previous)
70
+ headers["If-None-Match"] = previous.etag;
64
71
  const { label } = purpose;
65
72
  const startedAt = Date.now();
66
73
  const record = (status, ok, error, rateLimit) => recordGitHubCall({
@@ -85,6 +92,10 @@ async function githubRequest(apiPath, token, purpose) {
85
92
  }
86
93
  const rateLimit = readRateLimit(response);
87
94
  const status = String(response.status);
95
+ if (response.status === 304 && previous) {
96
+ record(status, true, undefined, rateLimit);
97
+ return previous.body;
98
+ }
88
99
  if (response.status === 404 || response.status === 422) {
89
100
  const message = `not found: ${label} (repository, branch or path missing — or the token cannot see this repository)`;
90
101
  record(status, false, message, rateLimit);
@@ -104,7 +115,11 @@ async function githubRequest(apiPath, token, purpose) {
104
115
  throw new GitHubApiError(message, response.status);
105
116
  }
106
117
  record(status, true, undefined, rateLimit);
107
- return response.json();
118
+ const body = await response.json();
119
+ const etag = response.headers.get("etag");
120
+ if (purpose.revalidate && etag)
121
+ revalidated.set(cacheKey, { etag, body });
122
+ return body;
108
123
  }
109
124
  /** Visibility and default branch of a repository. */
110
125
  export async function getRepositoryInfo(owner, repo, token) {
@@ -137,7 +152,7 @@ export async function listBranches(owner, repo, token) {
137
152
  }
138
153
  /** The commit a branch points at, with its first message line. */
139
154
  export async function getBranchHead(owner, repo, branch, token) {
140
- const commit = await githubRequest(`/repos/${owner}/${repo}/commits/${encodeURIComponent(branch)}`, token, { label: `${owner}/${repo}@${branch}`, action: `Read the head commit of ${branch}`, repo: `${owner}/${repo}` });
155
+ const commit = await githubRequest(`/repos/${owner}/${repo}/commits/${encodeURIComponent(branch)}`, token, { label: `${owner}/${repo}@${branch}`, action: `Read the head commit of ${branch}`, repo: `${owner}/${repo}`, revalidate: true });
141
156
  return {
142
157
  sha: commit.sha,
143
158
  message: String(commit.commit?.message ?? "").split("\n")[0],
@@ -176,8 +191,8 @@ export async function searchRepositories(term, token, limit) {
176
191
  * - the commit a ref points at — the only time-varying one, so it is resolved once per
177
192
  * operation and never carried across them (see `code-operation`);
178
193
  * - that commit's tree (path → blob sha) — immutable, cached per commit;
179
- * - a blob's bytes — immutable, cached per blob sha, so a file that survives a commit
180
- * untouched is never downloaded twice.
194
+ * - a blob's bytes — immutable, cached per blob sha in memory and on disk (see `blob-store`),
195
+ * so a given version of a file is downloaded once per machine.
181
196
  *
182
197
  * One instance per (repo, ref) pair; instances are shared across projects by `code-source`.
183
198
  */
@@ -198,23 +213,25 @@ export class GitHubCodeApi {
198
213
  get label() {
199
214
  return formatRepoSpec(this.spec);
200
215
  }
201
- request(path, action) {
216
+ request(path, action, revalidate = false) {
202
217
  return githubRequest(path, this.token, {
203
218
  label: this.label,
204
219
  action,
205
220
  repo: `${this.spec.owner}/${this.spec.repo}`,
221
+ revalidate,
206
222
  });
207
223
  }
208
224
  /**
209
225
  * The commit the configured ref points at right now.
210
226
  *
211
227
  * Resolved afresh for each operation — so a push is visible to the very next call — but only
212
- * once within it, however many files that operation ends up reading.
228
+ * once within it, however many files that operation ends up reading. The request is conditional:
229
+ * while the branch has not moved, GitHub answers 304 and the call costs no rate limit.
213
230
  */
214
231
  resolveRef() {
215
232
  return oncePerOperation(`ref:${this.label}`, async () => {
216
233
  const ref = this.spec.ref ?? "HEAD";
217
- const commit = await this.request(`/repos/${this.spec.owner}/${this.spec.repo}/commits/${encodeURIComponent(ref)}`, `Resolve ${ref} to its current commit`);
234
+ const commit = await this.request(`/repos/${this.spec.owner}/${this.spec.repo}/commits/${encodeURIComponent(ref)}`, `Resolve ${ref} to its current commit`, true);
218
235
  logger.debug(`${this.label} is at ${String(commit.sha).slice(0, 7)}`);
219
236
  return {
220
237
  commitSha: commit.sha,
@@ -294,13 +311,18 @@ export class GitHubCodeApi {
294
311
  }
295
312
  }
296
313
  /**
297
- * A blob's bytes. Blobs are immutable, so this is cached for the life of the process.
314
+ * A blob's bytes, read from the blob store when this machine already downloaded them.
298
315
  * `filePath` names the file in github.log.
299
316
  */
300
317
  getBlob(blobSha, filePath) {
301
318
  return this.cached(this.blobCache, blobSha, async () => {
319
+ const stored = readStoredBlob(blobSha);
320
+ if (stored)
321
+ return stored;
302
322
  const blob = await this.request(`/repos/${this.spec.owner}/${this.spec.repo}/git/blobs/${blobSha}`, filePath ? `Download ${filePath}` : `Download blob ${blobSha.slice(0, 7)}`);
303
- return Buffer.from(blob.content ?? "", blob.encoding === "base64" ? "base64" : "utf-8");
323
+ const bytes = Buffer.from(blob.content ?? "", blob.encoding === "base64" ? "base64" : "utf-8");
324
+ storeBlob(blobSha, bytes);
325
+ return bytes;
304
326
  });
305
327
  }
306
328
  }
package/dist/src/index.js CHANGED
@@ -24,6 +24,7 @@ import { findAndHashSection } from "./core/section-tracker.js";
24
24
  import { getCodeSource, invalidateCodeSources, setCodeSourceProvider } from "./core/code-source.js";
25
25
  import { readCodeSourceConfig } from "./core/project-settings.js";
26
26
  import { LocalState } from "./core/local-state.js";
27
+ import { setBlobStoreDir } from "./core/blob-store.js";
27
28
  import { migrateCodeSources } from "./core/migrate-code-sources.js";
28
29
  import { setCodeRootProvider } from "./core/path-resolver.js";
29
30
  import { getCodeSourceTool, getGitHubTokenStatus, GITHUB_TOKEN_VAR, inspectCodeRoot, inspectRepository, listCodeSources, previewCodeSource, saveCodeSource, searchRepositoriesForInput, setCodeRoot, setCodeRootTool, setCodeSourceTool, } from "./tools/code-sources.js";
@@ -108,6 +109,7 @@ historyStore.init(storageEngine.getDataDir());
108
109
  storageEngine.setHistoryStore(historyStore);
109
110
  const syncEngine = new SyncEngine(storageEngine);
110
111
  const localState = new LocalState(storageEngine.getDataDir());
112
+ setBlobStoreDir(path.join(storageEngine.getDataDir(), "blob-cache"));
111
113
  setCodeRootProvider((project) => localState.getCodeRoot(project));
112
114
  setCodeSourceProvider({
113
115
  codeSourceConfig: (project) => readCodeSourceConfig(storageEngine.getProjectDir(project)),
@@ -1 +1 @@
1
- import{a0 as ln,a1 as an,a2 as y,a3 as tn,a4 as H,a5 as q,a6 as _,a7 as un,a8 as rn,a9 as L,aa as o,ab as B,ac as sn,ad as on,ae as fn}from"./index-DvVxuv5N.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,I,D,v,A,C,a){var O=I-l,i=D-h,n=C-v,d=a-A,u=d*O-n*i;if(!(u*u<y))return u=(n*(h-A)-d*(l-v))/u,[l+u*O,h+u*i]}function W(l,h,I,D,v,A,C){var a=l-I,O=h-D,i=(C?A:-A)/L(a*a+O*O),n=i*O,d=-i*a,u=l+n,s=h+d,f=I+n,c=D+d,F=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,R=m*m+g*g,T=v-A,P=u*c-f*s,S=(g<0?-1:1)*L(fn(0,T*T*R-P*P)),j=(P*g-m*S)/R,z=(-P*m-g*S)/R,w=(P*g+m*S)/R,p=(-P*m+g*S)/R,x=j-F,e=z-t,r=w-F,G=p-t;return x*x+e*e>r*r+G*G&&(j=w,z=p),{cx:j,cy:z,x01:-n,y01:-d,x11:j*(v/T-1),y11:z*(v/T-1)}}function hn(){var l=cn,h=yn,I=B(0),D=null,v=gn,A=dn,C=mn,a=null,O=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-an,c=A.apply(this,arguments)-an,F=un(c-f),t=c>f;if(a||(a=n=O()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(F>tn-y)a.moveTo(s*H(f),s*q(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*H(c),u*q(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,R=f,T=c,P=F,S=F,j=C.apply(this,arguments)/2,z=j>y&&(D?+D.apply(this,arguments):L(u*u+s*s)),w=_(un(s-u)/2,+I.apply(this,arguments)),p=w,x=w,e,r;if(z>y){var G=sn(z/u*q(j)),M=sn(z/s*q(j));(P-=G*2)>y?(G*=t?1:-1,R+=G,T-=G):(P=0,R=T=(f+c)/2),(S-=M*2)>y?(M*=t?1:-1,m+=M,g-=M):(S=0,m=g=(f+c)/2)}var J=s*H(m),K=s*q(m),N=u*H(T),Q=u*q(T);if(w>y){var U=s*H(g),V=s*q(g),X=u*H(R),Y=u*q(R),E;if(F<rn)if(E=pn(J,K,X,Y,U,V,N,Q)){var Z=J-E[0],$=K-E[1],b=U-E[0],k=V-E[1],nn=1/q(on((Z*b+$*k)/(L(Z*Z+$*$)*L(b*b+k*k)))/2),en=L(E[0]*E[0]+E[1]*E[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}S>y?x>y?(e=W(X,Y,J,K,s,x,t),r=W(U,V,N,Q,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(J,K),a.arc(0,0,s,m,g,!t)):a.moveTo(J,K),!(u>y)||!(P>y)?a.lineTo(N,Q):p>y?(e=W(N,Q,U,V,u,-p,t),r=W(J,K,X,Y,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-rn/2;return[H(d)*n,q(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:B(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:B(+n),i):h},i.cornerRadius=function(n){return arguments.length?(I=typeof n=="function"?n:B(+n),i):I},i.padRadius=function(n){return arguments.length?(D=n==null?null:typeof n=="function"?n:B(+n),i):D},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:B(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:B(+n),i):A},i.padAngle=function(n){return arguments.length?(C=typeof n=="function"?n:B(+n),i):C},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};
1
+ import{a0 as ln,a1 as an,a2 as y,a3 as tn,a4 as H,a5 as q,a6 as _,a7 as un,a8 as rn,a9 as L,aa as o,ab as B,ac as sn,ad as on,ae as fn}from"./index-C8LVeAsA.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,I,D,v,A,C,a){var O=I-l,i=D-h,n=C-v,d=a-A,u=d*O-n*i;if(!(u*u<y))return u=(n*(h-A)-d*(l-v))/u,[l+u*O,h+u*i]}function W(l,h,I,D,v,A,C){var a=l-I,O=h-D,i=(C?A:-A)/L(a*a+O*O),n=i*O,d=-i*a,u=l+n,s=h+d,f=I+n,c=D+d,F=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,R=m*m+g*g,T=v-A,P=u*c-f*s,S=(g<0?-1:1)*L(fn(0,T*T*R-P*P)),j=(P*g-m*S)/R,z=(-P*m-g*S)/R,w=(P*g+m*S)/R,p=(-P*m+g*S)/R,x=j-F,e=z-t,r=w-F,G=p-t;return x*x+e*e>r*r+G*G&&(j=w,z=p),{cx:j,cy:z,x01:-n,y01:-d,x11:j*(v/T-1),y11:z*(v/T-1)}}function hn(){var l=cn,h=yn,I=B(0),D=null,v=gn,A=dn,C=mn,a=null,O=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-an,c=A.apply(this,arguments)-an,F=un(c-f),t=c>f;if(a||(a=n=O()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(F>tn-y)a.moveTo(s*H(f),s*q(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*H(c),u*q(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,R=f,T=c,P=F,S=F,j=C.apply(this,arguments)/2,z=j>y&&(D?+D.apply(this,arguments):L(u*u+s*s)),w=_(un(s-u)/2,+I.apply(this,arguments)),p=w,x=w,e,r;if(z>y){var G=sn(z/u*q(j)),M=sn(z/s*q(j));(P-=G*2)>y?(G*=t?1:-1,R+=G,T-=G):(P=0,R=T=(f+c)/2),(S-=M*2)>y?(M*=t?1:-1,m+=M,g-=M):(S=0,m=g=(f+c)/2)}var J=s*H(m),K=s*q(m),N=u*H(T),Q=u*q(T);if(w>y){var U=s*H(g),V=s*q(g),X=u*H(R),Y=u*q(R),E;if(F<rn)if(E=pn(J,K,X,Y,U,V,N,Q)){var Z=J-E[0],$=K-E[1],b=U-E[0],k=V-E[1],nn=1/q(on((Z*b+$*k)/(L(Z*Z+$*$)*L(b*b+k*k)))/2),en=L(E[0]*E[0]+E[1]*E[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}S>y?x>y?(e=W(X,Y,J,K,s,x,t),r=W(U,V,N,Q,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(J,K),a.arc(0,0,s,m,g,!t)):a.moveTo(J,K),!(u>y)||!(P>y)?a.lineTo(N,Q):p>y?(e=W(N,Q,U,V,u,-p,t),r=W(J,K,X,Y,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-rn/2;return[H(d)*n,q(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:B(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:B(+n),i):h},i.cornerRadius=function(n){return arguments.length?(I=typeof n=="function"?n:B(+n),i):I},i.padRadius=function(n){return arguments.length?(D=n==null?null:typeof n=="function"?n:B(+n),i):D},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:B(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:B(+n),i):A},i.padAngle=function(n){return arguments.length?(C=typeof n=="function"?n:B(+n),i):C},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};