@vpxa/kb 0.1.1
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/LICENSE +21 -0
- package/README.md +1140 -0
- package/bin/kb.mjs +10 -0
- package/package.json +67 -0
- package/packages/analyzers/dist/blast-radius-analyzer.d.ts +23 -0
- package/packages/analyzers/dist/blast-radius-analyzer.js +114 -0
- package/packages/analyzers/dist/dependency-analyzer.d.ts +29 -0
- package/packages/analyzers/dist/dependency-analyzer.js +425 -0
- package/packages/analyzers/dist/diagram-generator.d.ts +13 -0
- package/packages/analyzers/dist/diagram-generator.js +86 -0
- package/packages/analyzers/dist/entry-point-analyzer.d.ts +19 -0
- package/packages/analyzers/dist/entry-point-analyzer.js +239 -0
- package/packages/analyzers/dist/index.d.ts +14 -0
- package/packages/analyzers/dist/index.js +23 -0
- package/packages/analyzers/dist/knowledge-producer.d.ts +32 -0
- package/packages/analyzers/dist/knowledge-producer.js +113 -0
- package/packages/analyzers/dist/pattern-analyzer.d.ts +12 -0
- package/packages/analyzers/dist/pattern-analyzer.js +359 -0
- package/packages/analyzers/dist/regex-call-graph.d.ts +17 -0
- package/packages/analyzers/dist/regex-call-graph.js +428 -0
- package/packages/analyzers/dist/structure-analyzer.d.ts +11 -0
- package/packages/analyzers/dist/structure-analyzer.js +258 -0
- package/packages/analyzers/dist/symbol-analyzer.d.ts +10 -0
- package/packages/analyzers/dist/symbol-analyzer.js +442 -0
- package/packages/analyzers/dist/ts-call-graph.d.ts +27 -0
- package/packages/analyzers/dist/ts-call-graph.js +160 -0
- package/packages/analyzers/dist/types.d.ts +98 -0
- package/packages/analyzers/dist/types.js +1 -0
- package/packages/chunker/dist/call-graph-extractor.d.ts +22 -0
- package/packages/chunker/dist/call-graph-extractor.js +90 -0
- package/packages/chunker/dist/chunker-factory.d.ts +7 -0
- package/packages/chunker/dist/chunker-factory.js +36 -0
- package/packages/chunker/dist/chunker.interface.d.ts +10 -0
- package/packages/chunker/dist/chunker.interface.js +1 -0
- package/packages/chunker/dist/code-chunker.d.ts +14 -0
- package/packages/chunker/dist/code-chunker.js +134 -0
- package/packages/chunker/dist/generic-chunker.d.ts +12 -0
- package/packages/chunker/dist/generic-chunker.js +72 -0
- package/packages/chunker/dist/index.d.ts +8 -0
- package/packages/chunker/dist/index.js +21 -0
- package/packages/chunker/dist/markdown-chunker.d.ts +14 -0
- package/packages/chunker/dist/markdown-chunker.js +122 -0
- package/packages/chunker/dist/treesitter-chunker.d.ts +47 -0
- package/packages/chunker/dist/treesitter-chunker.js +234 -0
- package/packages/cli/dist/commands/analyze.d.ts +3 -0
- package/packages/cli/dist/commands/analyze.js +112 -0
- package/packages/cli/dist/commands/context-cmds.d.ts +3 -0
- package/packages/cli/dist/commands/context-cmds.js +155 -0
- package/packages/cli/dist/commands/environment.d.ts +3 -0
- package/packages/cli/dist/commands/environment.js +204 -0
- package/packages/cli/dist/commands/execution.d.ts +3 -0
- package/packages/cli/dist/commands/execution.js +137 -0
- package/packages/cli/dist/commands/graph.d.ts +3 -0
- package/packages/cli/dist/commands/graph.js +81 -0
- package/packages/cli/dist/commands/init.d.ts +8 -0
- package/packages/cli/dist/commands/init.js +87 -0
- package/packages/cli/dist/commands/knowledge.d.ts +3 -0
- package/packages/cli/dist/commands/knowledge.js +139 -0
- package/packages/cli/dist/commands/search.d.ts +3 -0
- package/packages/cli/dist/commands/search.js +267 -0
- package/packages/cli/dist/commands/system.d.ts +3 -0
- package/packages/cli/dist/commands/system.js +241 -0
- package/packages/cli/dist/commands/workspace.d.ts +3 -0
- package/packages/cli/dist/commands/workspace.js +388 -0
- package/packages/cli/dist/context.d.ts +5 -0
- package/packages/cli/dist/context.js +14 -0
- package/packages/cli/dist/helpers.d.ts +52 -0
- package/packages/cli/dist/helpers.js +458 -0
- package/packages/cli/dist/index.d.ts +8 -0
- package/packages/cli/dist/index.js +69 -0
- package/packages/cli/dist/kb-init.d.ts +57 -0
- package/packages/cli/dist/kb-init.js +82 -0
- package/packages/cli/dist/types.d.ts +7 -0
- package/packages/cli/dist/types.js +1 -0
- package/packages/core/dist/constants.d.ts +49 -0
- package/packages/core/dist/constants.js +43 -0
- package/packages/core/dist/content-detector.d.ts +9 -0
- package/packages/core/dist/content-detector.js +79 -0
- package/packages/core/dist/errors.d.ts +18 -0
- package/packages/core/dist/errors.js +40 -0
- package/packages/core/dist/index.d.ts +6 -0
- package/packages/core/dist/index.js +9 -0
- package/packages/core/dist/logger.d.ts +9 -0
- package/packages/core/dist/logger.js +34 -0
- package/packages/core/dist/types.d.ts +108 -0
- package/packages/core/dist/types.js +1 -0
- package/packages/embeddings/dist/embedder.interface.d.ts +24 -0
- package/packages/embeddings/dist/embedder.interface.js +1 -0
- package/packages/embeddings/dist/index.d.ts +3 -0
- package/packages/embeddings/dist/index.js +5 -0
- package/packages/embeddings/dist/onnx-embedder.d.ts +24 -0
- package/packages/embeddings/dist/onnx-embedder.js +82 -0
- package/packages/indexer/dist/file-hasher.d.ts +11 -0
- package/packages/indexer/dist/file-hasher.js +13 -0
- package/packages/indexer/dist/filesystem-crawler.d.ts +27 -0
- package/packages/indexer/dist/filesystem-crawler.js +125 -0
- package/packages/indexer/dist/graph-extractor.d.ts +22 -0
- package/packages/indexer/dist/graph-extractor.js +111 -0
- package/packages/indexer/dist/incremental-indexer.d.ts +47 -0
- package/packages/indexer/dist/incremental-indexer.js +278 -0
- package/packages/indexer/dist/index.d.ts +5 -0
- package/packages/indexer/dist/index.js +14 -0
- package/packages/server/dist/api.d.ts +8 -0
- package/packages/server/dist/api.js +9 -0
- package/packages/server/dist/config.d.ts +3 -0
- package/packages/server/dist/config.js +75 -0
- package/packages/server/dist/curated-manager.d.ts +86 -0
- package/packages/server/dist/curated-manager.js +357 -0
- package/packages/server/dist/index.d.ts +2 -0
- package/packages/server/dist/index.js +134 -0
- package/packages/server/dist/replay-interceptor.d.ts +11 -0
- package/packages/server/dist/replay-interceptor.js +38 -0
- package/packages/server/dist/resources/resources.d.ts +4 -0
- package/packages/server/dist/resources/resources.js +40 -0
- package/packages/server/dist/server.d.ts +21 -0
- package/packages/server/dist/server.js +247 -0
- package/packages/server/dist/tools/analyze.tools.d.ts +11 -0
- package/packages/server/dist/tools/analyze.tools.js +288 -0
- package/packages/server/dist/tools/forge.tools.d.ts +12 -0
- package/packages/server/dist/tools/forge.tools.js +501 -0
- package/packages/server/dist/tools/forget.tool.d.ts +4 -0
- package/packages/server/dist/tools/forget.tool.js +43 -0
- package/packages/server/dist/tools/graph.tool.d.ts +4 -0
- package/packages/server/dist/tools/graph.tool.js +110 -0
- package/packages/server/dist/tools/list.tool.d.ts +4 -0
- package/packages/server/dist/tools/list.tool.js +56 -0
- package/packages/server/dist/tools/lookup.tool.d.ts +4 -0
- package/packages/server/dist/tools/lookup.tool.js +53 -0
- package/packages/server/dist/tools/onboard.tool.d.ts +5 -0
- package/packages/server/dist/tools/onboard.tool.js +112 -0
- package/packages/server/dist/tools/produce.tool.d.ts +3 -0
- package/packages/server/dist/tools/produce.tool.js +74 -0
- package/packages/server/dist/tools/read.tool.d.ts +4 -0
- package/packages/server/dist/tools/read.tool.js +49 -0
- package/packages/server/dist/tools/reindex.tool.d.ts +7 -0
- package/packages/server/dist/tools/reindex.tool.js +70 -0
- package/packages/server/dist/tools/remember.tool.d.ts +4 -0
- package/packages/server/dist/tools/remember.tool.js +45 -0
- package/packages/server/dist/tools/replay.tool.d.ts +3 -0
- package/packages/server/dist/tools/replay.tool.js +89 -0
- package/packages/server/dist/tools/search.tool.d.ts +5 -0
- package/packages/server/dist/tools/search.tool.js +331 -0
- package/packages/server/dist/tools/status.tool.d.ts +4 -0
- package/packages/server/dist/tools/status.tool.js +68 -0
- package/packages/server/dist/tools/toolkit.tools.d.ts +35 -0
- package/packages/server/dist/tools/toolkit.tools.js +1674 -0
- package/packages/server/dist/tools/update.tool.d.ts +4 -0
- package/packages/server/dist/tools/update.tool.js +42 -0
- package/packages/server/dist/tools/utility.tools.d.ts +15 -0
- package/packages/server/dist/tools/utility.tools.js +461 -0
- package/packages/store/dist/graph-store.interface.d.ts +104 -0
- package/packages/store/dist/graph-store.interface.js +1 -0
- package/packages/store/dist/index.d.ts +6 -0
- package/packages/store/dist/index.js +9 -0
- package/packages/store/dist/lance-store.d.ts +32 -0
- package/packages/store/dist/lance-store.js +258 -0
- package/packages/store/dist/sqlite-graph-store.d.ts +43 -0
- package/packages/store/dist/sqlite-graph-store.js +374 -0
- package/packages/store/dist/store-factory.d.ts +9 -0
- package/packages/store/dist/store-factory.js +14 -0
- package/packages/store/dist/store.interface.d.ts +48 -0
- package/packages/store/dist/store.interface.js +1 -0
- package/packages/tools/dist/batch.d.ts +21 -0
- package/packages/tools/dist/batch.js +45 -0
- package/packages/tools/dist/changelog.d.ts +34 -0
- package/packages/tools/dist/changelog.js +112 -0
- package/packages/tools/dist/check.d.ts +26 -0
- package/packages/tools/dist/check.js +59 -0
- package/packages/tools/dist/checkpoint.d.ts +17 -0
- package/packages/tools/dist/checkpoint.js +43 -0
- package/packages/tools/dist/codemod.d.ts +37 -0
- package/packages/tools/dist/codemod.js +69 -0
- package/packages/tools/dist/compact.d.ts +41 -0
- package/packages/tools/dist/compact.js +60 -0
- package/packages/tools/dist/data-transform.d.ts +10 -0
- package/packages/tools/dist/data-transform.js +124 -0
- package/packages/tools/dist/dead-symbols.d.ts +21 -0
- package/packages/tools/dist/dead-symbols.js +71 -0
- package/packages/tools/dist/delegate.d.ts +34 -0
- package/packages/tools/dist/delegate.js +130 -0
- package/packages/tools/dist/diff-parse.d.ts +26 -0
- package/packages/tools/dist/diff-parse.js +153 -0
- package/packages/tools/dist/digest.d.ts +53 -0
- package/packages/tools/dist/digest.js +242 -0
- package/packages/tools/dist/encode.d.ts +14 -0
- package/packages/tools/dist/encode.js +46 -0
- package/packages/tools/dist/env-info.d.ts +28 -0
- package/packages/tools/dist/env-info.js +58 -0
- package/packages/tools/dist/eval.d.ts +13 -0
- package/packages/tools/dist/eval.js +79 -0
- package/packages/tools/dist/evidence-map.d.ts +79 -0
- package/packages/tools/dist/evidence-map.js +203 -0
- package/packages/tools/dist/file-summary.d.ts +32 -0
- package/packages/tools/dist/file-summary.js +106 -0
- package/packages/tools/dist/file-walk.d.ts +4 -0
- package/packages/tools/dist/file-walk.js +75 -0
- package/packages/tools/dist/find-examples.d.ts +25 -0
- package/packages/tools/dist/find-examples.js +48 -0
- package/packages/tools/dist/find.d.ts +47 -0
- package/packages/tools/dist/find.js +120 -0
- package/packages/tools/dist/forge-classify.d.ts +44 -0
- package/packages/tools/dist/forge-classify.js +319 -0
- package/packages/tools/dist/forge-ground.d.ts +64 -0
- package/packages/tools/dist/forge-ground.js +184 -0
- package/packages/tools/dist/git-context.d.ts +22 -0
- package/packages/tools/dist/git-context.js +46 -0
- package/packages/tools/dist/graph-query.d.ts +89 -0
- package/packages/tools/dist/graph-query.js +194 -0
- package/packages/tools/dist/health.d.ts +14 -0
- package/packages/tools/dist/health.js +118 -0
- package/packages/tools/dist/http-request.d.ts +23 -0
- package/packages/tools/dist/http-request.js +58 -0
- package/packages/tools/dist/index.d.ts +49 -0
- package/packages/tools/dist/index.js +273 -0
- package/packages/tools/dist/lane.d.ts +39 -0
- package/packages/tools/dist/lane.js +227 -0
- package/packages/tools/dist/measure.d.ts +38 -0
- package/packages/tools/dist/measure.js +119 -0
- package/packages/tools/dist/onboard.d.ts +41 -0
- package/packages/tools/dist/onboard.js +1139 -0
- package/packages/tools/dist/parse-output.d.ts +80 -0
- package/packages/tools/dist/parse-output.js +158 -0
- package/packages/tools/dist/process-manager.d.ts +18 -0
- package/packages/tools/dist/process-manager.js +69 -0
- package/packages/tools/dist/queue.d.ts +38 -0
- package/packages/tools/dist/queue.js +126 -0
- package/packages/tools/dist/regex-test.d.ts +31 -0
- package/packages/tools/dist/regex-test.js +39 -0
- package/packages/tools/dist/rename.d.ts +29 -0
- package/packages/tools/dist/rename.js +70 -0
- package/packages/tools/dist/replay.d.ts +56 -0
- package/packages/tools/dist/replay.js +108 -0
- package/packages/tools/dist/schema-validate.d.ts +23 -0
- package/packages/tools/dist/schema-validate.js +141 -0
- package/packages/tools/dist/scope-map.d.ts +52 -0
- package/packages/tools/dist/scope-map.js +72 -0
- package/packages/tools/dist/snippet.d.ts +34 -0
- package/packages/tools/dist/snippet.js +80 -0
- package/packages/tools/dist/stash.d.ts +12 -0
- package/packages/tools/dist/stash.js +60 -0
- package/packages/tools/dist/stratum-card.d.ts +31 -0
- package/packages/tools/dist/stratum-card.js +239 -0
- package/packages/tools/dist/symbol.d.ts +28 -0
- package/packages/tools/dist/symbol.js +87 -0
- package/packages/tools/dist/test-run.d.ts +23 -0
- package/packages/tools/dist/test-run.js +55 -0
- package/packages/tools/dist/text-utils.d.ts +16 -0
- package/packages/tools/dist/text-utils.js +31 -0
- package/packages/tools/dist/time-utils.d.ts +18 -0
- package/packages/tools/dist/time-utils.js +135 -0
- package/packages/tools/dist/trace.d.ts +24 -0
- package/packages/tools/dist/trace.js +114 -0
- package/packages/tools/dist/truncation.d.ts +22 -0
- package/packages/tools/dist/truncation.js +45 -0
- package/packages/tools/dist/watch.d.ts +30 -0
- package/packages/tools/dist/watch.js +61 -0
- package/packages/tools/dist/web-fetch.d.ts +45 -0
- package/packages/tools/dist/web-fetch.js +249 -0
- package/packages/tools/dist/web-search.d.ts +23 -0
- package/packages/tools/dist/web-search.js +46 -0
- package/packages/tools/dist/workset.d.ts +45 -0
- package/packages/tools/dist/workset.js +77 -0
- package/packages/tui/dist/App.d.ts +8 -0
- package/packages/tui/dist/App.js +52659 -0
- package/packages/tui/dist/index.d.ts +19 -0
- package/packages/tui/dist/index.js +54742 -0
- package/packages/tui/dist/panels/CuratedPanel.d.ts +8 -0
- package/packages/tui/dist/panels/CuratedPanel.js +34452 -0
- package/packages/tui/dist/panels/LogPanel.d.ts +3 -0
- package/packages/tui/dist/panels/LogPanel.js +51894 -0
- package/packages/tui/dist/panels/SearchPanel.d.ts +10 -0
- package/packages/tui/dist/panels/SearchPanel.js +34985 -0
- package/packages/tui/dist/panels/StatusPanel.d.ts +8 -0
- package/packages/tui/dist/panels/StatusPanel.js +34465 -0
- package/skills/knowledge-base/SKILL.md +316 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
async function trace(embedder, store, options) {
|
|
2
|
+
const { start, direction, maxDepth = 3 } = options;
|
|
3
|
+
const nodes = [];
|
|
4
|
+
const visitedTargets = /* @__PURE__ */ new Set();
|
|
5
|
+
const seenNodes = /* @__PURE__ */ new Set();
|
|
6
|
+
const startVector = await embedder.embed(start);
|
|
7
|
+
const startResults = await store.search(startVector, { limit: 10 });
|
|
8
|
+
if (startResults.length === 0) {
|
|
9
|
+
return { start, direction, nodes, depth: 0 };
|
|
10
|
+
}
|
|
11
|
+
const queue = [{ target: start, depth: 0 }];
|
|
12
|
+
let maxObservedDepth = 0;
|
|
13
|
+
while (queue.length > 0) {
|
|
14
|
+
const current = queue.shift();
|
|
15
|
+
if (!current) break;
|
|
16
|
+
if (current.depth >= maxDepth) continue;
|
|
17
|
+
if (visitedTargets.has(current.target)) continue;
|
|
18
|
+
visitedTargets.add(current.target);
|
|
19
|
+
const vector = await embedder.embed(current.target);
|
|
20
|
+
const results = await store.search(vector, { limit: 20 });
|
|
21
|
+
const escapedTarget = escapeRegExp(current.target);
|
|
22
|
+
const targetLooksLikeModule = isModuleSpecifier(current.target);
|
|
23
|
+
for (const result of results) {
|
|
24
|
+
const lines = result.record.content.split("\n");
|
|
25
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
26
|
+
const line = lines[index];
|
|
27
|
+
const lineNum = result.record.startLine + index;
|
|
28
|
+
const path = result.record.sourcePath;
|
|
29
|
+
if (direction !== "forward") {
|
|
30
|
+
const importPattern = targetLooksLikeModule ? new RegExp(`from\\s+['"]${escapedTarget}['"]`) : new RegExp(`import\\s+.*\\b${escapedTarget}\\b.*from\\s+`);
|
|
31
|
+
if (importPattern.test(line)) {
|
|
32
|
+
addNode(seenNodes, nodes, {
|
|
33
|
+
path,
|
|
34
|
+
symbol: current.target,
|
|
35
|
+
line: lineNum,
|
|
36
|
+
relationship: "imported-by"
|
|
37
|
+
});
|
|
38
|
+
maxObservedDepth = Math.max(maxObservedDepth, current.depth + 1);
|
|
39
|
+
const fromMatch = line.match(/from\s+['"]([^'"]+)['"]/);
|
|
40
|
+
if (!targetLooksLikeModule && fromMatch) {
|
|
41
|
+
queue.push({ target: fromMatch[1], depth: current.depth + 1 });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (direction !== "backward") {
|
|
46
|
+
if (targetLooksLikeModule) {
|
|
47
|
+
const moduleImportPattern = new RegExp(`from\\s+['"]${escapedTarget}['"]`);
|
|
48
|
+
if (moduleImportPattern.test(line)) {
|
|
49
|
+
addNode(seenNodes, nodes, {
|
|
50
|
+
path,
|
|
51
|
+
symbol: current.target,
|
|
52
|
+
line: lineNum,
|
|
53
|
+
relationship: "imports"
|
|
54
|
+
});
|
|
55
|
+
maxObservedDepth = Math.max(maxObservedDepth, current.depth + 1);
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
const callPattern = new RegExp(`\\b${escapedTarget}\\s*\\(`);
|
|
59
|
+
if (callPattern.test(line) && !/^\s*(?:export\s+)?(?:async\s+)?function\s/.test(line)) {
|
|
60
|
+
addNode(seenNodes, nodes, {
|
|
61
|
+
path,
|
|
62
|
+
symbol: current.target,
|
|
63
|
+
line: lineNum,
|
|
64
|
+
relationship: "calls"
|
|
65
|
+
});
|
|
66
|
+
maxObservedDepth = Math.max(maxObservedDepth, current.depth + 1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const refPattern = targetLooksLikeModule ? new RegExp(`['"]${escapedTarget}['"]`) : new RegExp(`\\b${escapedTarget}\\b`);
|
|
71
|
+
if (refPattern.test(line) && !/^\s*import\s/.test(line) && !/^\s*(?:export\s+)?(?:async\s+)?function\s/.test(line)) {
|
|
72
|
+
addNode(seenNodes, nodes, {
|
|
73
|
+
path,
|
|
74
|
+
symbol: current.target,
|
|
75
|
+
line: lineNum,
|
|
76
|
+
relationship: "references"
|
|
77
|
+
});
|
|
78
|
+
maxObservedDepth = Math.max(maxObservedDepth, current.depth + 1);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
start,
|
|
85
|
+
direction,
|
|
86
|
+
nodes: deduplicateNodes(nodes),
|
|
87
|
+
depth: maxObservedDepth
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function addNode(seenNodes, nodes, node) {
|
|
91
|
+
const key = `${node.path}:${node.line}:${node.relationship}`;
|
|
92
|
+
if (seenNodes.has(key)) return;
|
|
93
|
+
seenNodes.add(key);
|
|
94
|
+
nodes.push(node);
|
|
95
|
+
}
|
|
96
|
+
function escapeRegExp(value) {
|
|
97
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
98
|
+
}
|
|
99
|
+
function isModuleSpecifier(value) {
|
|
100
|
+
return /[./\\]/.test(value);
|
|
101
|
+
}
|
|
102
|
+
function deduplicateNodes(nodes) {
|
|
103
|
+
const seen = /* @__PURE__ */ new Set();
|
|
104
|
+
return nodes.filter((node) => {
|
|
105
|
+
const key = `${node.path}:${node.line}:${node.relationship}`;
|
|
106
|
+
if (seen.has(key)) return false;
|
|
107
|
+
seen.add(key);
|
|
108
|
+
return true;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
export {
|
|
112
|
+
trace
|
|
113
|
+
};
|
|
114
|
+
//# sourceMappingURL=trace.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart truncation utilities.
|
|
3
|
+
*
|
|
4
|
+
* Head/tail split: preserves both the beginning (context/setup) and
|
|
5
|
+
* end (errors/results/stack traces) of content. Snaps to line boundaries.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Truncate text keeping both head and tail portions.
|
|
9
|
+
* Critical for process output where errors appear at the end.
|
|
10
|
+
*
|
|
11
|
+
* @param text - Full text to truncate
|
|
12
|
+
* @param maxLen - Maximum character length
|
|
13
|
+
* @param headRatio - Fraction for head portion (default 0.6)
|
|
14
|
+
* @returns Truncated text with separator showing omitted line/char count
|
|
15
|
+
*/
|
|
16
|
+
export declare function headTailTruncate(text: string, maxLen: number, headRatio?: number): string;
|
|
17
|
+
/**
|
|
18
|
+
* Truncate at a paragraph boundary near maxLength (head-only).
|
|
19
|
+
* Best for web content and markdown where structure is at the top.
|
|
20
|
+
*/
|
|
21
|
+
export declare function paragraphTruncate(text: string, maxLen: number): string;
|
|
22
|
+
//# sourceMappingURL=truncation.d.ts.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const HEAD_RATIO = 0.6;
|
|
2
|
+
function headTailTruncate(text, maxLen, headRatio = HEAD_RATIO) {
|
|
3
|
+
if (text.length <= maxLen) return text;
|
|
4
|
+
const separatorBudget = 120;
|
|
5
|
+
const contentBudget = Math.max(0, maxLen - separatorBudget);
|
|
6
|
+
const headBudget = Math.floor(contentBudget * headRatio);
|
|
7
|
+
const tailBudget = contentBudget - headBudget;
|
|
8
|
+
const headRegion = text.slice(0, headBudget);
|
|
9
|
+
const headCut = headRegion.lastIndexOf("\n");
|
|
10
|
+
const head = headCut > 0 ? headRegion.slice(0, headCut) : headRegion;
|
|
11
|
+
const tailStart = text.length - tailBudget;
|
|
12
|
+
const tailRegion = text.slice(tailStart);
|
|
13
|
+
const tailCut = tailRegion.indexOf("\n");
|
|
14
|
+
const tail = tailCut >= 0 ? tailRegion.slice(tailCut + 1) : tailRegion;
|
|
15
|
+
const omittedChars = text.length - head.length - tail.length;
|
|
16
|
+
let omittedLines = 1;
|
|
17
|
+
const omittedStart = head.length;
|
|
18
|
+
const omittedEnd = text.length - tail.length;
|
|
19
|
+
for (let i = omittedStart; i < omittedEnd; i++) {
|
|
20
|
+
if (text.charCodeAt(i) === 10) omittedLines++;
|
|
21
|
+
}
|
|
22
|
+
return `${head}
|
|
23
|
+
|
|
24
|
+
[\u2026 ${omittedLines} lines / ${(omittedChars / 1024).toFixed(1)}KB truncated \u2014 showing first ${head.split("\n").length} + last ${tail.split("\n").length} lines]
|
|
25
|
+
|
|
26
|
+
${tail}`;
|
|
27
|
+
}
|
|
28
|
+
function paragraphTruncate(text, maxLen) {
|
|
29
|
+
if (text.length <= maxLen) return text;
|
|
30
|
+
const searchStart = Math.max(0, maxLen - 200);
|
|
31
|
+
const searchRegion = text.slice(searchStart, maxLen);
|
|
32
|
+
const lastBreak = searchRegion.lastIndexOf("\n\n");
|
|
33
|
+
const cutPoint = lastBreak >= 0 ? searchStart + lastBreak : maxLen;
|
|
34
|
+
const truncated = text.slice(0, cutPoint).trimEnd();
|
|
35
|
+
const pct = Math.round(cutPoint / text.length * 100);
|
|
36
|
+
return `${truncated}
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
*[Truncated at ${cutPoint.toLocaleString()} chars \u2014 ${pct}% of original content]*`;
|
|
40
|
+
}
|
|
41
|
+
export {
|
|
42
|
+
headTailTruncate,
|
|
43
|
+
paragraphTruncate
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=truncation.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface WatchEvent {
|
|
2
|
+
type: 'change' | 'rename';
|
|
3
|
+
path: string;
|
|
4
|
+
timestamp: string;
|
|
5
|
+
}
|
|
6
|
+
export interface WatchOptions {
|
|
7
|
+
/** Directory to watch */
|
|
8
|
+
path: string;
|
|
9
|
+
/** Glob patterns to exclude */
|
|
10
|
+
exclude?: string[];
|
|
11
|
+
/** Max events to buffer (default: 100) */
|
|
12
|
+
maxEvents?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface WatchHandle {
|
|
15
|
+
id: string;
|
|
16
|
+
path: string;
|
|
17
|
+
events: WatchEvent[];
|
|
18
|
+
status: 'watching' | 'stopped';
|
|
19
|
+
stop: () => void;
|
|
20
|
+
getEvents: (since?: string) => WatchEvent[];
|
|
21
|
+
}
|
|
22
|
+
export declare function watchStart(options: WatchOptions): WatchHandle;
|
|
23
|
+
export declare function watchStop(id: string): boolean;
|
|
24
|
+
export declare function watchList(): Array<{
|
|
25
|
+
id: string;
|
|
26
|
+
path: string;
|
|
27
|
+
status: string;
|
|
28
|
+
eventCount: number;
|
|
29
|
+
}>;
|
|
30
|
+
//# sourceMappingURL=watch.d.ts.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { watch } from "node:fs";
|
|
2
|
+
const watchers = /* @__PURE__ */ new Map();
|
|
3
|
+
const DEFAULT_EXCLUDES = ["node_modules", ".git", "dist", ".turbo", ".kb-data", "cdk.out"];
|
|
4
|
+
function watchStart(options) {
|
|
5
|
+
const { path: watchPath, maxEvents = 100 } = options;
|
|
6
|
+
const excludes = options.exclude ?? DEFAULT_EXCLUDES;
|
|
7
|
+
const id = `watch-${Date.now()}`;
|
|
8
|
+
const events = [];
|
|
9
|
+
const shouldExclude = (filePath) => excludes.some((pattern) => normalizePath(filePath).includes(normalizePath(pattern)));
|
|
10
|
+
const watcher = watch(watchPath, { recursive: true }, (eventType, filename) => {
|
|
11
|
+
if (filename == null) return;
|
|
12
|
+
const filePath = String(filename);
|
|
13
|
+
if (!filePath || shouldExclude(filePath)) return;
|
|
14
|
+
events.push({
|
|
15
|
+
type: eventType,
|
|
16
|
+
path: normalizePath(filePath),
|
|
17
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
18
|
+
});
|
|
19
|
+
if (events.length > maxEvents) events.shift();
|
|
20
|
+
});
|
|
21
|
+
const handle = {
|
|
22
|
+
id,
|
|
23
|
+
path: watchPath,
|
|
24
|
+
events,
|
|
25
|
+
status: "watching",
|
|
26
|
+
stop: () => {
|
|
27
|
+
watcher.close();
|
|
28
|
+
handle.status = "stopped";
|
|
29
|
+
watchers.delete(id);
|
|
30
|
+
},
|
|
31
|
+
getEvents: (since) => {
|
|
32
|
+
if (!since) return events;
|
|
33
|
+
return events.filter((event) => event.timestamp > since);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
watchers.set(id, { watcher, handle });
|
|
37
|
+
return handle;
|
|
38
|
+
}
|
|
39
|
+
function watchStop(id) {
|
|
40
|
+
const entry = watchers.get(id);
|
|
41
|
+
if (!entry) return false;
|
|
42
|
+
entry.handle.stop();
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
function watchList() {
|
|
46
|
+
return [...watchers.values()].map((entry) => ({
|
|
47
|
+
id: entry.handle.id,
|
|
48
|
+
path: entry.handle.path,
|
|
49
|
+
status: entry.handle.status,
|
|
50
|
+
eventCount: entry.handle.events.length
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
function normalizePath(value) {
|
|
54
|
+
return value.replace(/\\/g, "/");
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
watchList,
|
|
58
|
+
watchStart,
|
|
59
|
+
watchStop
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=watch.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* kb_web_fetch — Fetch a web page and convert to LLM-friendly format.
|
|
3
|
+
*
|
|
4
|
+
* Supports multiple output modes: markdown (default), raw HTML,
|
|
5
|
+
* extracted links, or heading outline. Strips scripts, styles,
|
|
6
|
+
* and non-content elements for compact, token-efficient output.
|
|
7
|
+
*/
|
|
8
|
+
export type WebFetchMode = 'markdown' | 'raw' | 'links' | 'outline';
|
|
9
|
+
export interface WebFetchOptions {
|
|
10
|
+
/** URL to fetch */
|
|
11
|
+
url: string;
|
|
12
|
+
/** Output mode */
|
|
13
|
+
mode?: WebFetchMode;
|
|
14
|
+
/** CSS selector to extract specific element (default: auto-detect main content) */
|
|
15
|
+
selector?: string;
|
|
16
|
+
/** Max characters in output — smart truncation at paragraph boundaries */
|
|
17
|
+
maxLength?: number;
|
|
18
|
+
/** Include page metadata header (title, description, URL) */
|
|
19
|
+
includeMetadata?: boolean;
|
|
20
|
+
/** Append extracted links list at the end */
|
|
21
|
+
includeLinks?: boolean;
|
|
22
|
+
/** Include image alt texts inline */
|
|
23
|
+
includeImages?: boolean;
|
|
24
|
+
/** Request timeout in milliseconds */
|
|
25
|
+
timeout?: number;
|
|
26
|
+
}
|
|
27
|
+
export interface WebFetchResult {
|
|
28
|
+
/** The converted content */
|
|
29
|
+
content: string;
|
|
30
|
+
/** Page title */
|
|
31
|
+
title: string;
|
|
32
|
+
/** Page description (from meta) */
|
|
33
|
+
description: string;
|
|
34
|
+
/** Final URL after redirects */
|
|
35
|
+
url: string;
|
|
36
|
+
/** Content length before truncation */
|
|
37
|
+
originalLength: number;
|
|
38
|
+
/** Whether content was truncated */
|
|
39
|
+
truncated: boolean;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Fetch a web page and convert to an LLM-friendly format.
|
|
43
|
+
*/
|
|
44
|
+
export declare function webFetch(options: WebFetchOptions): Promise<WebFetchResult>;
|
|
45
|
+
//# sourceMappingURL=web-fetch.d.ts.map
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import TurndownService from "turndown";
|
|
2
|
+
import { paragraphTruncate } from "./truncation.js";
|
|
3
|
+
const DEFAULT_MAX_LENGTH = 15e3;
|
|
4
|
+
const DEFAULT_TIMEOUT = 15e3;
|
|
5
|
+
const STRIP_ELEMENTS = [
|
|
6
|
+
"script",
|
|
7
|
+
"style",
|
|
8
|
+
"noscript",
|
|
9
|
+
"iframe",
|
|
10
|
+
"svg",
|
|
11
|
+
"nav",
|
|
12
|
+
"footer",
|
|
13
|
+
"header",
|
|
14
|
+
"aside",
|
|
15
|
+
"form",
|
|
16
|
+
"button",
|
|
17
|
+
"input",
|
|
18
|
+
"select",
|
|
19
|
+
"textarea",
|
|
20
|
+
'[role="navigation"]',
|
|
21
|
+
'[role="banner"]',
|
|
22
|
+
'[role="contentinfo"]',
|
|
23
|
+
'[aria-hidden="true"]',
|
|
24
|
+
".sidebar",
|
|
25
|
+
".nav",
|
|
26
|
+
".menu",
|
|
27
|
+
".footer",
|
|
28
|
+
".header",
|
|
29
|
+
".ad",
|
|
30
|
+
".advertisement",
|
|
31
|
+
".cookie-banner",
|
|
32
|
+
".popup",
|
|
33
|
+
".modal"
|
|
34
|
+
];
|
|
35
|
+
const MAIN_CONTENT_SELECTORS = [
|
|
36
|
+
"article",
|
|
37
|
+
'[role="main"]',
|
|
38
|
+
"main",
|
|
39
|
+
".post-content",
|
|
40
|
+
".article-content",
|
|
41
|
+
".entry-content",
|
|
42
|
+
".content",
|
|
43
|
+
"#content",
|
|
44
|
+
".prose",
|
|
45
|
+
".markdown-body",
|
|
46
|
+
".documentation",
|
|
47
|
+
".doc-content"
|
|
48
|
+
];
|
|
49
|
+
async function webFetch(options) {
|
|
50
|
+
const {
|
|
51
|
+
url,
|
|
52
|
+
mode = "markdown",
|
|
53
|
+
selector,
|
|
54
|
+
maxLength = DEFAULT_MAX_LENGTH,
|
|
55
|
+
includeMetadata = true,
|
|
56
|
+
includeLinks = false,
|
|
57
|
+
includeImages = false,
|
|
58
|
+
timeout = DEFAULT_TIMEOUT
|
|
59
|
+
} = options;
|
|
60
|
+
const parsed = new URL(url);
|
|
61
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
62
|
+
throw new Error(`Unsupported protocol: ${parsed.protocol} \u2014 only http/https allowed`);
|
|
63
|
+
}
|
|
64
|
+
const controller = new AbortController();
|
|
65
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
66
|
+
let response;
|
|
67
|
+
try {
|
|
68
|
+
response = await fetch(url, {
|
|
69
|
+
signal: controller.signal,
|
|
70
|
+
headers: {
|
|
71
|
+
"User-Agent": "kb-web-fetch/1.0 (LLM context tool)",
|
|
72
|
+
Accept: "text/html,application/xhtml+xml,text/plain"
|
|
73
|
+
},
|
|
74
|
+
redirect: "follow"
|
|
75
|
+
});
|
|
76
|
+
} finally {
|
|
77
|
+
clearTimeout(timer);
|
|
78
|
+
}
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
81
|
+
}
|
|
82
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
83
|
+
const isHtml = /text\/html|application\/xhtml\+xml/i.test(contentType);
|
|
84
|
+
const responseText = await response.text();
|
|
85
|
+
const finalUrl = response.url;
|
|
86
|
+
if (!isHtml) {
|
|
87
|
+
let content2 = responseText;
|
|
88
|
+
const title2 = finalUrl.split("/").pop() ?? "";
|
|
89
|
+
if (includeMetadata) {
|
|
90
|
+
const meta = buildMetadataHeader(title2, "", finalUrl);
|
|
91
|
+
content2 = meta + content2;
|
|
92
|
+
}
|
|
93
|
+
const originalLength2 = content2.length;
|
|
94
|
+
const truncated2 = originalLength2 > maxLength;
|
|
95
|
+
if (truncated2) {
|
|
96
|
+
content2 = smartTruncate(content2, maxLength);
|
|
97
|
+
}
|
|
98
|
+
return { content: content2, title: title2, description: "", url: finalUrl, originalLength: originalLength2, truncated: truncated2 };
|
|
99
|
+
}
|
|
100
|
+
const { parseHTML } = await import("linkedom");
|
|
101
|
+
const { document } = parseHTML(responseText);
|
|
102
|
+
const title = document.querySelector("title")?.textContent?.trim() ?? document.querySelector('meta[property="og:title"]')?.getAttribute("content")?.trim() ?? "";
|
|
103
|
+
const description = document.querySelector('meta[name="description"]')?.getAttribute("content")?.trim() ?? document.querySelector('meta[property="og:description"]')?.getAttribute("content")?.trim() ?? "";
|
|
104
|
+
for (const sel of STRIP_ELEMENTS) {
|
|
105
|
+
for (const el of document.querySelectorAll(sel)) {
|
|
106
|
+
el.remove();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (!includeImages) {
|
|
110
|
+
for (const img of document.querySelectorAll("img")) {
|
|
111
|
+
img.remove();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
let contentEl = null;
|
|
115
|
+
if (selector) {
|
|
116
|
+
contentEl = document.querySelector(selector);
|
|
117
|
+
if (!contentEl) {
|
|
118
|
+
throw new Error(`Selector "${selector}" matched no elements on the page`);
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
for (const s of MAIN_CONTENT_SELECTORS) {
|
|
122
|
+
contentEl = document.querySelector(s);
|
|
123
|
+
if (contentEl) break;
|
|
124
|
+
}
|
|
125
|
+
if (!contentEl) {
|
|
126
|
+
contentEl = document.querySelector("body");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (!contentEl) {
|
|
130
|
+
return {
|
|
131
|
+
content: "(empty page \u2014 no content found)",
|
|
132
|
+
title,
|
|
133
|
+
description,
|
|
134
|
+
url: finalUrl,
|
|
135
|
+
originalLength: 0,
|
|
136
|
+
truncated: false
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const contentHtml = contentEl.innerHTML;
|
|
140
|
+
const links = [];
|
|
141
|
+
if (includeLinks || mode === "links") {
|
|
142
|
+
for (const a of contentEl.querySelectorAll("a[href]")) {
|
|
143
|
+
const text = a.textContent?.trim();
|
|
144
|
+
const href = a.getAttribute("href");
|
|
145
|
+
if (text && href && !href.startsWith("#") && !href.startsWith("javascript:")) {
|
|
146
|
+
links.push({ text, href: resolveUrl(href, finalUrl) });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
let content;
|
|
151
|
+
switch (mode) {
|
|
152
|
+
case "raw":
|
|
153
|
+
content = contentHtml;
|
|
154
|
+
break;
|
|
155
|
+
case "links":
|
|
156
|
+
content = formatLinks(links);
|
|
157
|
+
break;
|
|
158
|
+
case "outline":
|
|
159
|
+
content = extractOutline(contentEl);
|
|
160
|
+
break;
|
|
161
|
+
default:
|
|
162
|
+
content = htmlToMarkdown(contentHtml, includeImages);
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
if (includeMetadata && mode !== "links") {
|
|
166
|
+
const meta = buildMetadataHeader(title, description, finalUrl);
|
|
167
|
+
content = meta + content;
|
|
168
|
+
}
|
|
169
|
+
if (includeLinks && mode !== "links" && links.length > 0) {
|
|
170
|
+
content += `
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Links
|
|
175
|
+
|
|
176
|
+
${formatLinks(links)}`;
|
|
177
|
+
}
|
|
178
|
+
const originalLength = content.length;
|
|
179
|
+
const truncated = originalLength > maxLength;
|
|
180
|
+
if (truncated) {
|
|
181
|
+
content = smartTruncate(content, maxLength);
|
|
182
|
+
}
|
|
183
|
+
return { content, title, description, url: finalUrl, originalLength, truncated };
|
|
184
|
+
}
|
|
185
|
+
function htmlToMarkdown(html, includeImages) {
|
|
186
|
+
const td = new TurndownService({
|
|
187
|
+
headingStyle: "atx",
|
|
188
|
+
codeBlockStyle: "fenced",
|
|
189
|
+
bulletListMarker: "-"
|
|
190
|
+
});
|
|
191
|
+
td.addRule("emptyLinks", {
|
|
192
|
+
filter: (node) => node.nodeName === "A" && !node.textContent?.trim(),
|
|
193
|
+
replacement: () => ""
|
|
194
|
+
});
|
|
195
|
+
if (!includeImages) {
|
|
196
|
+
td.addRule("removeImages", {
|
|
197
|
+
filter: "img",
|
|
198
|
+
replacement: () => ""
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
let md = td.turndown(html);
|
|
202
|
+
md = md.replace(/\n{3,}/g, "\n\n").trim();
|
|
203
|
+
return md;
|
|
204
|
+
}
|
|
205
|
+
function extractOutline(element) {
|
|
206
|
+
const headings = element.querySelectorAll("h1, h2, h3, h4, h5, h6");
|
|
207
|
+
const lines = [];
|
|
208
|
+
for (const h of headings) {
|
|
209
|
+
const level = Number.parseInt(h.tagName.slice(1), 10);
|
|
210
|
+
const indent = " ".repeat(level - 1);
|
|
211
|
+
const text = h.textContent?.trim() ?? "";
|
|
212
|
+
if (text) {
|
|
213
|
+
lines.push(`${indent}- ${text}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return lines.length > 0 ? lines.join("\n") : "(no headings found)";
|
|
217
|
+
}
|
|
218
|
+
function formatLinks(links) {
|
|
219
|
+
if (links.length === 0) return "(no links found)";
|
|
220
|
+
const seen = /* @__PURE__ */ new Set();
|
|
221
|
+
const unique = [];
|
|
222
|
+
for (const link of links) {
|
|
223
|
+
if (!seen.has(link.href)) {
|
|
224
|
+
seen.add(link.href);
|
|
225
|
+
unique.push(link);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return unique.map((l) => `- [${l.text}](${l.href})`).join("\n");
|
|
229
|
+
}
|
|
230
|
+
function buildMetadataHeader(title, description, url) {
|
|
231
|
+
const parts = ["---"];
|
|
232
|
+
if (title) parts.push(`**${title}**`);
|
|
233
|
+
parts.push(`URL: ${url}`);
|
|
234
|
+
if (description) parts.push(`> ${description}`);
|
|
235
|
+
parts.push("---\n\n");
|
|
236
|
+
return parts.join("\n");
|
|
237
|
+
}
|
|
238
|
+
function resolveUrl(href, base) {
|
|
239
|
+
try {
|
|
240
|
+
return new URL(href, base).href;
|
|
241
|
+
} catch {
|
|
242
|
+
return href;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const smartTruncate = paragraphTruncate;
|
|
246
|
+
export {
|
|
247
|
+
webFetch
|
|
248
|
+
};
|
|
249
|
+
//# sourceMappingURL=web-fetch.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* kb_web_search — Search the web and return structured results.
|
|
3
|
+
*
|
|
4
|
+
* Uses DuckDuckGo HTML search (no API key required).
|
|
5
|
+
*/
|
|
6
|
+
export interface WebSearchOptions {
|
|
7
|
+
query: string;
|
|
8
|
+
limit?: number;
|
|
9
|
+
site?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface WebSearchResultItem {
|
|
12
|
+
title: string;
|
|
13
|
+
url: string;
|
|
14
|
+
snippet: string;
|
|
15
|
+
}
|
|
16
|
+
export interface WebSearchResult {
|
|
17
|
+
results: WebSearchResultItem[];
|
|
18
|
+
query: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function webSearch(options: WebSearchOptions): Promise<WebSearchResult>;
|
|
21
|
+
/** Parse search results from DuckDuckGo HTML. Exported for testing. */
|
|
22
|
+
export declare function parseSearchResults(html: string, query: string, limit: number): WebSearchResult;
|
|
23
|
+
//# sourceMappingURL=web-search.d.ts.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const DEFAULT_LIMIT = 5;
|
|
2
|
+
async function webSearch(options) {
|
|
3
|
+
const { query, limit = DEFAULT_LIMIT, site } = options;
|
|
4
|
+
if (!query.trim()) throw new Error("Search query cannot be empty");
|
|
5
|
+
const searchQuery = site ? `${query} site:${site}` : query;
|
|
6
|
+
const response = await fetch("https://html.duckduckgo.com/html/", {
|
|
7
|
+
method: "POST",
|
|
8
|
+
headers: {
|
|
9
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
10
|
+
"User-Agent": "kb-web-search/1.0 (LLM developer tool)"
|
|
11
|
+
},
|
|
12
|
+
body: `q=${encodeURIComponent(searchQuery)}`
|
|
13
|
+
});
|
|
14
|
+
if (!response.ok) {
|
|
15
|
+
throw new Error(`Search failed: HTTP ${response.status}`);
|
|
16
|
+
}
|
|
17
|
+
const html = await response.text();
|
|
18
|
+
return parseSearchResults(html, searchQuery, limit);
|
|
19
|
+
}
|
|
20
|
+
function parseSearchResults(html, query, limit) {
|
|
21
|
+
const results = [];
|
|
22
|
+
const blockRegex = /<a[^>]+class="result__a"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?<a[^>]+class="result__snippet"[^>]*>([\s\S]*?)<\/a>/gi;
|
|
23
|
+
let match = blockRegex.exec(html);
|
|
24
|
+
while (match !== null && results.length < limit) {
|
|
25
|
+
let url = match[1];
|
|
26
|
+
const title = stripHtml(match[2]).trim();
|
|
27
|
+
const snippet = stripHtml(match[3]).trim();
|
|
28
|
+
if (url.includes("uddg=")) {
|
|
29
|
+
const decoded = url.match(/uddg=([^&]+)/)?.[1];
|
|
30
|
+
if (decoded) url = decodeURIComponent(decoded);
|
|
31
|
+
}
|
|
32
|
+
if (title && url?.startsWith("http")) {
|
|
33
|
+
results.push({ title, url, snippet });
|
|
34
|
+
}
|
|
35
|
+
match = blockRegex.exec(html);
|
|
36
|
+
}
|
|
37
|
+
return { results, query };
|
|
38
|
+
}
|
|
39
|
+
function stripHtml(html) {
|
|
40
|
+
return html.replace(/<[^>]+>/g, "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'");
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
parseSearchResults,
|
|
44
|
+
webSearch
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=web-search.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* kb_workset — Named file set management.
|
|
3
|
+
*
|
|
4
|
+
* Persistent worksets stored in .kb-state/worksets.json.
|
|
5
|
+
* Use cases: "remember this set of files I'm working on",
|
|
6
|
+
* "load my previous context", "share file sets between sessions".
|
|
7
|
+
*/
|
|
8
|
+
export interface Workset {
|
|
9
|
+
name: string;
|
|
10
|
+
files: string[];
|
|
11
|
+
created: string;
|
|
12
|
+
updated: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface WorksetStore {
|
|
16
|
+
worksets: Record<string, Workset>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Create or update a workset.
|
|
20
|
+
*/
|
|
21
|
+
export declare function saveWorkset(name: string, files: string[], options?: {
|
|
22
|
+
description?: string;
|
|
23
|
+
cwd?: string;
|
|
24
|
+
}): Workset;
|
|
25
|
+
/**
|
|
26
|
+
* Get a workset by name.
|
|
27
|
+
*/
|
|
28
|
+
export declare function getWorkset(name: string, cwd?: string): Workset | null;
|
|
29
|
+
/**
|
|
30
|
+
* List all worksets.
|
|
31
|
+
*/
|
|
32
|
+
export declare function listWorksets(cwd?: string): Workset[];
|
|
33
|
+
/**
|
|
34
|
+
* Delete a workset.
|
|
35
|
+
*/
|
|
36
|
+
export declare function deleteWorkset(name: string, cwd?: string): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Add files to an existing workset (creates if not exists).
|
|
39
|
+
*/
|
|
40
|
+
export declare function addToWorkset(name: string, files: string[], cwd?: string): Workset;
|
|
41
|
+
/**
|
|
42
|
+
* Remove files from a workset.
|
|
43
|
+
*/
|
|
44
|
+
export declare function removeFromWorkset(name: string, files: string[], cwd?: string): Workset | null;
|
|
45
|
+
//# sourceMappingURL=workset.d.ts.map
|