@swarmvaultai/engine 1.0.1 → 1.2.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,
@@ -7072,6 +7073,202 @@ function enrichGraph(graph, manifests, analyses, extraSimilarityEdges = [], opti
7072
7073
  };
7073
7074
  }
7074
7075
 
7076
+ // src/graph-share.ts
7077
+ function displayVaultName(value) {
7078
+ const trimmed = value?.trim();
7079
+ return trimmed ? trimmed : "this vault";
7080
+ }
7081
+ function sortedFallbackHubs(graph) {
7082
+ return graph.nodes.filter((node) => node.type !== "source").sort(
7083
+ (left, right) => (right.degree ?? 0) - (left.degree ?? 0) || (right.bridgeScore ?? 0) - (left.bridgeScore ?? 0) || left.label.localeCompare(right.label)
7084
+ ).slice(0, 5);
7085
+ }
7086
+ function graphNodeMap(graph) {
7087
+ return new Map(graph.nodes.map((node) => [node.id, node]));
7088
+ }
7089
+ function compactJoin(values, fallback) {
7090
+ const filtered = values.filter(Boolean);
7091
+ if (!filtered.length) {
7092
+ return fallback;
7093
+ }
7094
+ if (filtered.length === 1) {
7095
+ return filtered[0] ?? fallback;
7096
+ }
7097
+ if (filtered.length === 2) {
7098
+ return `${filtered[0]} and ${filtered[1]}`;
7099
+ }
7100
+ return `${filtered.slice(0, -1).join(", ")}, and ${filtered[filtered.length - 1]}`;
7101
+ }
7102
+ function buildShortPost(input) {
7103
+ const topHubLine = input.topHubs.length ? `Top hubs: ${compactJoin(
7104
+ input.topHubs.slice(0, 3).map((node) => node.label),
7105
+ "still emerging"
7106
+ )}.` : "Top hubs are still emerging.";
7107
+ const surprise = input.surprisingConnections[0];
7108
+ const surpriseLine = surprise ? `Most surprising link: ${surprise.sourceLabel} ${surprise.relation} ${surprise.targetLabel}.` : "The graph is ready for its first surprising connection.";
7109
+ return [
7110
+ `I scanned ${input.vaultName} with SwarmVault: ${input.overview.sources} sources -> ${input.overview.pages} wiki pages, ${input.overview.nodes} graph nodes, ${input.overview.edges} edges.`,
7111
+ topHubLine,
7112
+ surpriseLine,
7113
+ "Everything stays local. Try: npm install -g @swarmvaultai/cli && swarmvault scan ./your-repo"
7114
+ ].join("\n");
7115
+ }
7116
+ function buildGraphShareArtifact(input) {
7117
+ const { graph, report } = input;
7118
+ const vaultName = displayVaultName(input.vaultName);
7119
+ const nodesById = graphNodeMap(graph);
7120
+ const fallbackHubs = sortedFallbackHubs(graph);
7121
+ const reportHubs = report?.godNodes.map((node) => {
7122
+ const graphNode = nodesById.get(node.nodeId);
7123
+ return {
7124
+ nodeId: node.nodeId,
7125
+ label: node.label ?? graphNode?.label ?? node.nodeId,
7126
+ degree: node.degree ?? graphNode?.degree
7127
+ };
7128
+ }) ?? [];
7129
+ const fallbackHubHighlights = fallbackHubs.map((node) => ({
7130
+ nodeId: node.id,
7131
+ label: node.label,
7132
+ degree: node.degree
7133
+ }));
7134
+ const topHubs = (reportHubs.length ? reportHubs : fallbackHubHighlights).slice(0, 5);
7135
+ const reportBridgeNodes = report?.bridgeNodes.map((node) => {
7136
+ const graphNode = nodesById.get(node.nodeId);
7137
+ return {
7138
+ nodeId: node.nodeId,
7139
+ label: node.label ?? graphNode?.label ?? node.nodeId,
7140
+ bridgeScore: node.bridgeScore ?? graphNode?.bridgeScore
7141
+ };
7142
+ }) ?? [];
7143
+ const fallbackBridgeNodes = fallbackHubs.map((node) => ({
7144
+ nodeId: node.id,
7145
+ label: node.label,
7146
+ bridgeScore: node.bridgeScore
7147
+ }));
7148
+ const bridgeNodes = (reportBridgeNodes.length ? reportBridgeNodes : fallbackBridgeNodes).slice(0, 3).filter((node) => node.label);
7149
+ const surprisingConnections = (report?.surprisingConnections ?? []).slice(0, 3).map((connection) => {
7150
+ const source = nodesById.get(connection.sourceNodeId);
7151
+ const target = nodesById.get(connection.targetNodeId);
7152
+ return {
7153
+ sourceLabel: source?.label ?? connection.sourceNodeId,
7154
+ targetLabel: target?.label ?? connection.targetNodeId,
7155
+ relation: connection.relation,
7156
+ why: truncate(connection.why || connection.explanation || "Cross-community connection", 180)
7157
+ };
7158
+ });
7159
+ const overview = {
7160
+ sources: graph.sources.length,
7161
+ nodes: report?.overview.nodes ?? graph.nodes.length,
7162
+ edges: report?.overview.edges ?? graph.edges.length,
7163
+ pages: report?.overview.pages ?? graph.pages.length,
7164
+ communities: report?.overview.communities ?? graph.communities?.length ?? 0
7165
+ };
7166
+ const firstPartyOverview = report?.firstPartyOverview ?? {
7167
+ nodes: graph.nodes.filter((node) => node.sourceClass === "first_party").length,
7168
+ edges: graph.edges.length,
7169
+ pages: graph.pages.filter((page) => page.sourceClass === "first_party").length,
7170
+ communities: graph.communities?.length ?? 0
7171
+ };
7172
+ const relatedNodeIds = uniqueBy([...topHubs.map((node) => node.nodeId), ...bridgeNodes.map((node) => node.nodeId)], (value) => value);
7173
+ const relatedPageIds = uniqueBy(
7174
+ relatedNodeIds.map((nodeId) => nodesById.get(nodeId)?.pageId).filter((pageId) => Boolean(pageId)),
7175
+ (value) => value
7176
+ );
7177
+ const relatedSourceIds = uniqueBy(
7178
+ [...graph.sources.map((source) => source.sourceId), ...relatedNodeIds.flatMap((nodeId) => nodesById.get(nodeId)?.sourceIds ?? [])],
7179
+ (value) => value
7180
+ );
7181
+ const knowledgeGaps = report?.knowledgeGaps?.warnings?.length ? report.knowledgeGaps.warnings.slice(0, 3) : report?.warnings?.length ? report.warnings.slice(0, 3) : [];
7182
+ const tagline = `A local-first map of ${vaultName}: ${overview.sources} sources compiled into ${overview.nodes} graph nodes and ${overview.pages} wiki pages.`;
7183
+ const artifact = {
7184
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7185
+ vaultName,
7186
+ tagline,
7187
+ overview,
7188
+ firstPartyOverview,
7189
+ highlights: {
7190
+ topHubs,
7191
+ bridgeNodes,
7192
+ surprisingConnections,
7193
+ suggestedQuestions: (report?.suggestedQuestions ?? []).slice(0, 5)
7194
+ },
7195
+ knowledgeGaps,
7196
+ shortPost: "",
7197
+ relatedNodeIds,
7198
+ relatedPageIds,
7199
+ relatedSourceIds
7200
+ };
7201
+ return {
7202
+ ...artifact,
7203
+ shortPost: buildShortPost({
7204
+ vaultName,
7205
+ overview,
7206
+ topHubs,
7207
+ surprisingConnections
7208
+ })
7209
+ };
7210
+ }
7211
+ function renderGraphShareMarkdown(artifact) {
7212
+ const lines = [
7213
+ "# SwarmVault Share Card",
7214
+ "",
7215
+ `> ${artifact.tagline}`,
7216
+ "",
7217
+ "## Snapshot",
7218
+ "",
7219
+ `- Sources: ${artifact.overview.sources}`,
7220
+ `- Wiki pages: ${artifact.overview.pages}`,
7221
+ `- Graph nodes: ${artifact.overview.nodes}`,
7222
+ `- Graph edges: ${artifact.overview.edges}`,
7223
+ `- Communities: ${artifact.overview.communities}`,
7224
+ `- First-party focus: ${artifact.firstPartyOverview.nodes} nodes, ${artifact.firstPartyOverview.edges} edges, ${artifact.firstPartyOverview.pages} pages`,
7225
+ "",
7226
+ "## Highlights",
7227
+ "",
7228
+ artifact.highlights.topHubs.length ? `- Top hubs: ${compactJoin(
7229
+ artifact.highlights.topHubs.slice(0, 5).map((node) => node.degree ? `${node.label} (${node.degree})` : node.label),
7230
+ "none yet"
7231
+ )}` : "- Top hubs: none yet",
7232
+ artifact.highlights.bridgeNodes.length ? `- Bridge nodes: ${compactJoin(
7233
+ artifact.highlights.bridgeNodes.slice(0, 3).map((node) => node.label),
7234
+ "none yet"
7235
+ )}` : "- Bridge nodes: none yet",
7236
+ ...artifact.highlights.surprisingConnections.length ? artifact.highlights.surprisingConnections.map(
7237
+ (connection) => `- Surprising link: ${connection.sourceLabel} ${connection.relation} ${connection.targetLabel}. ${connection.why}`
7238
+ ) : ["- Surprising link: not enough cross-community evidence yet"],
7239
+ "",
7240
+ "## Ask Next",
7241
+ "",
7242
+ ...artifact.highlights.suggestedQuestions.length ? artifact.highlights.suggestedQuestions.map((question) => `- ${question}`) : ["- Add more sources, run `swarmvault compile`, then ask the graph what changed."],
7243
+ "",
7244
+ "## Share Post",
7245
+ "",
7246
+ "```text",
7247
+ artifact.shortPost,
7248
+ "```",
7249
+ "",
7250
+ "## Reproduce",
7251
+ "",
7252
+ "```bash",
7253
+ "npm install -g @swarmvaultai/cli",
7254
+ "swarmvault scan ./your-repo",
7255
+ "swarmvault graph share --post",
7256
+ "```",
7257
+ ""
7258
+ ];
7259
+ if (artifact.knowledgeGaps.length) {
7260
+ lines.splice(
7261
+ lines.indexOf("## Ask Next"),
7262
+ 0,
7263
+ "## Gaps To Strengthen",
7264
+ "",
7265
+ ...artifact.knowledgeGaps.map((warning) => `- ${warning}`),
7266
+ ""
7267
+ );
7268
+ }
7269
+ return `${lines.join("\n")}`;
7270
+ }
7271
+
7075
7272
  // src/graph-query-core.ts
