@swarmvaultai/engine 1.0.0 → 1.1.0

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/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import {
2
+ LocalWhisperProviderAdapter,
2
3
  PRIMARY_SCHEMA_FILENAME,
3
4
  appendJsonLine,
4
5
  assertProviderCapability,
@@ -23,7 +24,7 @@ import {
23
24
  uniqueBy,
24
25
  writeFileIfChanged,
25
26
  writeJsonFile
26
- } from "./chunk-4MSSM2GH.js";
27
+ } from "./chunk-UQCF65BN.js";
27
28
  import {
28
29
  estimatePageTokens,
29
30
  estimateTokens,
@@ -8379,15 +8380,15 @@ function sourceTypeForNode(node, pagesById) {
8379
8380
  return pagesById.get(node.pageId)?.sourceType;
8380
8381
  }
8381
8382
  function supportingPathDetails(graph, edge) {
8382
- const path32 = shortestGraphPath(graph, edge.source, edge.target);
8383
+ const path33 = shortestGraphPath(graph, edge.source, edge.target);
8383
8384
  const edgesById = new Map(graph.edges.map((item) => [item.id, item]));
8384
- const pathEdges = path32.edgeIds.map((edgeId) => edgesById.get(edgeId)).filter((item) => Boolean(item));
8385
+ const pathEdges = path33.edgeIds.map((edgeId) => edgesById.get(edgeId)).filter((item) => Boolean(item));
8385
8386
  return {
8386
- pathNodeIds: path32.nodeIds,
8387
- pathEdgeIds: path32.edgeIds,
8387
+ pathNodeIds: path33.nodeIds,
8388
+ pathEdgeIds: path33.edgeIds,
8388
8389
  pathRelations: pathEdges.map((item) => item.relation),
8389
8390
  pathEvidenceClasses: pathEdges.map((item) => item.evidenceClass),
8390
- pathSummary: path32.summary
8391
+ pathSummary: path33.summary
8391
8392
  };
8392
8393
  }
8393
8394
  function surpriseScore(edge, graph, pagesById, hyperedgesByNodeId) {
@@ -8456,7 +8457,7 @@ function topSurprisingConnections(graph, pagesById) {
8456
8457
  }).map((edge) => {
8457
8458
  const source = nodesById.get(edge.source);
8458
8459
  const target = nodesById.get(edge.target);
8459
- const path32 = supportingPathDetails(graph, edge);
8460
+ const path33 = supportingPathDetails(graph, edge);
8460
8461
  const scored = surpriseScore(edge, graph, pagesById, hyperedgesByNodeId);
8461
8462
  return {
8462
8463
  id: edge.id,
@@ -8467,11 +8468,11 @@ function topSurprisingConnections(graph, pagesById) {
8467
8468
  relation: edge.relation,
8468
8469
  evidenceClass: edge.evidenceClass,
8469
8470
  confidence: edge.confidence,
8470
- pathNodeIds: path32.pathNodeIds,
8471
- pathEdgeIds: path32.pathEdgeIds,
8472
- pathRelations: path32.pathRelations,
8473
- pathEvidenceClasses: path32.pathEvidenceClasses,
8474
- pathSummary: path32.pathSummary,
8471
+ pathNodeIds: path33.pathNodeIds,
8472
+ pathEdgeIds: path33.pathEdgeIds,
8473
+ pathRelations: path33.pathRelations,
8474
+ pathEvidenceClasses: path33.pathEvidenceClasses,
8475
+ pathSummary: path33.pathSummary,
8475
8476
  why: scored.why,
8476
8477
  explanation: scored.explanation,
8477
8478
  surpriseScore: scored.score
@@ -18310,7 +18311,8 @@ function isSupportedInboxKind(sourceKind) {
18310
18311
  "chat_export",
18311
18312
  "email",
18312
18313
  "calendar",
18313
- "image"
18314
+ "image",
18315
+ "audio"
18314
18316
  ].includes(sourceKind);
18315
18317
  }
18316
18318
  async function ingestInputDetailed(rootDir, input, options) {
@@ -20897,7 +20899,7 @@ async function resolveImageGenerationProvider(rootDir) {
20897
20899
  if (!providerConfig) {
20898
20900
  throw new Error(`No provider configured with id "${preferredProviderId}" for task "imageProvider".`);
20899
20901
  }
20900
- const { createProvider: createProvider2 } = await import("./registry-QAG2ZYH3.js");
20902
+ const { createProvider: createProvider2 } = await import("./registry-GH4O3A7H.js");
20901
20903
  return createProvider2(preferredProviderId, providerConfig, rootDir);
20902
20904
  }
20903
20905
  async function generateOutputArtifacts(rootDir, input) {
@@ -26613,7 +26615,7 @@ async function getWatchStatus(rootDir) {
26613
26615
  }
26614
26616
 
26615
26617
  // src/mcp.ts
26616
- var SERVER_VERSION = "1.0.0";
26618
+ var SERVER_VERSION = "1.1.0";
26617
26619
  async function createMcpServer(rootDir) {
26618
26620
  const server = new McpServer({
26619
26621
  name: "swarmvault",
@@ -27149,6 +27151,155 @@ function asTextResource(uri, text) {
27149
27151
  };
27150
27152
  }
27151
27153
 
27154
+ // src/providers/local-whisper-setup.ts
27155
+ import { createWriteStream, constants as fsConstants } from "fs";
27156
+ import fs25 from "fs/promises";
27157
+ import os3 from "os";
27158
+ import path29 from "path";
27159
+ import { Readable as Readable2 } from "stream";
27160
+ import { pipeline } from "stream/promises";
27161
+ var BINARY_CANDIDATES = ["whisper-cli", "whisper-cpp", "whisper"];
27162
+ var HUGGINGFACE_BASE = "https://huggingface.co/ggerganov/whisper.cpp/resolve/main";
27163
+ var LOCAL_WHISPER_MODEL_SIZES = Object.freeze({
27164
+ "tiny.en": "78 MB",
27165
+ tiny: "78 MB",
27166
+ "base.en": "147 MB",
27167
+ base: "147 MB",
27168
+ "small.en": "488 MB",
27169
+ small: "488 MB",
27170
+ "medium.en": "1.5 GB",
27171
+ medium: "1.5 GB",
27172
+ "large-v3": "3.1 GB",
27173
+ "large-v3-turbo": "1.6 GB"
27174
+ });
27175
+ async function discoverLocalWhisperBinary(options = {}) {
27176
+ const env = options.env ?? process.env;
27177
+ if (env.SWARMVAULT_WHISPER_BINARY) {
27178
+ return {
27179
+ binaryPath: env.SWARMVAULT_WHISPER_BINARY,
27180
+ candidates: [env.SWARMVAULT_WHISPER_BINARY],
27181
+ source: "env"
27182
+ };
27183
+ }
27184
+ const pathValue = env.PATH ?? "";
27185
+ const candidates = [];
27186
+ for (const dir of pathValue.split(path29.delimiter)) {
27187
+ if (!dir) continue;
27188
+ for (const name of BINARY_CANDIDATES) {
27189
+ const full = path29.join(dir, name);
27190
+ candidates.push(full);
27191
+ if (await isExecutable(full)) {
27192
+ return { binaryPath: full, candidates, source: "path" };
27193
+ }
27194
+ }
27195
+ }
27196
+ return { binaryPath: null, candidates, source: "not-found" };
27197
+ }
27198
+ function expectedModelPath(modelName, homeDir) {
27199
+ const home = homeDir ?? os3.homedir();
27200
+ return path29.join(home, ".swarmvault", "models", `ggml-${modelName}.bin`);
27201
+ }
27202
+ function modelDownloadUrl(modelName) {
27203
+ return `${HUGGINGFACE_BASE}/ggml-${modelName}.bin`;
27204
+ }
27205
+ async function downloadWhisperModel(options) {
27206
+ const destPath = expectedModelPath(options.modelName, options.homeDir);
27207
+ await ensureDir(path29.dirname(destPath));
27208
+ const doFetch = options.fetchImpl ?? fetch;
27209
+ const url = modelDownloadUrl(options.modelName);
27210
+ const response = await doFetch(url);
27211
+ if (!response.ok) {
27212
+ throw new Error(`Failed to download ${url}: ${response.status} ${response.statusText}`);
27213
+ }
27214
+ if (!response.body) {
27215
+ throw new Error(`Response body missing for ${url}`);
27216
+ }
27217
+ const totalHeader = response.headers.get("content-length");
27218
+ const totalBytes = totalHeader ? Number.parseInt(totalHeader, 10) : void 0;
27219
+ let downloadedBytes = 0;
27220
+ const webStream = response.body;
27221
+ const source = Readable2.fromWeb(webStream);
27222
+ source.on("data", (chunk) => {
27223
+ downloadedBytes += chunk.length;
27224
+ options.onProgress?.({ downloadedBytes, totalBytes });
27225
+ });
27226
+ const tmpPath = `${destPath}.part`;
27227
+ await pipeline(source, createWriteStream(tmpPath));
27228
+ await fs25.rename(tmpPath, destPath);
27229
+ const stat = await fs25.stat(destPath);
27230
+ return { path: destPath, bytes: stat.size };
27231
+ }
27232
+ async function registerLocalWhisperProvider(options) {
27233
+ const { config, paths } = await loadVaultConfig(options.rootDir);
27234
+ const providerId = options.providerId ?? "local-whisper";
27235
+ const desired = buildProviderEntry(options);
27236
+ const existing = config.providers[providerId];
27237
+ const providerWasAdded = !existing;
27238
+ const providerWasUpdated = !providerWasAdded && !providerEntryMatches(existing, desired);
27239
+ const previousAudioProvider = config.tasks.audioProvider;
27240
+ const shouldSetAudio = options.setAsAudioProvider !== false && (options.setAsAudioProvider === true || !previousAudioProvider);
27241
+ const next = {
27242
+ ...config,
27243
+ providers: {
27244
+ ...config.providers,
27245
+ [providerId]: desired
27246
+ },
27247
+ tasks: {
27248
+ ...config.tasks,
27249
+ audioProvider: shouldSetAudio ? providerId : previousAudioProvider
27250
+ }
27251
+ };
27252
+ await writeJsonFile(paths.configPath, next);
27253
+ return {
27254
+ providerId,
27255
+ configPath: paths.configPath,
27256
+ providerWasAdded,
27257
+ providerWasUpdated,
27258
+ audioProviderSet: shouldSetAudio && previousAudioProvider !== providerId,
27259
+ previousAudioProvider
27260
+ };
27261
+ }
27262
+ function buildProviderEntry(options) {
27263
+ const entry = {
27264
+ type: "local-whisper",
27265
+ model: options.model
27266
+ };
27267
+ if (options.binaryPath) entry.binaryPath = options.binaryPath;
27268
+ if (options.modelPath) entry.modelPath = options.modelPath;
27269
+ if (options.threads !== void 0) entry.threads = options.threads;
27270
+ return entry;
27271
+ }
27272
+ function providerEntryMatches(existing, desired) {
27273
+ return existing.type === desired.type && existing.model === desired.model && existing.binaryPath === desired.binaryPath && existing.modelPath === desired.modelPath && existing.threads === desired.threads;
27274
+ }
27275
+ async function summarizeLocalWhisperSetup(options) {
27276
+ const discovery = await discoverLocalWhisperBinary({ env: options.env });
27277
+ const modelPath = expectedModelPath(options.modelName, options.homeDir);
27278
+ return {
27279
+ binary: {
27280
+ found: discovery.binaryPath !== null,
27281
+ path: discovery.binaryPath,
27282
+ source: discovery.source,
27283
+ installHint: 'Install whisper.cpp \u2014 macOS: "brew install whisper-cpp"; Debian/Ubuntu: "sudo apt install whisper.cpp" (or build from https://github.com/ggerganov/whisper.cpp).'
27284
+ },
27285
+ model: {
27286
+ name: options.modelName,
27287
+ expectedPath: modelPath,
27288
+ exists: await fileExists(modelPath),
27289
+ downloadUrl: modelDownloadUrl(options.modelName),
27290
+ approximateSize: LOCAL_WHISPER_MODEL_SIZES[options.modelName]
27291
+ }
27292
+ };
27293
+ }
27294
+ async function isExecutable(p) {
27295
+ try {
27296
+ await fs25.access(p, fsConstants.X_OK);
27297
+ return true;
27298
+ } catch {
27299
+ return false;
27300
+ }
27301
+ }
27302
+
27152
27303
  // src/providers/openai-compatible-capabilities.ts
27153
27304
  var OPENAI_COMPATIBLE_CAPABILITY_MATRIX = Object.freeze({
27154
27305
  openai: {
@@ -27212,13 +27363,13 @@ async function withCapabilityFallback(provider, capability, run, fallback) {
27212
27363
  }
27213
27364
 
27214
27365
  // src/schedule.ts
27215
- import fs25 from "fs/promises";
27216
- import path29 from "path";
27366
+ import fs26 from "fs/promises";
27367
+ import path30 from "path";
27217
27368
  function scheduleStatePath(schedulesDir, jobId) {
27218
- return path29.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
27369
+ return path30.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
27219
27370
  }
27220
27371
  function scheduleLockPath(schedulesDir, jobId) {
27221
- return path29.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
27372
+ return path30.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
27222
27373
  }
27223
27374
  function parseEveryDuration(value) {
27224
27375
  const match = value.trim().match(/^(\d+)(m|h|d)$/i);
@@ -27321,13 +27472,13 @@ async function acquireJobLease(rootDir, jobId) {
27321
27472
  const { paths } = await loadVaultConfig(rootDir);
27322
27473
  const leasePath = scheduleLockPath(paths.schedulesDir, jobId);
27323
27474
  await ensureDir(paths.schedulesDir);
27324
- const handle = await fs25.open(leasePath, "wx");
27475
+ const handle = await fs26.open(leasePath, "wx");
27325
27476
  await handle.writeFile(`${process.pid}
27326
27477
  ${(/* @__PURE__ */ new Date()).toISOString()}
27327
27478
  `);
27328
27479
  await handle.close();
27329
27480
  return async () => {
27330
- await fs25.rm(leasePath, { force: true });
27481
+ await fs26.rm(leasePath, { force: true });
27331
27482
  };
27332
27483
  }
27333
27484
  async function listSchedules(rootDir) {
@@ -27486,8 +27637,8 @@ async function serveSchedules(rootDir, pollMs = 3e4) {
27486
27637
 
27487
27638
  // src/sources.ts
27488
27639
  import { spawn as spawn2 } from "child_process";
27489
- import fs26 from "fs/promises";
27490
- import path30 from "path";
27640
+ import fs27 from "fs/promises";
27641
+ import path31 from "path";
27491
27642
  import matter14 from "gray-matter";
27492
27643
  import { JSDOM as JSDOM3 } from "jsdom";
27493
27644
  var DEFAULT_CRAWL_MAX_PAGES = 12;
@@ -27533,24 +27684,24 @@ function emptyManagedSourceSyncCounts() {
27533
27684
  };
27534
27685
  }
27535
27686
  function withinRoot2(rootPath, targetPath) {
27536
- const relative = path30.relative(rootPath, targetPath);
27537
- return relative === "" || !relative.startsWith("..") && !path30.isAbsolute(relative);
27687
+ const relative = path31.relative(rootPath, targetPath);
27688
+ return relative === "" || !relative.startsWith("..") && !path31.isAbsolute(relative);
27538
27689
  }
27539
27690
  async function findNearestGitRoot3(startPath) {
27540
- let current = path30.resolve(startPath);
27691
+ let current = path31.resolve(startPath);
27541
27692
  try {
27542
- const stat = await fs26.stat(current);
27693
+ const stat = await fs27.stat(current);
27543
27694
  if (!stat.isDirectory()) {
27544
- current = path30.dirname(current);
27695
+ current = path31.dirname(current);
27545
27696
  }
27546
27697
  } catch {
27547
- current = path30.dirname(current);
27698
+ current = path31.dirname(current);
27548
27699
  }
27549
27700
  while (true) {
27550
- if (await fileExists(path30.join(current, ".git"))) {
27701
+ if (await fileExists(path31.join(current, ".git"))) {
27551
27702
  return current;
27552
27703
  }
27553
- const parent = path30.dirname(current);
27704
+ const parent = path31.dirname(current);
27554
27705
  if (parent === current) {
27555
27706
  return null;
27556
27707
  }
@@ -27624,7 +27775,7 @@ function isAllowedDocsCandidate(candidate, startUrl) {
27624
27775
  if (candidate.origin !== startUrl.origin) {
27625
27776
  return false;
27626
27777
  }
27627
- const extension = path30.extname(candidate.pathname).toLowerCase();
27778
+ const extension = path31.extname(candidate.pathname).toLowerCase();
27628
27779
  if (extension && extension !== ".html" && extension !== ".htm" && extension !== ".md") {
27629
27780
  return false;
27630
27781
  }
@@ -27713,14 +27864,14 @@ function matchesManagedSourceSpec(existing, input) {
27713
27864
  return false;
27714
27865
  }
27715
27866
  if (input.kind === "directory" || input.kind === "file") {
27716
- return path30.resolve(existing.path ?? "") === path30.resolve(input.path);
27867
+ return path31.resolve(existing.path ?? "") === path31.resolve(input.path);
27717
27868
  }
27718
27869
  return (existing.url ?? "") === input.url;
27719
27870
  }
27720
27871
  async function resolveManagedSourceInput(rootDir, input) {
27721
- const absoluteInput = path30.resolve(rootDir, input);
27872
+ const absoluteInput = path31.resolve(rootDir, input);
27722
27873
  if (!(input.startsWith("http://") || input.startsWith("https://"))) {
27723
- const stat = await fs26.stat(absoluteInput).catch(() => null);
27874
+ const stat = await fs27.stat(absoluteInput).catch(() => null);
27724
27875
  if (!stat) {
27725
27876
  throw new Error(`Source not found: ${input}`);
27726
27877
  }
@@ -27728,7 +27879,7 @@ async function resolveManagedSourceInput(rootDir, input) {
27728
27879
  return {
27729
27880
  kind: "file",
27730
27881
  path: absoluteInput,
27731
- title: path30.basename(absoluteInput, path30.extname(absoluteInput)) || absoluteInput
27882
+ title: path31.basename(absoluteInput, path31.extname(absoluteInput)) || absoluteInput
27732
27883
  };
27733
27884
  }
27734
27885
  if (!stat.isDirectory()) {
@@ -27740,7 +27891,7 @@ async function resolveManagedSourceInput(rootDir, input) {
27740
27891
  kind: "directory",
27741
27892
  path: absoluteInput,
27742
27893
  repoRoot,
27743
- title: path30.basename(absoluteInput) || absoluteInput
27894
+ title: path31.basename(absoluteInput) || absoluteInput
27744
27895
  };
27745
27896
  }
27746
27897
  const github = normalizeGitHubRepoRootUrl(input);
@@ -27763,16 +27914,16 @@ async function resolveManagedSourceInput(rootDir, input) {
27763
27914
  };
27764
27915
  }
27765
27916
  function directorySourceIdsFor(manifests, inputPath) {
27766
- return manifests.filter((manifest) => manifest.originalPath && withinRoot2(path30.resolve(inputPath), path30.resolve(manifest.originalPath))).map((manifest) => manifest.sourceId).sort((left, right) => left.localeCompare(right));
27917
+ return manifests.filter((manifest) => manifest.originalPath && withinRoot2(path31.resolve(inputPath), path31.resolve(manifest.originalPath))).map((manifest) => manifest.sourceId).sort((left, right) => left.localeCompare(right));
27767
27918
  }
27768
27919
  function fileSourceIdsFor(manifests, inputPath) {
27769
- const absoluteInput = path30.resolve(inputPath);
27770
- return manifests.filter((manifest) => manifest.originalPath && path30.resolve(manifest.originalPath) === absoluteInput).map((manifest) => manifest.sourceId).sort((left, right) => left.localeCompare(right));
27920
+ const absoluteInput = path31.resolve(inputPath);
27921
+ return manifests.filter((manifest) => manifest.originalPath && path31.resolve(manifest.originalPath) === absoluteInput).map((manifest) => manifest.sourceId).sort((left, right) => left.localeCompare(right));
27771
27922
  }
27772
27923
  async function syncDirectorySource(rootDir, inputPath, repoRoot) {
27773
27924
  const manifestsBefore = await listManifests(rootDir);
27774
27925
  const previousInScope = manifestsBefore.filter(
27775
- (manifest) => manifest.originalPath && withinRoot2(path30.resolve(inputPath), path30.resolve(manifest.originalPath))
27926
+ (manifest) => manifest.originalPath && withinRoot2(path31.resolve(inputPath), path31.resolve(manifest.originalPath))
27776
27927
  );
27777
27928
  const result = await ingestDirectory(rootDir, inputPath, { repoRoot });
27778
27929
  const removed = [];
@@ -27780,7 +27931,7 @@ async function syncDirectorySource(rootDir, inputPath, repoRoot) {
27780
27931
  if (!manifest.originalPath) {
27781
27932
  continue;
27782
27933
  }
27783
- if (await fileExists(path30.resolve(manifest.originalPath))) {
27934
+ if (await fileExists(path31.resolve(manifest.originalPath))) {
27784
27935
  continue;
27785
27936
  }
27786
27937
  const removedManifest = await removeManifestBySourceId(rootDir, manifest.sourceId);
@@ -27790,7 +27941,7 @@ async function syncDirectorySource(rootDir, inputPath, repoRoot) {
27790
27941
  }
27791
27942
  const manifestsAfter = await listManifests(rootDir);
27792
27943
  return {
27793
- title: path30.basename(inputPath) || inputPath,
27944
+ title: path31.basename(inputPath) || inputPath,
27794
27945
  sourceIds: directorySourceIdsFor(manifestsAfter, inputPath),
27795
27946
  counts: {
27796
27947
  scannedCount: result.scannedCount,
@@ -27806,7 +27957,7 @@ async function syncFileSource(rootDir, inputPath) {
27806
27957
  const result = await ingestInputDetailed(rootDir, inputPath);
27807
27958
  const manifestsAfter = await listManifests(rootDir);
27808
27959
  return {
27809
- title: path30.basename(inputPath, path30.extname(inputPath)) || inputPath,
27960
+ title: path31.basename(inputPath, path31.extname(inputPath)) || inputPath,
27810
27961
  sourceIds: fileSourceIdsFor(manifestsAfter, inputPath),
27811
27962
  counts: {
27812
27963
  scannedCount: result.scannedCount,
@@ -27840,8 +27991,8 @@ async function runGitCommand(cwd, args) {
27840
27991
  }
27841
27992
  async function syncGitHubRepoSource(rootDir, entry) {
27842
27993
  const workingDir = await managedSourceWorkingDir(rootDir, entry.id);
27843
- const checkoutDir = path30.join(workingDir, "checkout");
27844
- await fs26.rm(checkoutDir, { recursive: true, force: true });
27994
+ const checkoutDir = path31.join(workingDir, "checkout");
27995
+ await fs27.rm(checkoutDir, { recursive: true, force: true });
27845
27996
  await ensureDir(workingDir);
27846
27997
  if (!entry.url) {
27847
27998
  throw new Error(`Managed source ${entry.id} is missing its repository URL.`);
@@ -27971,7 +28122,7 @@ function scopedNodeIds(graph, sourceIds) {
27971
28122
  async function loadSourceAnalyses(rootDir, sourceIds) {
27972
28123
  const { paths } = await loadVaultConfig(rootDir);
27973
28124
  const analyses = await Promise.all(
27974
- sourceIds.map(async (sourceId) => await readJsonFile(path30.join(paths.analysesDir, `${sourceId}.json`)))
28125
+ sourceIds.map(async (sourceId) => await readJsonFile(path31.join(paths.analysesDir, `${sourceId}.json`)))
27975
28126
  );
27976
28127
  return analyses.filter((analysis) => Boolean(analysis?.sourceId));
27977
28128
  }
@@ -28132,9 +28283,9 @@ async function writeSourceBriefForScope(rootDir, source) {
28132
28283
  confidence: 0.82
28133
28284
  }
28134
28285
  });
28135
- const absolutePath = path30.join(paths.wikiDir, output.page.path);
28136
- await ensureDir(path30.dirname(absolutePath));
28137
- await fs26.writeFile(absolutePath, output.content, "utf8");
28286
+ const absolutePath = path31.join(paths.wikiDir, output.page.path);
28287
+ await ensureDir(path31.dirname(absolutePath));
28288
+ await fs27.writeFile(absolutePath, output.content, "utf8");
28138
28289
  return absolutePath;
28139
28290
  }
28140
28291
  async function writeSourceBrief(rootDir, source) {
@@ -28422,7 +28573,7 @@ function selectGuidedTargetPages(scope, sourcePages, questions) {
28422
28573
  return (matchedTargets.length ? matchedTargets : canonicalPages).slice(0, 6);
28423
28574
  }
28424
28575
  function insightRelativePathForTarget(page, scope) {
28425
- const basename = path30.basename(page.path);
28576
+ const basename = path31.basename(page.path);
28426
28577
  if (page.kind === "concept") {
28427
28578
  return `insights/concepts/${basename}`;
28428
28579
  }
@@ -28649,7 +28800,7 @@ async function stageSourceReviewForScope(rootDir, scope) {
28649
28800
  return {
28650
28801
  sourceId: scope.id,
28651
28802
  pageId: output.page.id,
28652
- reviewPath: path30.join(approval.approvalDir, "wiki", output.page.path),
28803
+ reviewPath: path31.join(approval.approvalDir, "wiki", output.page.path),
28653
28804
  staged: true,
28654
28805
  approvalId: approval.approvalId,
28655
28806
  approvalDir: approval.approvalDir
@@ -28716,7 +28867,7 @@ async function buildSourceSessionSavedPage(rootDir, scope, session) {
28716
28867
  const evidenceState = contradictions.length > 0 ? "conflicting" : session.targetedPagePaths.some(
28717
28868
  (targetPath) => sourcePages.some((page) => page.path === targetPath && page.sourceIds.some((sourceId) => !scope.sourceIds.includes(sourceId)))
28718
28869
  ) ? "reinforcing" : session.targetedPagePaths.length ? "new" : "needs_judgment";
28719
- const relativeBriefPath = session.briefPath && path30.isAbsolute(session.briefPath) ? path30.relative(paths.wikiDir, session.briefPath) : session.briefPath;
28870
+ const relativeBriefPath = session.briefPath && path31.isAbsolute(session.briefPath) ? path31.relative(paths.wikiDir, session.briefPath) : session.briefPath;
28720
28871
  const sessionMarkdown = [
28721
28872
  `# Guided Session: ${scope.title}`,
28722
28873
  "",
@@ -28799,9 +28950,9 @@ async function buildSourceSessionSavedPage(rootDir, scope, session) {
28799
28950
  async function persistSourceSessionPage(rootDir, scope, session) {
28800
28951
  const { paths } = await loadVaultConfig(rootDir);
28801
28952
  const output = await buildSourceSessionSavedPage(rootDir, scope, session);
28802
- const absolutePath = path30.join(paths.wikiDir, output.page.path);
28803
- await ensureDir(path30.dirname(absolutePath));
28804
- await fs26.writeFile(absolutePath, output.content, "utf8");
28953
+ const absolutePath = path31.join(paths.wikiDir, output.page.path);
28954
+ await ensureDir(path31.dirname(absolutePath));
28955
+ await fs27.writeFile(absolutePath, output.content, "utf8");
28805
28956
  return { pageId: output.page.id, sessionPath: absolutePath };
28806
28957
  }
28807
28958
  async function buildGuidedUpdatePages(rootDir, scope, session) {
@@ -28829,8 +28980,8 @@ async function buildGuidedUpdatePages(rootDir, scope, session) {
28829
28980
  targetPages.map(async (targetPage) => {
28830
28981
  const evidenceState = classifyGuidedEvidenceState(scope, targetPage, contradictions);
28831
28982
  const relativePath = useCanonicalTargets && targetPage ? targetPage.path : targetPage ? insightRelativePathForTarget(targetPage, scope) : `insights/topics/${slugify(scope.title)}.md`;
28832
- const absolutePath = path30.join(paths.wikiDir, relativePath);
28833
- const existingContent = await fileExists(absolutePath) ? await fs26.readFile(absolutePath, "utf8") : "";
28983
+ const absolutePath = path31.join(paths.wikiDir, relativePath);
28984
+ const existingContent = await fileExists(absolutePath) ? await fs27.readFile(absolutePath, "utf8") : "";
28834
28985
  const parsed = existingContent ? matter14(existingContent) : { data: {}, content: "" };
28835
28986
  const existingData = parsed.data;
28836
28987
  const existingSourceIds = Array.isArray(existingData.source_ids) ? existingData.source_ids.filter((value) => typeof value === "string") : [];
@@ -29001,8 +29152,8 @@ async function stageSourceGuideForScope(rootDir, scope, options = {}) {
29001
29152
  }
29002
29153
  );
29003
29154
  session.status = "staged";
29004
- session.reviewPath = path30.join(approval.approvalDir, "wiki", reviewOutput.page.path);
29005
- session.guidePath = path30.join(approval.approvalDir, "wiki", guideOutput.page.path);
29155
+ session.reviewPath = path31.join(approval.approvalDir, "wiki", reviewOutput.page.path);
29156
+ session.guidePath = path31.join(approval.approvalDir, "wiki", guideOutput.page.path);
29006
29157
  session.approvalId = approval.approvalId;
29007
29158
  session.approvalDir = approval.approvalDir;
29008
29159
  const persisted = await persistSourceSessionPage(rootDir, scope, session);
@@ -29140,7 +29291,7 @@ async function addManagedSource(rootDir, input, options = {}) {
29140
29291
  const existing = sources.find((candidate) => matchesManagedSourceSpec(candidate, resolved));
29141
29292
  const now = (/* @__PURE__ */ new Date()).toISOString();
29142
29293
  const source = existing ?? {
29143
- id: resolved.kind === "directory" || resolved.kind === "file" ? stableManagedSourceId(resolved.kind, path30.resolve(resolved.path), resolved.title) : stableManagedSourceId(resolved.kind, resolved.url, resolved.title),
29294
+ id: resolved.kind === "directory" || resolved.kind === "file" ? stableManagedSourceId(resolved.kind, path31.resolve(resolved.path), resolved.title) : stableManagedSourceId(resolved.kind, resolved.url, resolved.title),
29144
29295
  kind: resolved.kind,
29145
29296
  title: resolved.title,
29146
29297
  path: resolved.kind === "directory" || resolved.kind === "file" ? resolved.path : void 0,
@@ -29268,7 +29419,7 @@ async function deleteManagedSource(rootDir, id) {
29268
29419
  sources.filter((source) => source.id !== id)
29269
29420
  );
29270
29421
  const workingDir = await managedSourceWorkingDir(rootDir, id);
29271
- await fs26.rm(workingDir, { recursive: true, force: true });
29422
+ await fs27.rm(workingDir, { recursive: true, force: true });
29272
29423
  return { removed: target };
29273
29424
  }
29274
29425
 
@@ -29276,9 +29427,9 @@ async function deleteManagedSource(rootDir, id) {
29276
29427
  import { execFile as execFile2 } from "child_process";
29277
29428
  import { randomUUID } from "crypto";
29278
29429
  import { EventEmitter } from "events";
29279
- import fs27 from "fs/promises";
29430
+ import fs28 from "fs/promises";
29280
29431
  import http from "http";
29281
- import path31 from "path";
29432
+ import path32 from "path";
29282
29433
  import { promisify as promisify2 } from "util";
29283
29434
  import matter15 from "gray-matter";
29284
29435
  import mime2 from "mime-types";
@@ -29432,7 +29583,7 @@ function toViewerLintFindings(findings) {
29432
29583
  var execFileAsync2 = promisify2(execFile2);
29433
29584
  async function isReadableFile(absolutePath) {
29434
29585
  try {
29435
- const stats = await fs27.stat(absolutePath);
29586
+ const stats = await fs28.stat(absolutePath);
29436
29587
  return stats.isFile();
29437
29588
  } catch {
29438
29589
  return false;
@@ -29443,15 +29594,15 @@ async function readViewerPage(rootDir, relativePath) {
29443
29594
  return null;
29444
29595
  }
29445
29596
  const { paths } = await loadVaultConfig(rootDir);
29446
- const absolutePath = path31.resolve(paths.wikiDir, relativePath);
29597
+ const absolutePath = path32.resolve(paths.wikiDir, relativePath);
29447
29598
  if (!isPathWithin(paths.wikiDir, absolutePath) || !await isReadableFile(absolutePath)) {
29448
29599
  return null;
29449
29600
  }
29450
- const raw = await fs27.readFile(absolutePath, "utf8");
29601
+ const raw = await fs28.readFile(absolutePath, "utf8");
29451
29602
  const parsed = matter15(raw);
29452
29603
  return {
29453
29604
  path: relativePath,
29454
- title: typeof parsed.data.title === "string" ? parsed.data.title : path31.basename(relativePath, path31.extname(relativePath)),
29605
+ title: typeof parsed.data.title === "string" ? parsed.data.title : path32.basename(relativePath, path32.extname(relativePath)),
29455
29606
  frontmatter: parsed.data,
29456
29607
  content: parsed.content,
29457
29608
  assets: normalizeOutputAssets(parsed.data.output_assets)
@@ -29462,12 +29613,12 @@ async function readViewerAsset(rootDir, relativePath) {
29462
29613
  return null;
29463
29614
  }
29464
29615
  const { paths } = await loadVaultConfig(rootDir);
29465
- const absolutePath = path31.resolve(paths.wikiDir, relativePath);
29616
+ const absolutePath = path32.resolve(paths.wikiDir, relativePath);
29466
29617
  if (!isPathWithin(paths.wikiDir, absolutePath) || !await isReadableFile(absolutePath)) {
29467
29618
  return null;
29468
29619
  }
29469
29620
  return {
29470
- buffer: await fs27.readFile(absolutePath),
29621
+ buffer: await fs28.readFile(absolutePath),
29471
29622
  mimeType: mime2.lookup(absolutePath) || "application/octet-stream"
29472
29623
  };
29473
29624
  }
@@ -29490,12 +29641,12 @@ async function readJsonBody(request) {
29490
29641
  return JSON.parse(raw);
29491
29642
  }
29492
29643
  async function ensureViewerDist(viewerDistDir) {
29493
- const indexPath = path31.join(viewerDistDir, "index.html");
29644
+ const indexPath = path32.join(viewerDistDir, "index.html");
29494
29645
  if (await fileExists(indexPath)) {
29495
29646
  return;
29496
29647
  }
29497
- const viewerProjectDir = path31.dirname(viewerDistDir);
29498
- if (await fileExists(path31.join(viewerProjectDir, "package.json"))) {
29648
+ const viewerProjectDir = path32.dirname(viewerDistDir);
29649
+ if (await fileExists(path32.join(viewerProjectDir, "package.json"))) {
29499
29650
  await execFileAsync2("pnpm", ["build"], { cwd: viewerProjectDir });
29500
29651
  }
29501
29652
  }
@@ -29518,7 +29669,7 @@ async function startGraphServer(rootDir, port, options = {}) {
29518
29669
  response.end(JSON.stringify({ error: "Graph artifact not found. Run `swarmvault compile` first." }));
29519
29670
  return;
29520
29671
  }
29521
- const reportPath = path31.join(paths.wikiDir, "graph", "report.json");
29672
+ const reportPath = path32.join(paths.wikiDir, "graph", "report.json");
29522
29673
  const report = await readJsonFile(reportPath) ?? null;
29523
29674
  response.writeHead(200, { "content-type": "application/json" });
29524
29675
  response.end(JSON.stringify(buildViewerGraphArtifact(graph, { report, full: options.full ?? false })));
@@ -29582,13 +29733,13 @@ async function startGraphServer(rootDir, port, options = {}) {
29582
29733
  return;
29583
29734
  }
29584
29735
  if (url.pathname === "/api/graph-report") {
29585
- const reportPath = path31.join(paths.wikiDir, "graph", "report.json");
29736
+ const reportPath = path32.join(paths.wikiDir, "graph", "report.json");
29586
29737
  if (!await fileExists(reportPath)) {
29587
29738
  response.writeHead(404, { "content-type": "application/json" });
29588
29739
  response.end(JSON.stringify({ error: "Graph report artifact not found. Run `swarmvault compile` first." }));
29589
29740
  return;
29590
29741
  }
29591
- const body = await fs27.readFile(reportPath, "utf8");
29742
+ const body = await fs28.readFile(reportPath, "utf8");
29592
29743
  response.writeHead(200, { "content-type": "application/json" });
29593
29744
  response.end(body);
29594
29745
  return;
@@ -29689,7 +29840,7 @@ async function startGraphServer(rootDir, port, options = {}) {
29689
29840
  return;
29690
29841
  }
29691
29842
  if (url.pathname === "/api/workspace") {
29692
- const reportPath = path31.join(paths.wikiDir, "graph", "report.json");
29843
+ const reportPath = path32.join(paths.wikiDir, "graph", "report.json");
29693
29844
  const [graphRaw, reportRaw, approvalsRaw, candidatesRaw, watchStatusRaw, lintRaw] = await Promise.all([
29694
29845
  readJsonFile(paths.graphPath).catch(() => null),
29695
29846
  readJsonFile(reportPath).catch(() => null),
@@ -29785,15 +29936,15 @@ async function startGraphServer(rootDir, port, options = {}) {
29785
29936
  return;
29786
29937
  }
29787
29938
  const relativePath = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
29788
- const target = path31.join(paths.viewerDistDir, relativePath);
29789
- const fallback = path31.join(paths.viewerDistDir, "index.html");
29939
+ const target = path32.join(paths.viewerDistDir, relativePath);
29940
+ const fallback = path32.join(paths.viewerDistDir, "index.html");
29790
29941
  const filePath = await fileExists(target) ? target : fallback;
29791
29942
  if (!await fileExists(filePath)) {
29792
29943
  response.writeHead(503, { "content-type": "text/plain" });
29793
29944
  response.end("Viewer build not found. Run `pnpm build` first.");
29794
29945
  return;
29795
29946
  }
29796
- const staticBody = await fs27.readFile(filePath);
29947
+ const staticBody = await fs28.readFile(filePath);
29797
29948
  response.writeHead(200, { "content-type": mime2.lookup(filePath) || "text/plain" });
29798
29949
  response.end(staticBody);
29799
29950
  } catch (error) {
@@ -29833,7 +29984,7 @@ async function exportGraphHtml(rootDir, outputPath, options = {}) {
29833
29984
  throw new Error("Graph artifact not found. Run `swarmvault compile` first.");
29834
29985
  }
29835
29986
  await ensureViewerDist(paths.viewerDistDir);
29836
- const indexPath = path31.join(paths.viewerDistDir, "index.html");
29987
+ const indexPath = path32.join(paths.viewerDistDir, "index.html");
29837
29988
  if (!await fileExists(indexPath)) {
29838
29989
  throw new Error("Viewer build not found. Run `pnpm build` first.");
29839
29990
  }
@@ -29859,17 +30010,17 @@ async function exportGraphHtml(rootDir, outputPath, options = {}) {
29859
30010
  } : null;
29860
30011
  })
29861
30012
  );
29862
- const rawHtml = await fs27.readFile(indexPath, "utf8");
30013
+ const rawHtml = await fs28.readFile(indexPath, "utf8");
29863
30014
  const scriptMatch = rawHtml.match(/<script type="module" crossorigin src="([^"]+)"><\/script>/);
29864
30015
  const styleMatch = rawHtml.match(/<link rel="stylesheet" crossorigin href="([^"]+)">/);
29865
- const scriptPath = scriptMatch?.[1] ? path31.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
29866
- const stylePath = styleMatch?.[1] ? path31.join(paths.viewerDistDir, styleMatch[1].replace(/^\//, "")) : null;
30016
+ const scriptPath = scriptMatch?.[1] ? path32.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
30017
+ const stylePath = styleMatch?.[1] ? path32.join(paths.viewerDistDir, styleMatch[1].replace(/^\//, "")) : null;
29867
30018
  if (!scriptPath || !await fileExists(scriptPath)) {
29868
30019
  throw new Error("Viewer script bundle not found. Run `pnpm build` first.");
29869
30020
  }
29870
- const script = await fs27.readFile(scriptPath, "utf8");
29871
- const style = stylePath && await fileExists(stylePath) ? await fs27.readFile(stylePath, "utf8") : "";
29872
- const report = await readJsonFile(path31.join(paths.wikiDir, "graph", "report.json"));
30021
+ const script = await fs28.readFile(scriptPath, "utf8");
30022
+ const style = stylePath && await fileExists(stylePath) ? await fs28.readFile(stylePath, "utf8") : "";
30023
+ const report = await readJsonFile(path32.join(paths.wikiDir, "graph", "report.json"));
29873
30024
  const embeddedData = JSON.stringify(
29874
30025
  { graph: buildViewerGraphArtifact(graph, { report, full: options.full ?? false }), pages: pages.filter(Boolean), report },
29875
30026
  null,
@@ -29892,9 +30043,9 @@ async function exportGraphHtml(rootDir, outputPath, options = {}) {
29892
30043
  "</html>",
29893
30044
  ""
29894
30045
  ].filter(Boolean).join("\n");
29895
- await fs27.mkdir(path31.dirname(outputPath), { recursive: true });
29896
- await fs27.writeFile(outputPath, html, "utf8");
29897
- return path31.resolve(outputPath);
30046
+ await fs28.mkdir(path32.dirname(outputPath), { recursive: true });
30047
+ await fs28.writeFile(outputPath, html, "utf8");
30048
+ return path32.resolve(outputPath);
29898
30049
  }
29899
30050
  export {
29900
30051
  ALL_MIGRATIONS,
@@ -29905,6 +30056,8 @@ export {
29905
30056
  DEFAULT_REDACTION_PATTERNS,
29906
30057
  DEFAULT_STALE_THRESHOLD,
29907
30058
  LARGE_REPO_NODE_THRESHOLD,
30059
+ LOCAL_WHISPER_MODEL_SIZES,
30060
+ LocalWhisperProviderAdapter,
29908
30061
  OPENAI_COMPATIBLE_CAPABILITY_MATRIX,
29909
30062
  acceptApproval,
29910
30063
  addInput,
@@ -29931,9 +30084,12 @@ export {
29931
30084
  defaultVaultSchema,
29932
30085
  deleteManagedSource,
29933
30086
  detectVaultVersion,
30087
+ discoverLocalWhisperBinary,
30088
+ downloadWhisperModel,
29934
30089
  estimatePageTokens,
29935
30090
  estimateTokens,
29936
30091
  evaluateCandidateForPromotion,
30092
+ expectedModelPath,
29937
30093
  explainGraphVault,
29938
30094
  exploreVault,
29939
30095
  exportGraphFormat,
@@ -29974,6 +30130,7 @@ export {
29974
30130
  loadVaultSchemas,
29975
30131
  lookupPresetCapabilities,
29976
30132
  markSuperseded,
30133
+ modelDownloadUrl,
29977
30134
  pathGraphVault,
29978
30135
  persistDecayFrontmatter,
29979
30136
  planMigration,
@@ -29986,6 +30143,7 @@ export {
29986
30143
  readExtractedText,
29987
30144
  readGraphReport,
29988
30145
  readPage,
30146
+ registerLocalWhisperProvider,
29989
30147
  rejectApproval,
29990
30148
  reloadManagedSources,
29991
30149
  removeWatchedRoot,
@@ -30010,6 +30168,7 @@ export {
30010
30168
  stageGeneratedOutputPages,
30011
30169
  startGraphServer,
30012
30170
  startMcpServer,
30171
+ summarizeLocalWhisperSetup,
30013
30172
  syncTrackedRepos,
30014
30173
  syncTrackedReposForWatch,
30015
30174
  synthesizeHyperedgeHubs,