7076
7273
  var NODE_TYPE_PRIORITY = {
7077
7274
  concept: 6,
@@ -8379,15 +8576,15 @@ function sourceTypeForNode(node, pagesById) {
8379
8576
  return pagesById.get(node.pageId)?.sourceType;
8380
8577
  }
8381
8578
  function supportingPathDetails(graph, edge) {
8382
- const path32 = shortestGraphPath(graph, edge.source, edge.target);
8579
+ const path33 = shortestGraphPath(graph, edge.source, edge.target);
8383
8580
  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));
8581
+ const pathEdges = path33.edgeIds.map((edgeId) => edgesById.get(edgeId)).filter((item) => Boolean(item));
8385
8582
  return {
8386
- pathNodeIds: path32.nodeIds,
8387
- pathEdgeIds: path32.edgeIds,
8583
+ pathNodeIds: path33.nodeIds,
8584
+ pathEdgeIds: path33.edgeIds,
8388
8585
  pathRelations: pathEdges.map((item) => item.relation),
8389
8586
  pathEvidenceClasses: pathEdges.map((item) => item.evidenceClass),
8390
- pathSummary: path32.summary
8587
+ pathSummary: path33.summary
8391
8588
  };
8392
8589
  }
8393
8590
  function surpriseScore(edge, graph, pagesById, hyperedgesByNodeId) {
@@ -8456,7 +8653,7 @@ function topSurprisingConnections(graph, pagesById) {
8456
8653
  }).map((edge) => {
8457
8654
  const source = nodesById.get(edge.source);
8458
8655
  const target = nodesById.get(edge.target);
8459
- const path32 = supportingPathDetails(graph, edge);
8656
+ const path33 = supportingPathDetails(graph, edge);
8460
8657
  const scored = surpriseScore(edge, graph, pagesById, hyperedgesByNodeId);
8461
8658
  return {
8462
8659
  id: edge.id,
@@ -8467,11 +8664,11 @@ function topSurprisingConnections(graph, pagesById) {
8467
8664
  relation: edge.relation,
8468
8665
  evidenceClass: edge.evidenceClass,
8469
8666
  confidence: edge.confidence,
8470
- pathNodeIds: path32.pathNodeIds,
8471
- pathEdgeIds: path32.pathEdgeIds,
8472
- pathRelations: path32.pathRelations,
8473
- pathEvidenceClasses: path32.pathEvidenceClasses,
8474
- pathSummary: path32.pathSummary,
8667
+ pathNodeIds: path33.pathNodeIds,
8668
+ pathEdgeIds: path33.pathEdgeIds,
8669
+ pathRelations: path33.pathRelations,
8670
+ pathEvidenceClasses: path33.pathEvidenceClasses,
8671
+ pathSummary: path33.pathSummary,
8475
8672
  why: scored.why,
8476
8673
  explanation: scored.explanation,
8477
8674
  surpriseScore: scored.score
@@ -8906,6 +9103,65 @@ function buildGraphReportPage(input) {
8906
9103
  content: matter2.stringify(body, frontmatter)
8907
9104
  };
8908
9105
  }
9106
+ function buildGraphSharePage(input) {
9107
+ const pageId = "graph:share-card";
9108
+ const pathValue = "graph/share-card.md";
9109
+ const artifact = buildGraphShareArtifact({
9110
+ graph: input.graph,
9111
+ report: input.report,
9112
+ vaultName: input.vaultName
9113
+ });
9114
+ const frontmatter = {
9115
+ page_id: pageId,
9116
+ kind: "graph_report",
9117
+ cssclasses: cssclassesFor("graph_report"),
9118
+ title: "Share Card",
9119
+ tags: ["graph", "share"],
9120
+ source_ids: artifact.relatedSourceIds,
9121
+ project_ids: [],
9122
+ node_ids: artifact.relatedNodeIds,
9123
+ freshness: "fresh",
9124
+ status: input.metadata.status,
9125
+ confidence: input.metadata.confidence,
9126
+ created_at: input.metadata.createdAt,
9127
+ updated_at: input.metadata.updatedAt,
9128
+ compiled_from: input.metadata.compiledFrom,
9129
+ managed_by: input.metadata.managedBy,
9130
+ backlinks: [],
9131
+ schema_hash: input.schemaHash,
9132
+ source_hashes: {},
9133
+ source_semantic_hashes: {},
9134
+ related_page_ids: artifact.relatedPageIds,
9135
+ related_node_ids: artifact.relatedNodeIds,
9136
+ related_source_ids: artifact.relatedSourceIds
9137
+ };
9138
+ return {
9139
+ page: {
9140
+ id: pageId,
9141
+ path: pathValue,
9142
+ title: "Share Card",
9143
+ kind: "graph_report",
9144
+ sourceIds: artifact.relatedSourceIds,
9145
+ projectIds: [],
9146
+ nodeIds: artifact.relatedNodeIds,
9147
+ freshness: "fresh",
9148
+ status: input.metadata.status,
9149
+ confidence: input.metadata.confidence,
9150
+ backlinks: [],
9151
+ schemaHash: input.schemaHash,
9152
+ sourceHashes: {},
9153
+ sourceSemanticHashes: {},
9154
+ relatedPageIds: artifact.relatedPageIds,
9155
+ relatedNodeIds: artifact.relatedNodeIds,
9156
+ relatedSourceIds: artifact.relatedSourceIds,
9157
+ createdAt: input.metadata.createdAt,
9158
+ updatedAt: input.metadata.updatedAt,
9159
+ compiledFrom: input.metadata.compiledFrom,
9160
+ managedBy: input.metadata.managedBy
9161
+ },
9162
+ content: matter2.stringify(renderGraphShareMarkdown(artifact), frontmatter)
9163
+ };
9164
+ }
8909
9165
  function buildCommunitySummaryPage(input) {
8910
9166
  const pageId = `graph:${input.community.id}`;
8911
9167
  const pathValue = communityPagePath(input.community.id);
@@ -18310,7 +18566,8 @@ function isSupportedInboxKind(sourceKind) {
18310
18566
  "chat_export",
18311
18567
  "email",
18312
18568
  "calendar",
18313
- "image"
18569
+ "image",
18570
+ "audio"
18314
18571
  ].includes(sourceKind);
18315
18572
  }
18316
18573
  async function ingestInputDetailed(rootDir, input, options) {
@@ -20897,7 +21154,7 @@ async function resolveImageGenerationProvider(rootDir) {
20897
21154
  if (!providerConfig) {
20898
21155
  throw new Error(`No provider configured with id "${preferredProviderId}" for task "imageProvider".`);
20899
21156
  }
20900
- const { createProvider: createProvider2 } = await import("./registry-QAG2ZYH3.js");
21157
+ const { createProvider: createProvider2 } = await import("./registry-GH4O3A7H.js");
20901
21158
  return createProvider2(preferredProviderId, providerConfig, rootDir);
20902
21159
  }
20903
21160
  async function generateOutputArtifacts(rootDir, input) {
@@ -22639,8 +22896,23 @@ async function buildGraphOrientationPages(graph, paths, schemaHash, previousComp
22639
22896
  report
22640
22897
  })
22641
22898
  );
22899
+ const shareRecord = await buildManagedGraphPage(
22900
+ path26.join(paths.wikiDir, "graph", "share-card.md"),
22901
+ {
22902
+ managedBy: "system",
22903
+ compiledFrom: uniqueStrings4(graph.pages.flatMap((page) => page.sourceIds)),
22904
+ confidence: 1
22905
+ },
22906
+ (metadata) => buildGraphSharePage({
22907
+ graph,
22908
+ schemaHash,
22909
+ metadata,
22910
+ report,
22911
+ vaultName: path26.basename(paths.rootDir)
22912
+ })
22913
+ );
22642
22914
  return {
22643
- records: [reportRecord, ...communityRecords],
22915
+ records: [reportRecord, shareRecord, ...communityRecords],
22644
22916
  report
22645
22917
  };
22646
22918
  }
@@ -26613,7 +26885,7 @@ async function getWatchStatus(rootDir) {
26613
26885
  }
26614
26886
 
26615
26887
  // src/mcp.ts
26616
- var SERVER_VERSION = "1.0.1";
26888
+ var SERVER_VERSION = "1.2.0";
26617
26889
  async function createMcpServer(rootDir) {
26618
26890
  const server = new McpServer({
26619
26891
  name: "swarmvault",
@@ -27149,6 +27421,155 @@ function asTextResource(uri, text) {
27149
27421
  };
27150
27422
  }
27151
27423
 
27424
+ // src/providers/local-whisper-setup.ts
27425
+ import { createWriteStream, constants as fsConstants } from "fs";
27426
+ import fs25 from "fs/promises";
27427
+ import os3 from "os";
27428
+ import path29 from "path";
27429
+ import { Readable as Readable2 } from "stream";
27430
+ import { pipeline } from "stream/promises";
27431
+ var BINARY_CANDIDATES = ["whisper-cli", "whisper-cpp", "whisper"];
27432
+ var HUGGINGFACE_BASE = "https://huggingface.co/ggerganov/whisper.cpp/resolve/main";
27433
+ var LOCAL_WHISPER_MODEL_SIZES = Object.freeze({
27434
+ "tiny.en": "78 MB",
27435
+ tiny: "78 MB",
27436
+ "base.en": "147 MB",
27437
+ base: "147 MB",
27438
+ "small.en": "488 MB",
27439
+ small: "488 MB",
27440
+ "medium.en": "1.5 GB",
27441
+ medium: "1.5 GB",
27442
+ "large-v3": "3.1 GB",
27443
+ "large-v3-turbo": "1.6 GB"
27444
+ });
27445
+ async function discoverLocalWhisperBinary(options = {}) {
27446
+ const env = options.env ?? process.env;
27447
+ if (env.SWARMVAULT_WHISPER_BINARY) {
27448
+ return {
27449
+ binaryPath: env.SWARMVAULT_WHISPER_BINARY,
27450
+ candidates: [env.SWARMVAULT_WHISPER_BINARY],
27451
+ source: "env"
27452
+ };
27453
+ }
27454
+ const pathValue = env.PATH ?? "";
27455
+ const candidates = [];
27456
+ for (const dir of pathValue.split(path29.delimiter)) {
27457
+ if (!dir) continue;
27458
+ for (const name of BINARY_CANDIDATES) {
27459
+ const full = path29.join(dir, name);
27460
+ candidates.push(full);
27461
+ if (await isExecutable(full)) {
27462
+ return { binaryPath: full, candidates, source: "path" };
27463
+ }
27464
+ }
27465
+ }
27466
+ return { binaryPath: null, candidates, source: "not-found" };
27467
+ }
27468
+ function expectedModelPath(modelName, homeDir) {
27469
+ const home = homeDir ?? os3.homedir();
27470
+ return path29.join(home, ".swarmvault", "models", `ggml-${modelName}.bin`);
27471
+ }
27472
+ function modelDownloadUrl(modelName) {
27473
+ return `${HUGGINGFACE_BASE}/ggml-${modelName}.bin`;
27474
+ }
27475
+ async function downloadWhisperModel(options) {
27476
+ const destPath = expectedModelPath(options.modelName, options.homeDir);
27477
+ await ensureDir(path29.dirname(destPath));
27478
+ const doFetch = options.fetchImpl ?? fetch;
27479
+ const url = modelDownloadUrl(options.modelName);
27480
+ const response = await doFetch(url);
27481
+ if (!response.ok) {
27482
+ throw new Error(`Failed to download ${url}: ${response.status} ${response.statusText}`);
27483
+ }
27484
+ if (!response.body) {
27485
+ throw new Error(`Response body missing for ${url}`);
27486
+ }
27487
+ const totalHeader = response.headers.get("content-length");
27488
+ const totalBytes = totalHeader ? Number.parseInt(totalHeader, 10) : void 0;
27489
+ let downloadedBytes = 0;
27490
+ const webStream = response.body;
27491
+ const source = Readable2.fromWeb(webStream);
27492
+ source.on("data", (chunk) => {
27493
+ downloadedBytes += chunk.length;
27494
+ options.onProgress?.({ downloadedBytes, totalBytes });
27495
+ });
27496
+ const tmpPath = `${destPath}.part`;
27497
+ await pipeline(source, createWriteStream(tmpPath));
27498
+ await fs25.rename(tmpPath, destPath);
27499
+ const stat = await fs25.stat(destPath);
27500
+ return { path: destPath, bytes: stat.size };
27501
+ }
27502
+ async function registerLocalWhisperProvider(options) {
27503
+ const { config, paths } = await loadVaultConfig(options.rootDir);
27504
+ const providerId = options.providerId ?? "local-whisper";
27505
+ const desired = buildProviderEntry(options);
27506
+ const existing = config.providers[providerId];
27507
+ const providerWasAdded = !existing;
27508
+ const providerWasUpdated = !providerWasAdded && !providerEntryMatches(existing, desired);
27509
+ const previousAudioProvider = config.tasks.audioProvider;
27510
+ const shouldSetAudio = options.setAsAudioProvider !== false && (options.setAsAudioProvider === true || !previousAudioProvider);
27511
+ const next = {
27512
+ ...config,
27513
+ providers: {
27514
+ ...config.providers,
27515
+ [providerId]: desired
27516
+ },
27517
+ tasks: {
27518
+ ...config.tasks,
27519
+ audioProvider: shouldSetAudio ? providerId : previousAudioProvider
27520
+ }
27521
+ };
27522
+ await writeJsonFile(paths.configPath, next);
27523
+ return {
27524
+ providerId,
27525
+ configPath: paths.configPath,
27526
+ providerWasAdded,
27527
+ providerWasUpdated,
27528
+ audioProviderSet: shouldSetAudio && previousAudioProvider !== providerId,
27529
+ previousAudioProvider
27530
+ };
27531
+ }
27532
+ function buildProviderEntry(options) {
27533
+ const entry = {
27534
+ type: "local-whisper",
27535
+ model: options.model
27536
+ };
27537
+ if (options.binaryPath) entry.binaryPath = options.binaryPath;
27538
+ if (options.modelPath) entry.modelPath = options.modelPath;
27539
+ if (options.threads !== void 0) entry.threads = options.threads;
27540
+ return entry;
27541
+ }
27542
+ function providerEntryMatches(existing, desired) {
27543
+ return existing.type === desired.type && existing.model === desired.model && existing.binaryPath === desired.binaryPath && existing.modelPath === desired.modelPath && existing.threads === desired.threads;
27544
+ }
27545
+ async function summarizeLocalWhisperSetup(options) {
27546
+ const discovery = await discoverLocalWhisperBinary({ env: options.env });
27547
+ const modelPath = expectedModelPath(options.modelName, options.homeDir);
27548
+ return {
27549
+ binary: {
27550
+ found: discovery.binaryPath !== null,
27551
+ path: discovery.binaryPath,
27552
+ source: discovery.source,
27553
+ 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).'
27554
+ },
27555
+ model: {
27556
+ name: options.modelName,
27557
+ expectedPath: modelPath,
27558
+ exists: await fileExists(modelPath),
27559
+ downloadUrl: modelDownloadUrl(options.modelName),
27560
+ approximateSize: LOCAL_WHISPER_MODEL_SIZES[options.modelName]
27561
+ }
27562
+ };
27563
+ }
27564
+ async function isExecutable(p) {
27565
+ try {
27566
+ await fs25.access(p, fsConstants.X_OK);
27567
+ return true;
27568
+ } catch {
27569
+ return false;
27570
+ }
27571
+ }
27572
+
27152
27573
  // src/providers/openai-compatible-capabilities.ts
27153
27574
  var OPENAI_COMPATIBLE_CAPABILITY_MATRIX = Object.freeze({
27154
27575
  openai: {
@@ -27212,13 +27633,13 @@ async function withCapabilityFallback(provider, capability, run, fallback) {
27212
27633
  }
27213
27634
 
27214
27635
  // src/schedule.ts
27215
- import fs25 from "fs/promises";
27216
- import path29 from "path";
27636
+ import fs26 from "fs/promises";
27637
+ import path30 from "path";
27217
27638
  function scheduleStatePath(schedulesDir, jobId) {
27218
- return path29.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
27639
+ return path30.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
27219
27640
  }
27220
27641
  function scheduleLockPath(schedulesDir, jobId) {
27221
- return path29.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
27642
+ return path30.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
27222
27643
  }
27223
27644
  function parseEveryDuration(value) {
27224
27645
  const match = value.trim().match(/^(\d+)(m|h|d)$/i);
@@ -27321,13 +27742,13 @@ async function acquireJobLease(rootDir, jobId) {
27321
27742
  const { paths } = await loadVaultConfig(rootDir);
27322
27743
  const leasePath = scheduleLockPath(paths.schedulesDir, jobId);
27323
27744
  await ensureDir(paths.schedulesDir);
27324
- const handle = await fs25.open(leasePath, "wx");
27745
+ const handle = await fs26.open(leasePath, "wx");
27325
27746
  await handle.writeFile(`${process.pid}
27326
27747
  ${(/* @__PURE__ */ new Date()).toISOString()}
27327
27748
  `);
27328
27749
  await handle.close();
27329
27750
  return async () => {
27330
- await fs25.rm(leasePath, { force: true });
27751
+ await fs26.rm(leasePath, { force: true });
27331
27752
  };
27332
27753
  }
27333
27754
  async function listSchedules(rootDir) {
@@ -27486,8 +27907,8 @@ async function serveSchedules(rootDir, pollMs = 3e4) {
27486
27907
 
27487
27908
  // src/sources.ts
27488
27909
  import { spawn as spawn2 } from "child_process";
27489
- import fs26 from "fs/promises";
27490
- import path30 from "path";
27910
+ import fs27 from "fs/promises";
27911
+ import path31 from "path";
27491
27912
  import matter14 from "gray-matter";
27492
27913
  import { JSDOM as JSDOM3 } from "jsdom";
27493
27914
  var DEFAULT_CRAWL_MAX_PAGES = 12;
@@ -27533,24 +27954,24 @@ function emptyManagedSourceSyncCounts() {
27533
27954
  };
27534
27955
  }
27535
27956
  function withinRoot2(rootPath, targetPath) {
27536
- const relative = path30.relative(rootPath, targetPath);
27537
- return relative === "" || !relative.startsWith("..") && !path30.isAbsolute(relative);
27957
+ const relative = path31.relative(rootPath, targetPath);
27958
+ return relative === "" || !relative.startsWith("..") && !path31.isAbsolute(relative);
27538
27959
  }
27539
27960
  async function findNearestGitRoot3(startPath) {
27540
- let current = path30.resolve(startPath);
27961
+ let current = path31.resolve(startPath);
27541
27962
  try {
27542
- const stat = await fs26.stat(current);
27963
+ const stat = await fs27.stat(current);
27543
27964
  if (!stat.isDirectory()) {
27544
- current = path30.dirname(current);
27965
+ current = path31.dirname(current);
27545
27966
  }
27546
27967
  } catch {
27547
- current = path30.dirname(current);
27968
+ current = path31.dirname(current);
27548
27969
  }
27549
27970
  while (true) {
27550
- if (await fileExists(path30.join(current, ".git"))) {
27971
+ if (await fileExists(path31.join(current, ".git"))) {
27551
27972
  return current;
27552
27973
  }
27553
- const parent = path30.dirname(current);
27974
+ const parent = path31.dirname(current);
27554
27975
  if (parent === current) {
27555
27976
  return null;
27556
27977
  }
@@ -27624,7 +28045,7 @@ function isAllowedDocsCandidate(candidate, startUrl) {
27624
28045
  if (candidate.origin !== startUrl.origin) {
27625
28046
  return false;
27626
28047
  }
27627
- const extension = path30.extname(candidate.pathname).toLowerCase();
28048
+ const extension = path31.extname(candidate.pathname).toLowerCase();
27628
28049
  if (extension && extension !== ".html" && extension !== ".htm" && extension !== ".md") {
27629
28050
  return false;
27630
28051
  }
@@ -27713,14 +28134,14 @@ function matchesManagedSourceSpec(existing, input) {
27713
28134
  return false;
27714
28135
  }
27715
28136
  if (input.kind === "directory" || input.kind === "file") {
27716
- return path30.resolve(existing.path ?? "") === path30.resolve(input.path);
28137
+ return path31.resolve(existing.path ?? "") === path31.resolve(input.path);
27717
28138
  }
27718
28139
  return (existing.url ?? "") === input.url;
27719
28140
  }
27720
28141
  async function resolveManagedSourceInput(rootDir, input) {
27721
- const absoluteInput = path30.resolve(rootDir, input);
28142
+ const absoluteInput = path31.resolve(rootDir, input);
27722
28143
  if (!(input.startsWith("http://") || input.startsWith("https://"))) {
27723
- const stat = await fs26.stat(absoluteInput).catch(() => null);
28144
+ const stat = await fs27.stat(absoluteInput).catch(() => null);
27724
28145
  if (!stat) {
27725
28146
  throw new Error(`Source not found: ${input}`);
27726
28147
  }
@@ -27728,7 +28149,7 @@ async function resolveManagedSourceInput(rootDir, input) {
27728
28149
  return {
27729
28150
  kind: "file",
27730
28151
  path: absoluteInput,
27731
- title: path30.basename(absoluteInput, path30.extname(absoluteInput)) || absoluteInput
28152
+ title: path31.basename(absoluteInput, path31.extname(absoluteInput)) || absoluteInput
27732
28153
  };
27733
28154
  }
27734
28155
  if (!stat.isDirectory()) {
@@ -27740,7 +28161,7 @@ async function resolveManagedSourceInput(rootDir, input) {
27740
28161
  kind: "directory",
27741
28162
  path: absoluteInput,
27742
28163
  repoRoot,
27743
- title: path30.basename(absoluteInput) || absoluteInput
28164
+ title: path31.basename(absoluteInput) || absoluteInput
27744
28165
  };
27745
28166
  }
27746
28167
  const github = normalizeGitHubRepoRootUrl(input);
@@ -27763,16 +28184,16 @@ async function resolveManagedSourceInput(rootDir, input) {
27763
28184
  };
27764
28185
  }
27765
28186
  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));
28187
+ 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
28188
  }
27768
28189
  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));
28190
+ const absoluteInput = path31.resolve(inputPath);
28191
+ return manifests.filter((manifest) => manifest.originalPath && path31.resolve(manifest.originalPath) === absoluteInput).map((manifest) => manifest.sourceId).sort((left, right) => left.localeCompare(right));
27771
28192
  }
27772
28193
  async function syncDirectorySource(rootDir, inputPath, repoRoot) {
27773
28194
  const manifestsBefore = await listManifests(rootDir);
27774
28195
  const previousInScope = manifestsBefore.filter(
27775
- (manifest) => manifest.originalPath && withinRoot2(path30.resolve(inputPath), path30.resolve(manifest.originalPath))
28196
+ (manifest) => manifest.originalPath && withinRoot2(path31.resolve(inputPath), path31.resolve(manifest.originalPath))
27776
28197
  );
27777
28198
  const result = await ingestDirectory(rootDir, inputPath, { repoRoot });
27778
28199
  const removed = [];
@@ -27780,7 +28201,7 @@ async function syncDirectorySource(rootDir, inputPath, repoRoot) {
27780
28201
  if (!manifest.originalPath) {
27781
28202
  continue;
27782
28203
  }
27783
- if (await fileExists(path30.resolve(manifest.originalPath))) {
28204
+ if (await fileExists(path31.resolve(manifest.originalPath))) {
27784
28205
  continue;
27785
28206
  }
27786
28207
  const removedManifest = await removeManifestBySourceId(rootDir, manifest.sourceId);
@@ -27790,7 +28211,7 @@ async function syncDirectorySource(rootDir, inputPath, repoRoot) {
27790
28211
  }
27791
28212
  const manifestsAfter = await listManifests(rootDir);
27792
28213
  return {
27793
- title: path30.basename(inputPath) || inputPath,
28214
+ title: path31.basename(inputPath) || inputPath,
27794
28215
  sourceIds: directorySourceIdsFor(manifestsAfter, inputPath),
27795
28216
  counts: {
27796
28217
  scannedCount: result.scannedCount,
@@ -27806,7 +28227,7 @@ async function syncFileSource(rootDir, inputPath) {
27806
28227
  const result = await ingestInputDetailed(rootDir, inputPath);
27807
28228
  const manifestsAfter = await listManifests(rootDir);
27808
28229
  return {
27809
- title: path30.basename(inputPath, path30.extname(inputPath)) || inputPath,
28230
+ title: path31.basename(inputPath, path31.extname(inputPath)) || inputPath,
27810
28231
  sourceIds: fileSourceIdsFor(manifestsAfter, inputPath),
27811
28232
  counts: {
27812
28233
  scannedCount: result.scannedCount,
@@ -27840,8 +28261,8 @@ async function runGitCommand(cwd, args) {
27840
28261
  }
27841
28262
  async function syncGitHubRepoSource(rootDir, entry) {
27842
28263
  const workingDir = await managedSourceWorkingDir(rootDir, entry.id);
27843
- const checkoutDir = path30.join(workingDir, "checkout");
27844
- await fs26.rm(checkoutDir, { recursive: true, force: true });
28264
+ const checkoutDir = path31.join(workingDir, "checkout");
28265
+ await fs27.rm(checkoutDir, { recursive: true, force: true });
27845
28266
  await ensureDir(workingDir);
27846
28267
  if (!entry.url) {
27847
28268
  throw new Error(`Managed source ${entry.id} is missing its repository URL.`);
@@ -27971,7 +28392,7 @@ function scopedNodeIds(graph, sourceIds) {
27971
28392
  async function loadSourceAnalyses(rootDir, sourceIds) {
27972
28393
  const { paths } = await loadVaultConfig(rootDir);
27973
28394
  const analyses = await Promise.all(
27974
- sourceIds.map(async (sourceId) => await readJsonFile(path30.join(paths.analysesDir, `${sourceId}.json`)))
28395
+ sourceIds.map(async (sourceId) => await readJsonFile(path31.join(paths.analysesDir, `${sourceId}.json`)))
27975
28396
  );
27976
28397
  return analyses.filter((analysis) => Boolean(analysis?.sourceId));
27977
28398
  }
@@ -28132,9 +28553,9 @@ async function writeSourceBriefForScope(rootDir, source) {
28132
28553
  confidence: 0.82
28133
28554
  }
28134
28555
  });
28135
- const absolutePath = path30.join(paths.wikiDir, output.page.path);
28136
- await ensureDir(path30.dirname(absolutePath));
28137
- await fs26.writeFile(absolutePath, output.content, "utf8");
28556
+ const absolutePath = path31.join(paths.wikiDir, output.page.path);
28557
+ await ensureDir(path31.dirname(absolutePath));
28558
+ await fs27.writeFile(absolutePath, output.content, "utf8");
28138
28559
  return absolutePath;
28139
28560
  }
28140
28561
  async function writeSourceBrief(rootDir, source) {
@@ -28422,7 +28843,7 @@ function selectGuidedTargetPages(scope, sourcePages, questions) {
28422
28843
  return (matchedTargets.length ? matchedTargets : canonicalPages).slice(0, 6);
28423
28844
  }
28424
28845
  function insightRelativePathForTarget(page, scope) {
28425
- const basename = path30.basename(page.path);
28846
+ const basename = path31.basename(page.path);
28426
28847
  if (page.kind === "concept") {
28427
28848
  return `insights/concepts/${basename}`;
28428
28849
  }
@@ -28649,7 +29070,7 @@ async function stageSourceReviewForScope(rootDir, scope) {
28649
29070
  return {
28650
29071
  sourceId: scope.id,
28651
29072
  pageId: output.page.id,
28652
- reviewPath: path30.join(approval.approvalDir, "wiki", output.page.path),
29073
+ reviewPath: path31.join(approval.approvalDir, "wiki", output.page.path),
28653
29074
  staged: true,
28654
29075
  approvalId: approval.approvalId,
28655
29076
  approvalDir: approval.approvalDir
@@ -28716,7 +29137,7 @@ async function buildSourceSessionSavedPage(rootDir, scope, session) {
28716
29137
  const evidenceState = contradictions.length > 0 ? "conflicting" : session.targetedPagePaths.some(
28717
29138
  (targetPath) => sourcePages.some((page) => page.path === targetPath && page.sourceIds.some((sourceId) => !scope.sourceIds.includes(sourceId)))
28718
29139
  ) ? "reinforcing" : session.targetedPagePaths.length ? "new" : "needs_judgment";
28719
- const relativeBriefPath = session.briefPath && path30.isAbsolute(session.briefPath) ? path30.relative(paths.wikiDir, session.briefPath) : session.briefPath;
29140
+ const relativeBriefPath = session.briefPath && path31.isAbsolute(session.briefPath) ? path31.relative(paths.wikiDir, session.briefPath) : session.briefPath;
28720
29141
  const sessionMarkdown = [
28721
29142
  `# Guided Session: ${scope.title}`,
28722
29143
  "",
@@ -28799,9 +29220,9 @@ async function buildSourceSessionSavedPage(rootDir, scope, session) {
28799
29220
  async function persistSourceSessionPage(rootDir, scope, session) {
28800
29221
  const { paths } = await loadVaultConfig(rootDir);
28801
29222
  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");
29223
+ const absolutePath = path31.join(paths.wikiDir, output.page.path);
29224
+ await ensureDir(path31.dirname(absolutePath));
29225
+ await fs27.writeFile(absolutePath, output.content, "utf8");
28805
29226
  return { pageId: output.page.id, sessionPath: absolutePath };
28806
29227
  }
28807
29228
  async function buildGuidedUpdatePages(rootDir, scope, session) {
@@ -28829,8 +29250,8 @@ async function buildGuidedUpdatePages(rootDir, scope, session) {
28829
29250
  targetPages.map(async (targetPage) => {
28830
29251
  const evidenceState = classifyGuidedEvidenceState(scope, targetPage, contradictions);
28831
29252
  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") : "";
29253
+ const absolutePath = path31.join(paths.wikiDir, relativePath);
29254
+ const existingContent = await fileExists(absolutePath) ? await fs27.readFile(absolutePath, "utf8") : "";
28834
29255
  const parsed = existingContent ? matter14(existingContent) : { data: {}, content: "" };
28835
29256
  const existingData = parsed.data;
28836
29257
  const existingSourceIds = Array.isArray(existingData.source_ids) ? existingData.source_ids.filter((value) => typeof value === "string") : [];
@@ -29001,8 +29422,8 @@ async function stageSourceGuideForScope(rootDir, scope, options = {}) {
29001
29422
  }
29002
29423
  );
29003
29424
  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);
29425
+ session.reviewPath = path31.join(approval.approvalDir, "wiki", reviewOutput.page.path);
29426
+ session.guidePath = path31.join(approval.approvalDir, "wiki", guideOutput.page.path);
29006
29427
  session.approvalId = approval.approvalId;
29007
29428
  session.approvalDir = approval.approvalDir;
29008
29429
  const persisted = await persistSourceSessionPage(rootDir, scope, session);
@@ -29140,7 +29561,7 @@ async function addManagedSource(rootDir, input, options = {}) {
29140
29561
  const existing = sources.find((candidate) => matchesManagedSourceSpec(candidate, resolved));
29141
29562
  const now = (/* @__PURE__ */ new Date()).toISOString();
29142
29563
  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),
29564
+ id: resolved.kind === "directory" || resolved.kind === "file" ? stableManagedSourceId(resolved.kind, path31.resolve(resolved.path), resolved.title) : stableManagedSourceId(resolved.kind, resolved.url, resolved.title),
29144
29565
  kind: resolved.kind,
29145
29566
  title: resolved.title,
29146
29567
  path: resolved.kind === "directory" || resolved.kind === "file" ? resolved.path : void 0,
@@ -29268,7 +29689,7 @@ async function deleteManagedSource(rootDir, id) {
29268
29689
  sources.filter((source) => source.id !== id)
29269
29690
  );
29270
29691
  const workingDir = await managedSourceWorkingDir(rootDir, id);
29271
- await fs26.rm(workingDir, { recursive: true, force: true });
29692
+ await fs27.rm(workingDir, { recursive: true, force: true });
29272
29693
  return { removed: target };
29273
29694
  }
29274
29695
 
@@ -29276,9 +29697,9 @@ async function deleteManagedSource(rootDir, id) {
29276
29697
  import { execFile as execFile2 } from "child_process";
29277
29698
  import { randomUUID } from "crypto";
29278
29699
  import { EventEmitter } from "events";
29279
- import fs27 from "fs/promises";
29700
+ import fs28 from "fs/promises";
29280
29701
  import http from "http";
29281
- import path31 from "path";
29702
+ import path32 from "path";
29282
29703
  import { promisify as promisify2 } from "util";
29283
29704
  import matter15 from "gray-matter";
29284
29705
  import mime2 from "mime-types";
@@ -29432,7 +29853,7 @@ function toViewerLintFindings(findings) {
29432
29853
  var execFileAsync2 = promisify2(execFile2);
29433
29854
  async function isReadableFile(absolutePath) {
29434
29855
  try {
29435
- const stats = await fs27.stat(absolutePath);
29856
+ const stats = await fs28.stat(absolutePath);
29436
29857
  return stats.isFile();
29437
29858
  } catch {
29438
29859
  return false;
@@ -29443,15 +29864,15 @@ async function readViewerPage(rootDir, relativePath) {
29443
29864
  return null;
29444
29865
  }
29445
29866
  const { paths } = await loadVaultConfig(rootDir);
29446
- const absolutePath = path31.resolve(paths.wikiDir, relativePath);
29867
+ const absolutePath = path32.resolve(paths.wikiDir, relativePath);
29447
29868
  if (!isPathWithin(paths.wikiDir, absolutePath) || !await isReadableFile(absolutePath)) {
29448
29869
  return null;
29449
29870
  }
29450
- const raw = await fs27.readFile(absolutePath, "utf8");
29871
+ const raw = await fs28.readFile(absolutePath, "utf8");
29451
29872
  const parsed = matter15(raw);
29452
29873
  return {
29453
29874
  path: relativePath,
29454
- title: typeof parsed.data.title === "string" ? parsed.data.title : path31.basename(relativePath, path31.extname(relativePath)),
29875
+ title: typeof parsed.data.title === "string" ? parsed.data.title : path32.basename(relativePath, path32.extname(relativePath)),
29455
29876
  frontmatter: parsed.data,
29456
29877
  content: parsed.content,
29457
29878
  assets: normalizeOutputAssets(parsed.data.output_assets)
@@ -29462,12 +29883,12 @@ async function readViewerAsset(rootDir, relativePath) {
29462
29883
  return null;
29463
29884
  }
29464
29885
  const { paths } = await loadVaultConfig(rootDir);
29465
- const absolutePath = path31.resolve(paths.wikiDir, relativePath);
29886
+ const absolutePath = path32.resolve(paths.wikiDir, relativePath);
29466
29887
  if (!isPathWithin(paths.wikiDir, absolutePath) || !await isReadableFile(absolutePath)) {
29467
29888
  return null;
29468
29889
  }
29469
29890
  return {
29470
- buffer: await fs27.readFile(absolutePath),
29891
+ buffer: await fs28.readFile(absolutePath),
29471
29892
  mimeType: mime2.lookup(absolutePath) || "application/octet-stream"
29472
29893
  };
29473
29894
  }
@@ -29490,12 +29911,12 @@ async function readJsonBody(request) {
29490
29911
  return JSON.parse(raw);
29491
29912
  }
29492
29913
  async function ensureViewerDist(viewerDistDir) {
29493
- const indexPath = path31.join(viewerDistDir, "index.html");
29914
+ const indexPath = path32.join(viewerDistDir, "index.html");
29494
29915
  if (await fileExists(indexPath)) {
29495
29916
  return;
29496
29917
  }
29497
- const viewerProjectDir = path31.dirname(viewerDistDir);
29498
- if (await fileExists(path31.join(viewerProjectDir, "package.json"))) {
29918
+ const viewerProjectDir = path32.dirname(viewerDistDir);
29919
+ if (await fileExists(path32.join(viewerProjectDir, "package.json"))) {
29499
29920
  await execFileAsync2("pnpm", ["build"], { cwd: viewerProjectDir });
29500
29921
  }
29501
29922
  }
@@ -29518,7 +29939,7 @@ async function startGraphServer(rootDir, port, options = {}) {
29518
29939
  response.end(JSON.stringify({ error: "Graph artifact not found. Run `swarmvault compile` first." }));
29519
29940
  return;
29520
29941
  }
29521
- const reportPath = path31.join(paths.wikiDir, "graph", "report.json");
29942
+ const reportPath = path32.join(paths.wikiDir, "graph", "report.json");
29522
29943
  const report = await readJsonFile(reportPath) ?? null;
29523
29944
  response.writeHead(200, { "content-type": "application/json" });
29524
29945
  response.end(JSON.stringify(buildViewerGraphArtifact(graph, { report, full: options.full ?? false })));
@@ -29582,13 +30003,13 @@ async function startGraphServer(rootDir, port, options = {}) {
29582
30003
  return;
29583
30004
  }
29584
30005
  if (url.pathname === "/api/graph-report") {
29585
- const reportPath = path31.join(paths.wikiDir, "graph", "report.json");
30006
+ const reportPath = path32.join(paths.wikiDir, "graph", "report.json");
29586
30007
  if (!await fileExists(reportPath)) {
29587
30008
  response.writeHead(404, { "content-type": "application/json" });
29588
30009
  response.end(JSON.stringify({ error: "Graph report artifact not found. Run `swarmvault compile` first." }));
29589
30010
  return;
29590
30011
  }
29591
- const body = await fs27.readFile(reportPath, "utf8");
30012
+ const body = await fs28.readFile(reportPath, "utf8");
29592
30013
  response.writeHead(200, { "content-type": "application/json" });
29593
30014
  response.end(body);
29594
30015
  return;
@@ -29689,7 +30110,7 @@ async function startGraphServer(rootDir, port, options = {}) {
29689
30110
  return;
29690
30111
  }
29691
30112
  if (url.pathname === "/api/workspace") {
29692
- const reportPath = path31.join(paths.wikiDir, "graph", "report.json");
30113
+ const reportPath = path32.join(paths.wikiDir, "graph", "report.json");
29693
30114
  const [graphRaw, reportRaw, approvalsRaw, candidatesRaw, watchStatusRaw, lintRaw] = await Promise.all([
29694
30115
  readJsonFile(paths.graphPath).catch(() => null),
29695
30116
  readJsonFile(reportPath).catch(() => null),
@@ -29785,15 +30206,15 @@ async function startGraphServer(rootDir, port, options = {}) {
29785
30206
  return;
29786
30207
  }
29787
30208
  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");
30209
+ const target = path32.join(paths.viewerDistDir, relativePath);
30210
+ const fallback = path32.join(paths.viewerDistDir, "index.html");
29790
30211
  const filePath = await fileExists(target) ? target : fallback;
29791
30212
  if (!await fileExists(filePath)) {
29792
30213
  response.writeHead(503, { "content-type": "text/plain" });
29793
30214
  response.end("Viewer build not found. Run `pnpm build` first.");
29794
30215
  return;
29795
30216
  }
29796
- const staticBody = await fs27.readFile(filePath);
30217
+ const staticBody = await fs28.readFile(filePath);
29797
30218
  response.writeHead(200, { "content-type": mime2.lookup(filePath) || "text/plain" });
29798
30219
  response.end(staticBody);
29799
30220
  } catch (error) {
@@ -29833,7 +30254,7 @@ async function exportGraphHtml(rootDir, outputPath, options = {}) {
29833
30254
  throw new Error("Graph artifact not found. Run `swarmvault compile` first.");
29834
30255
  }
29835
30256
  await ensureViewerDist(paths.viewerDistDir);
29836
- const indexPath = path31.join(paths.viewerDistDir, "index.html");
30257
+ const indexPath = path32.join(paths.viewerDistDir, "index.html");
29837
30258
  if (!await fileExists(indexPath)) {
29838
30259
  throw new Error("Viewer build not found. Run `pnpm build` first.");
29839
30260
  }
@@ -29859,17 +30280,17 @@ async function exportGraphHtml(rootDir, outputPath, options = {}) {
29859
30280
  } : null;
29860
30281
  })
29861
30282
  );
29862
- const rawHtml = await fs27.readFile(indexPath, "utf8");
30283
+ const rawHtml = await fs28.readFile(indexPath, "utf8");
29863
30284
  const scriptMatch = rawHtml.match(/<script type="module" crossorigin src="([^"]+)"><\/script>/);
29864
30285
  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;
30286
+ const scriptPath = scriptMatch?.[1] ? path32.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
30287
+ const stylePath = styleMatch?.[1] ? path32.join(paths.viewerDistDir, styleMatch[1].replace(/^\//, "")) : null;
29867
30288
  if (!scriptPath || !await fileExists(scriptPath)) {
29868
30289
  throw new Error("Viewer script bundle not found. Run `pnpm build` first.");
29869
30290
  }
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"));
30291
+ const script = await fs28.readFile(scriptPath, "utf8");
30292
+ const style = stylePath && await fileExists(stylePath) ? await fs28.readFile(stylePath, "utf8") : "";
30293
+ const report = await readJsonFile(path32.join(paths.wikiDir, "graph", "report.json"));
29873
30294
  const embeddedData = JSON.stringify(
29874
30295
  { graph: buildViewerGraphArtifact(graph, { report, full: options.full ?? false }), pages: pages.filter(Boolean), report },
29875
30296
  null,
@@ -29892,9 +30313,9 @@ async function exportGraphHtml(rootDir, outputPath, options = {}) {
29892
30313
  "</html>",
29893
30314
  ""
29894
30315
  ].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);
30316
+ await fs28.mkdir(path32.dirname(outputPath), { recursive: true });
30317
+ await fs28.writeFile(outputPath, html, "utf8");
30318
+ return path32.resolve(outputPath);
29898
30319
  }
29899
30320
  export {
29900
30321
  ALL_MIGRATIONS,
@@ -29905,6 +30326,8 @@ export {
29905
30326
  DEFAULT_REDACTION_PATTERNS,
29906
30327
  DEFAULT_STALE_THRESHOLD,
29907
30328
  LARGE_REPO_NODE_THRESHOLD,
30329
+ LOCAL_WHISPER_MODEL_SIZES,
30330
+ LocalWhisperProviderAdapter,
29908
30331
  OPENAI_COMPATIBLE_CAPABILITY_MATRIX,
29909
30332
  acceptApproval,
29910
30333
  addInput,
@@ -29919,6 +30342,7 @@ export {
29919
30342
  blastRadiusVault,
29920
30343
  bootstrapDemo,
29921
30344
  buildConfiguredRedactor,
30345
+ buildGraphShareArtifact,
29922
30346
  buildRedactor,
29923
30347
  compileVault,
29924
30348
  computeDecayScore,
@@ -29931,9 +30355,12 @@ export {
29931
30355
  defaultVaultSchema,
29932
30356
  deleteManagedSource,
29933
30357
  detectVaultVersion,
30358
+ discoverLocalWhisperBinary,
30359
+ downloadWhisperModel,
29934
30360
  estimatePageTokens,
29935
30361
  estimateTokens,
29936
30362
  evaluateCandidateForPromotion,
30363
+ expectedModelPath,
29937
30364
  explainGraphVault,
29938
30365
  exploreVault,
29939
30366
  exportGraphFormat,
@@ -29974,6 +30401,7 @@ export {
29974
30401
  loadVaultSchemas,
29975
30402
  lookupPresetCapabilities,
29976
30403
  markSuperseded,
30404
+ modelDownloadUrl,
29977
30405
  pathGraphVault,
29978
30406
  persistDecayFrontmatter,
29979
30407
  planMigration,
@@ -29986,9 +30414,11 @@ export {
29986
30414
  readExtractedText,
29987
30415
  readGraphReport,
29988
30416
  readPage,
30417
+ registerLocalWhisperProvider,
29989
30418
  rejectApproval,
29990
30419
  reloadManagedSources,
29991
30420
  removeWatchedRoot,
30421
+ renderGraphShareMarkdown,
29992
30422
  resetDecay,
29993
30423
  resolveConsolidationConfig,
29994
30424
  resolveDecayConfig,
@@ -30010,6 +30440,7 @@ export {
30010
30440
  stageGeneratedOutputPages,
30011
30441
  startGraphServer,
30012
30442
  startMcpServer,
30443
+ summarizeLocalWhisperSetup,
30013
30444
  syncTrackedRepos,
30014
30445
  syncTrackedReposForWatch,
30015
30446
  synthesizeHyperedgeHubs,