@lon-ask/dockit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +674 -0
- package/README.md +496 -0
- package/SKILL.md +154 -0
- package/apps/client/dist/assets/index-CqOXxsEZ.js +240 -0
- package/apps/client/dist/assets/index-DwvaANnI.css +1 -0
- package/apps/client/dist/index.html +13 -0
- package/apps/server/src/core/domain/entry.ts +22 -0
- package/apps/server/src/core/domain/errors.ts +27 -0
- package/apps/server/src/core/domain/knowledge-graph.ts +51 -0
- package/apps/server/src/core/domain/types.ts +168 -0
- package/apps/server/src/core/ports/IBuildRepository.ts +7 -0
- package/apps/server/src/core/ports/IDocumentNormalizer.ts +6 -0
- package/apps/server/src/core/ports/IDocumentStore.ts +4 -0
- package/apps/server/src/core/ports/IEntryReadModel.ts +9 -0
- package/apps/server/src/core/ports/IEntryRepository.ts +11 -0
- package/apps/server/src/core/ports/IKnowledgeGraph.ts +10 -0
- package/apps/server/src/core/ports/IPathResolver.ts +3 -0
- package/apps/server/src/core/ports/ISearchEngine.ts +9 -0
- package/apps/server/src/core/ports/ISourceProcessor.ts +7 -0
- package/apps/server/src/core/ports/ISourceRepository.ts +11 -0
- package/apps/server/src/core/usecases/BuildUseCase.ts +98 -0
- package/apps/server/src/core/usecases/ConfigUseCase.ts +64 -0
- package/apps/server/src/core/usecases/SearchUseCase.ts +16 -0
- package/apps/server/src/index.ts +98 -0
- package/apps/server/src/infrastructure/filesystem/FileSystemDocumentStore.ts +27 -0
- package/apps/server/src/infrastructure/graph/GraphSearchDecorator.ts +53 -0
- package/apps/server/src/infrastructure/graph/GraphifyKnowledgeGraph.ts +172 -0
- package/apps/server/src/infrastructure/graph/index.ts +2 -0
- package/apps/server/src/infrastructure/persistence/sqlite/SqliteBuildRepository.ts +34 -0
- package/apps/server/src/infrastructure/persistence/sqlite/SqliteEntryReadModel.ts +17 -0
- package/apps/server/src/infrastructure/persistence/sqlite/SqliteEntryRepository.ts +81 -0
- package/apps/server/src/infrastructure/persistence/sqlite/SqliteSourceRepository.ts +65 -0
- package/apps/server/src/infrastructure/persistence/sqlite/connection.ts +52 -0
- package/apps/server/src/infrastructure/search/SearchEngineFactory.ts +43 -0
- package/apps/server/src/infrastructure/search/json/JsonSearchEngine.ts +164 -0
- package/apps/server/src/infrastructure/search/vector/EmbeddingService.ts +23 -0
- package/apps/server/src/infrastructure/search/vector/VectorSearchEngine.ts +480 -0
- package/apps/server/src/infrastructure/source-processors/AntoraSourceProcessor.ts +14 -0
- package/apps/server/src/infrastructure/source-processors/AsciidocSourceProcessor.ts +12 -0
- package/apps/server/src/infrastructure/source-processors/DocumentNormalizer.ts +16 -0
- package/apps/server/src/infrastructure/source-processors/GithubMarkdownSourceProcessor.ts +12 -0
- package/apps/server/src/infrastructure/source-processors/MavenSourceProcessor.ts +12 -0
- package/apps/server/src/infrastructure/source-processors/PathResolver.ts +6 -0
- package/apps/server/src/infrastructure/source-processors/SourceCodeSourceProcessor.ts +260 -0
- package/apps/server/src/infrastructure/source-processors/ZipSourceProcessor.ts +12 -0
- package/apps/server/src/mcp-http.ts +102 -0
- package/apps/server/src/mcp.ts +432 -0
- package/apps/server/src/routes/build.ts +105 -0
- package/apps/server/src/routes/entries.ts +62 -0
- package/apps/server/src/routes/graph.ts +57 -0
- package/apps/server/src/routes/search.ts +28 -0
- package/apps/server/src/routes/sources.ts +105 -0
- package/apps/server/src/routes/viewer.ts +28 -0
- package/apps/server/src/services/antora.ts +238 -0
- package/apps/server/src/services/asciidoc.ts +221 -0
- package/apps/server/src/services/configLoader.ts +207 -0
- package/apps/server/src/services/githubMarkdown.ts +236 -0
- package/apps/server/src/services/maven.ts +178 -0
- package/apps/server/src/services/normalizer.ts +63 -0
- package/apps/server/src/services/paths.ts +5 -0
- package/apps/server/src/services/textExtractor.ts +49 -0
- package/apps/server/src/services/zip.ts +84 -0
- package/bin/commands/build.ts +85 -0
- package/bin/commands/dev.ts +36 -0
- package/bin/commands/get.ts +36 -0
- package/bin/commands/graph.ts +153 -0
- package/bin/commands/init.ts +170 -0
- package/bin/commands/list.ts +47 -0
- package/bin/commands/mcp.ts +32 -0
- package/bin/commands/search.ts +185 -0
- package/bin/commands/serve.ts +23 -0
- package/bin/commands/status.ts +46 -0
- package/bin/dockit-cli.ts +92 -0
- package/bin/dockit.js +17 -0
- package/bin/utils.ts +85 -0
- package/dockit.yaml +154 -0
- package/package.json +60 -0
- package/scripts/mcp-wrapper.sh +44 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
export default function mcp(root, positional, flags) {
|
|
4
|
+
const httpPort = flags.port || process.env.DOCKIT_MCP_HTTP_PORT;
|
|
5
|
+
const useHttp = flags.http || !!httpPort;
|
|
6
|
+
|
|
7
|
+
if (useHttp) {
|
|
8
|
+
const port = httpPort || 3456;
|
|
9
|
+
console.log(`Starting Dockit MCP server (HTTP) on port ${port}...`);
|
|
10
|
+
const env = { ...process.env, DOCKIT_MCP_HTTP_PORT: String(port) };
|
|
11
|
+
|
|
12
|
+
const proc = spawn('npx', ['tsx', 'apps/server/src/mcp.ts'], {
|
|
13
|
+
cwd: root,
|
|
14
|
+
stdio: 'inherit',
|
|
15
|
+
shell: true,
|
|
16
|
+
env,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
process.on('SIGINT', () => proc.kill());
|
|
20
|
+
process.on('SIGTERM', () => proc.kill());
|
|
21
|
+
} else {
|
|
22
|
+
console.log('Starting Dockit MCP server (stdio)...');
|
|
23
|
+
const proc = spawn('npx', ['tsx', 'apps/server/src/mcp.ts'], {
|
|
24
|
+
cwd: root,
|
|
25
|
+
stdio: 'inherit',
|
|
26
|
+
shell: true,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
process.on('SIGINT', () => proc.kill());
|
|
30
|
+
process.on('SIGTERM', () => proc.kill());
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { resolveConfigPath } from '../utils.js';
|
|
4
|
+
|
|
5
|
+
export default async function search(root, positional, flags) {
|
|
6
|
+
let entryId = flags.entry || null;
|
|
7
|
+
let query = positional[0];
|
|
8
|
+
|
|
9
|
+
if (positional.length >= 2 && !flags.entry) {
|
|
10
|
+
entryId = positional[0];
|
|
11
|
+
query = positional.slice(1).join(' ');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (!query) {
|
|
15
|
+
console.error('Error: search query is required');
|
|
16
|
+
console.error('Usage: dockit search [<entry>] <query> [--json] [--limit <n>] [--get-top <N>]');
|
|
17
|
+
console.error('');
|
|
18
|
+
console.error('Examples:');
|
|
19
|
+
console.error(' dockit search react "how to create a hook"');
|
|
20
|
+
console.error(' dockit search quarkus "configure cache"');
|
|
21
|
+
console.error(' dockit search "react hooks"');
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const limit = parseInt(flags.limit || '20', 10);
|
|
26
|
+
const asJson = !!flags.json;
|
|
27
|
+
const getTop = flags['get-top'] === true ? 3 : parseInt(String(flags['get-top']), 10);
|
|
28
|
+
|
|
29
|
+
const { getDb } = await import(path.join(root, 'apps/server/src/infrastructure/persistence/sqlite/connection.js'));
|
|
30
|
+
const { SqliteEntryRepository } = await import(path.join(root, 'apps/server/src/infrastructure/persistence/sqlite/SqliteEntryRepository.js'));
|
|
31
|
+
const { SqliteSourceRepository } = await import(path.join(root, 'apps/server/src/infrastructure/persistence/sqlite/SqliteSourceRepository.js'));
|
|
32
|
+
const { SqliteBuildRepository } = await import(path.join(root, 'apps/server/src/infrastructure/persistence/sqlite/SqliteBuildRepository.js'));
|
|
33
|
+
const { SqliteEntryReadModel } = await import(path.join(root, 'apps/server/src/infrastructure/persistence/sqlite/SqliteEntryReadModel.js'));
|
|
34
|
+
const { createSearchEngine } = await import(path.join(root, 'apps/server/src/infrastructure/search/SearchEngineFactory.js'));
|
|
35
|
+
const { SearchUseCase } = await import(path.join(root, 'apps/server/src/core/usecases/SearchUseCase.js'));
|
|
36
|
+
const { loadConfig, syncConfigToDb } = await import(path.join(root, 'apps/server/src/services/configLoader.js'));
|
|
37
|
+
const { DATA_ROOT } = await import(path.join(root, 'apps/server/src/services/paths.js'));
|
|
38
|
+
const { extractTextFromHtml } = await import(path.join(root, 'apps/server/src/services/textExtractor.js'));
|
|
39
|
+
|
|
40
|
+
// Ensure DB is initialized
|
|
41
|
+
const db = getDb();
|
|
42
|
+
|
|
43
|
+
// Sync config to DB
|
|
44
|
+
const configPath = resolveConfigPath(root);
|
|
45
|
+
const config = loadConfig(configPath);
|
|
46
|
+
|
|
47
|
+
const entryRepo = new SqliteEntryRepository(db);
|
|
48
|
+
const sourceRepo = new SqliteSourceRepository(db);
|
|
49
|
+
await syncConfigToDb(config, entryRepo, sourceRepo);
|
|
50
|
+
|
|
51
|
+
const entryReadModel = new SqliteEntryReadModel(db);
|
|
52
|
+
const searchEngine = await createSearchEngine(entryReadModel, config.search?.engine);
|
|
53
|
+
const searchUseCase = new SearchUseCase(searchEngine);
|
|
54
|
+
|
|
55
|
+
let results: any[] = [];
|
|
56
|
+
let scoped = false;
|
|
57
|
+
|
|
58
|
+
if (entryId) {
|
|
59
|
+
scoped = true;
|
|
60
|
+
const entry = await entryRepo.findById(entryId);
|
|
61
|
+
if (!entry || entry.status !== 'ready') {
|
|
62
|
+
console.error(`Entry "${entryId}" not found or not built.`);
|
|
63
|
+
console.error('Run "dockit list" to see available entries.');
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
results = await searchUseCase.searchEntry(entryId, query, limit);
|
|
67
|
+
results = results.map((r) => ({
|
|
68
|
+
entryId,
|
|
69
|
+
...r,
|
|
70
|
+
}));
|
|
71
|
+
} else {
|
|
72
|
+
results = await searchUseCase.globalSearch(query, limit);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (getTop) {
|
|
76
|
+
for (let i = 0; i < Math.min(getTop, results.length); i++) {
|
|
77
|
+
const r = results[i];
|
|
78
|
+
const filePath = path.join(DATA_ROOT, r.entryId, 'bundle', r.path);
|
|
79
|
+
if (fs.existsSync(filePath)) {
|
|
80
|
+
const html = fs.readFileSync(filePath, 'utf-8');
|
|
81
|
+
r.content = extractTextFromHtml(html);
|
|
82
|
+
} else {
|
|
83
|
+
r.content = null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (asJson) {
|
|
89
|
+
if (getTop) {
|
|
90
|
+
const output = results.slice(0, getTop).map((r, i) => ({
|
|
91
|
+
rank: i + 1,
|
|
92
|
+
entryId: r.entryId,
|
|
93
|
+
entryName: r.entryName,
|
|
94
|
+
title: r.title,
|
|
95
|
+
path: r.path,
|
|
96
|
+
content: r.content,
|
|
97
|
+
}));
|
|
98
|
+
console.log(JSON.stringify(output, null, 2));
|
|
99
|
+
} else {
|
|
100
|
+
console.log(JSON.stringify(results, null, 2));
|
|
101
|
+
}
|
|
102
|
+
} else if (results.length === 0) {
|
|
103
|
+
console.log('No results found.');
|
|
104
|
+
} else {
|
|
105
|
+
const scope = entryId ? ` [${entryId}]` : '';
|
|
106
|
+
console.log(`Search${scope}: "${query}" — ${results.length} result(s)\n`);
|
|
107
|
+
|
|
108
|
+
if (getTop) {
|
|
109
|
+
console.log(`Showing full content for top ${Math.min(getTop, results.length)} result(s).\n`);
|
|
110
|
+
|
|
111
|
+
for (let i = 0; i < Math.min(getTop, results.length); i++) {
|
|
112
|
+
const r = results[i];
|
|
113
|
+
const entryLabel = r.entryName || r.entryId;
|
|
114
|
+
const title = r.title || '(no title)';
|
|
115
|
+
|
|
116
|
+
console.log(`[${i + 1}] [${entryLabel}] ${title}`);
|
|
117
|
+
console.log(` Path: ${r.path || ''}`);
|
|
118
|
+
console.log(' ─────────────────────────────────────────────');
|
|
119
|
+
if (r.content) {
|
|
120
|
+
console.log(r.content);
|
|
121
|
+
} else {
|
|
122
|
+
console.log(' (content not available — entry may not be built)');
|
|
123
|
+
}
|
|
124
|
+
console.log(' ─────────────────────────────────────────────');
|
|
125
|
+
console.log('');
|
|
126
|
+
}
|
|
127
|
+
} else if (!scoped) {
|
|
128
|
+
// Global search: show top result per entry with hint to scope
|
|
129
|
+
for (const r of results) {
|
|
130
|
+
const entryLabel = r.entryName || r.entryId;
|
|
131
|
+
const title = r.title || '(no title)';
|
|
132
|
+
const snippet = r.snippet ? `\n ${extractSnippet(query, r.snippet)}` : '';
|
|
133
|
+
|
|
134
|
+
console.log(`[${entryLabel}] ${title}`);
|
|
135
|
+
console.log(` Path: ${r.path || ''}${snippet}`);
|
|
136
|
+
console.log('');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
console.log(`Use 'dockit search <entry> "<query>"' to search within a specific entry.`);
|
|
140
|
+
console.log(`Use 'dockit search <entry> "<query>" --get-top' to fetch full content.`);
|
|
141
|
+
} else {
|
|
142
|
+
for (const r of results) {
|
|
143
|
+
const entryLabel = r.entryName || r.entryId;
|
|
144
|
+
const title = r.title || '(no title)';
|
|
145
|
+
const headings = r.headings && r.headings.length > 0 ? `\n Headings: ${r.headings.slice(0, 3).join(' > ')}` : '';
|
|
146
|
+
const snippet = r.snippet ? `\n ${extractSnippet(query, r.snippet)}` : '';
|
|
147
|
+
|
|
148
|
+
console.log(`[${entryLabel}] ${title}`);
|
|
149
|
+
console.log(` Path: ${r.path || ''}${headings}${snippet}`);
|
|
150
|
+
console.log('');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
console.log(`Use 'dockit search ${entryId} "<query>" --get-top [N]' to fetch full content.`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function extractSnippet(query: string, fullSnippet: string, contextLength: number = 150): string {
|
|
159
|
+
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
160
|
+
const lower = fullSnippet.toLowerCase();
|
|
161
|
+
|
|
162
|
+
let bestStart = 0;
|
|
163
|
+
let bestScore = 0;
|
|
164
|
+
|
|
165
|
+
for (const term of terms) {
|
|
166
|
+
const idx = lower.indexOf(term);
|
|
167
|
+
if (idx !== -1) {
|
|
168
|
+
const start = Math.max(0, idx - 30);
|
|
169
|
+
const score = terms.filter((t) => lower.slice(start, start + contextLength).includes(t)).length;
|
|
170
|
+
if (score > bestScore) {
|
|
171
|
+
bestScore = score;
|
|
172
|
+
bestStart = start;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const start = Math.max(0, bestStart);
|
|
178
|
+
const end = Math.min(fullSnippet.length, start + contextLength);
|
|
179
|
+
let snippet = fullSnippet.slice(start, end).replace(/\s+/g, ' ').trim();
|
|
180
|
+
|
|
181
|
+
if (start > 0) snippet = '...' + snippet;
|
|
182
|
+
if (end < fullSnippet.length) snippet = snippet + '...';
|
|
183
|
+
|
|
184
|
+
return snippet;
|
|
185
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
export default function serve(root, positional, flags) {
|
|
4
|
+
const port = flags.port || process.env.PORT || 3001;
|
|
5
|
+
console.log(`Starting Dockit production server on port ${port}...`);
|
|
6
|
+
console.log('');
|
|
7
|
+
|
|
8
|
+
const env = { ...process.env, PORT: String(port) };
|
|
9
|
+
|
|
10
|
+
const server = spawn('npm', ['run', 'start', '-w', 'apps/server'], {
|
|
11
|
+
cwd: root,
|
|
12
|
+
stdio: 'inherit',
|
|
13
|
+
shell: true,
|
|
14
|
+
env,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
process.on('SIGINT', () => server.kill());
|
|
18
|
+
process.on('SIGTERM', () => server.kill());
|
|
19
|
+
|
|
20
|
+
server.on('close', (code) => {
|
|
21
|
+
console.log(`Server exited with code ${code}`);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
export default async function status(root, positional, flags) {
|
|
4
|
+
const entryId = positional[0];
|
|
5
|
+
if (!entryId) {
|
|
6
|
+
console.error('Error: entry ID is required');
|
|
7
|
+
console.error('Usage: dockit status <entry>');
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const asJson = !!flags.json;
|
|
12
|
+
|
|
13
|
+
const { getDb } = await import(path.join(root, 'apps/server/src/infrastructure/persistence/sqlite/connection.js'));
|
|
14
|
+
const { SqliteBuildRepository } = await import(path.join(root, 'apps/server/src/infrastructure/persistence/sqlite/SqliteBuildRepository.js'));
|
|
15
|
+
|
|
16
|
+
const db = getDb();
|
|
17
|
+
const buildRepo = new SqliteBuildRepository(db);
|
|
18
|
+
const build = await buildRepo.findLatest(entryId);
|
|
19
|
+
|
|
20
|
+
if (!build) {
|
|
21
|
+
if (asJson) {
|
|
22
|
+
console.log(JSON.stringify({ status: 'none', message: 'No builds found' }, null, 2));
|
|
23
|
+
} else {
|
|
24
|
+
console.log(`No builds found for entry: ${entryId}`);
|
|
25
|
+
}
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (asJson) {
|
|
30
|
+
console.log(JSON.stringify({
|
|
31
|
+
status: build.status,
|
|
32
|
+
startedAt: build.started_at,
|
|
33
|
+
finishedAt: build.finished_at,
|
|
34
|
+
log: build.log.slice(-2000),
|
|
35
|
+
}, null, 2));
|
|
36
|
+
} else {
|
|
37
|
+
console.log(`Entry: ${entryId}`);
|
|
38
|
+
console.log(`Status: ${build.status}`);
|
|
39
|
+
console.log(`Started: ${build.started_at}`);
|
|
40
|
+
console.log(`Finished: ${build.finished_at || 'N/A'}`);
|
|
41
|
+
console.log('');
|
|
42
|
+
console.log('Last 2000 chars of build log:');
|
|
43
|
+
console.log('---');
|
|
44
|
+
console.log(build.log.slice(-2000));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { parseArgs, resolveProjectRoot } from './utils.js';
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
const { command, positional, flags } = parseArgs(process.argv);
|
|
10
|
+
|
|
11
|
+
const commands = {
|
|
12
|
+
dev: () => import('./commands/dev.js'),
|
|
13
|
+
serve: () => import('./commands/serve.js'),
|
|
14
|
+
mcp: () => import('./commands/mcp.js'),
|
|
15
|
+
search: () => import('./commands/search.js'),
|
|
16
|
+
list: () => import('./commands/list.js'),
|
|
17
|
+
build: () => import('./commands/build.js'),
|
|
18
|
+
status: () => import('./commands/status.js'),
|
|
19
|
+
get: () => import('./commands/get.js'),
|
|
20
|
+
graph: () => import('./commands/graph.js'),
|
|
21
|
+
init: () => import('./commands/init.js'),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
if (command && commands[command]) {
|
|
25
|
+
const root = resolveProjectRoot();
|
|
26
|
+
const mod = await commands[command]();
|
|
27
|
+
mod.default(root, positional, flags);
|
|
28
|
+
} else if (command === 'help' || command === '--help' || command === '-h') {
|
|
29
|
+
showHelp();
|
|
30
|
+
} else if (command) {
|
|
31
|
+
console.error(`Unknown command: ${command}\n`);
|
|
32
|
+
showHelp();
|
|
33
|
+
process.exit(1);
|
|
34
|
+
} else {
|
|
35
|
+
showHelp();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function showHelp() {
|
|
39
|
+
console.log(`
|
|
40
|
+
Dockit - Local documentation hub
|
|
41
|
+
|
|
42
|
+
Usage:
|
|
43
|
+
dockit <command> [options]
|
|
44
|
+
|
|
45
|
+
Commands:
|
|
46
|
+
dev Start dev servers (client + server)
|
|
47
|
+
serve [--port <port>] Start production server
|
|
48
|
+
mcp [--http] [--port <port>] Start MCP server
|
|
49
|
+
search [<entry>] <query> Search documentation (scoped to entry if provided)
|
|
50
|
+
Without entry: shows top result per entry to help pick the right one
|
|
51
|
+
With entry: shows all matching results within that entry
|
|
52
|
+
--limit <n> Max results (default 20)
|
|
53
|
+
--get-top [N] Fetch full content for top N results (default 3)
|
|
54
|
+
--json Output as JSON
|
|
55
|
+
list List all documentation entries
|
|
56
|
+
--json Output as JSON
|
|
57
|
+
build <entry> Build documentation for an entry
|
|
58
|
+
status <entry> Check build status
|
|
59
|
+
--json Output as JSON
|
|
60
|
+
get <entry> <path> Fetch full document content
|
|
61
|
+
--json Output as JSON
|
|
62
|
+
graph query <entry> <query> Search knowledge graph nodes
|
|
63
|
+
--limit <n> Max results (default 20)
|
|
64
|
+
--json Output as JSON
|
|
65
|
+
graph path <entry> <from> <to> Find shortest path between two nodes
|
|
66
|
+
--json Output as JSON
|
|
67
|
+
graph gods <entry> List most connected (god) nodes
|
|
68
|
+
--limit <n> Max nodes (default 10)
|
|
69
|
+
--json Output as JSON
|
|
70
|
+
graph explain <entry> <node> Show node details and connections
|
|
71
|
+
--json Output as JSON
|
|
72
|
+
init [--path <dir>] [--name <name>] Initialize a project as a dockit source
|
|
73
|
+
--path <dir> Path to source directory (default: .)
|
|
74
|
+
--name <name> Entry name (default: directory name)
|
|
75
|
+
--version <ver> Version string (default: 1.0)
|
|
76
|
+
--code-path <path> Subdirectory for source code scanning (optional)
|
|
77
|
+
|
|
78
|
+
Examples:
|
|
79
|
+
dockit search react "how to create a hook"
|
|
80
|
+
dockit search quarkus "configure cache"
|
|
81
|
+
dockit search "react hooks" --get-top
|
|
82
|
+
dockit search "react hooks" --get-top 3 --json
|
|
83
|
+
dockit list
|
|
84
|
+
dockit build quarkus
|
|
85
|
+
dockit status quarkus
|
|
86
|
+
dockit get react asciidoc/getting-started.html
|
|
87
|
+
dockit graph query dockit-code "SourceCodeSourceProcessor"
|
|
88
|
+
dockit graph gods dockit-code
|
|
89
|
+
dockit graph path dockit-code "BuildUseCase" "SourceCodeSourceProcessor"
|
|
90
|
+
dockit serve --port 8080
|
|
91
|
+
`);
|
|
92
|
+
}
|
package/bin/dockit.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const projectRoot = path.resolve(__dirname, '..');
|
|
9
|
+
|
|
10
|
+
const proc = spawn('npx', ['tsx', path.join(projectRoot, 'bin/dockit-cli.ts'), ...process.argv.slice(2)], {
|
|
11
|
+
cwd: projectRoot,
|
|
12
|
+
stdio: 'inherit',
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
proc.on('close', (code) => {
|
|
16
|
+
process.exit(code || 0);
|
|
17
|
+
});
|
package/bin/utils.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import { spawn } from 'node:child_process';
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
export function resolveProjectRoot() {
|
|
10
|
+
let dir = __dirname;
|
|
11
|
+
while (dir !== path.parse(dir).root) {
|
|
12
|
+
if (fs.existsSync(path.join(dir, 'package.json'))) {
|
|
13
|
+
return dir;
|
|
14
|
+
}
|
|
15
|
+
dir = path.dirname(dir);
|
|
16
|
+
}
|
|
17
|
+
throw new Error('Could not find dockit project root');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function resolveDockitHome(): string {
|
|
21
|
+
const home = os.homedir() || process.cwd();
|
|
22
|
+
return process.env.DOCKIT_DATA_DIR || path.join(home, '.dockit');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function resolveConfigPath(root: string): string {
|
|
26
|
+
const homeConfig = path.join(resolveDockitHome(), 'dockit.yaml');
|
|
27
|
+
if (fs.existsSync(homeConfig)) return homeConfig;
|
|
28
|
+
const projectConfig = path.join(root, 'dockit.yaml');
|
|
29
|
+
if (fs.existsSync(projectConfig)) return projectConfig;
|
|
30
|
+
return homeConfig;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function parseArgs(argv) {
|
|
34
|
+
const args = argv.slice(2);
|
|
35
|
+
const flags = {};
|
|
36
|
+
const positional = [];
|
|
37
|
+
|
|
38
|
+
for (let i = 0; i < args.length; i++) {
|
|
39
|
+
const arg = args[i];
|
|
40
|
+
if (arg.startsWith('--')) {
|
|
41
|
+
const key = arg.slice(2);
|
|
42
|
+
const next = args[i + 1];
|
|
43
|
+
if (next && !next.startsWith('--')) {
|
|
44
|
+
flags[key] = next;
|
|
45
|
+
i++;
|
|
46
|
+
} else {
|
|
47
|
+
flags[key] = true;
|
|
48
|
+
}
|
|
49
|
+
} else if (arg.startsWith('-')) {
|
|
50
|
+
const key = arg.slice(1);
|
|
51
|
+
flags[key] = true;
|
|
52
|
+
} else {
|
|
53
|
+
positional.push(arg);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { command: positional[0] || null, positional: positional.slice(1), flags };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function formatTable(headers, rows) {
|
|
61
|
+
const widths = headers.map((h, i) =>
|
|
62
|
+
Math.max(h.length, ...rows.map((r) => String(r[i] || '').length))
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const pad = (str, width) => String(str).padEnd(width);
|
|
66
|
+
|
|
67
|
+
const headerLine = headers.map((h, i) => pad(h, widths[i])).join(' ');
|
|
68
|
+
const separator = widths.map((w) => '─'.repeat(w)).join(' ');
|
|
69
|
+
const dataLines = rows.map((row) =>
|
|
70
|
+
row.map((cell, i) => pad(cell || '', widths[i])).join(' ')
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
return [headerLine, separator, ...dataLines].join('\n');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function spawnProcess(cmd, args, cwd) {
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
const proc = spawn(cmd, args, { cwd, stdio: 'inherit', shell: true });
|
|
79
|
+
proc.on('close', (code) => {
|
|
80
|
+
if (code === 0) resolve();
|
|
81
|
+
else reject(new Error(`Process exited with code ${code}`));
|
|
82
|
+
});
|
|
83
|
+
proc.on('error', reject);
|
|
84
|
+
});
|
|
85
|
+
}
|
package/dockit.yaml
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Dockit - Documentation index for LLM context
|
|
2
|
+
# Define your documentation entries and their sources below.
|
|
3
|
+
# Run `npx tsx apps/server/src/mcp.ts` to start the MCP server.
|
|
4
|
+
|
|
5
|
+
entries:
|
|
6
|
+
- id: quarkus
|
|
7
|
+
name: Quarkus
|
|
8
|
+
version: "3.35"
|
|
9
|
+
description: "Quarkus - Supersonic Subatomic Java"
|
|
10
|
+
sources:
|
|
11
|
+
- type: asciidoc
|
|
12
|
+
label: "Quarkus Docs (AsciiDoc)"
|
|
13
|
+
repoUrl: "https://github.com/quarkusio/quarkus.git"
|
|
14
|
+
sourcePath: "docs/src/main/asciidoc"
|
|
15
|
+
|
|
16
|
+
- id: quarkus-core
|
|
17
|
+
name: Quarkus Core
|
|
18
|
+
version: "3.35.2"
|
|
19
|
+
description: "Quarkus Core API"
|
|
20
|
+
sources:
|
|
21
|
+
- type: maven
|
|
22
|
+
label: "Quarkus Core Javadoc"
|
|
23
|
+
groupId: "io.quarkus"
|
|
24
|
+
artifactId: "quarkus-core"
|
|
25
|
+
version: "3.35.2"
|
|
26
|
+
classifier: "javadoc"
|
|
27
|
+
|
|
28
|
+
- id: spring-boot
|
|
29
|
+
name: Spring Boot
|
|
30
|
+
version: "3.5.x"
|
|
31
|
+
description: "Spring Boot - Production-ready Spring applications"
|
|
32
|
+
sources:
|
|
33
|
+
- type: antora
|
|
34
|
+
label: "Spring Boot Reference Docs"
|
|
35
|
+
repoUrl: "https://github.com/spring-projects/spring-boot.git"
|
|
36
|
+
|
|
37
|
+
- id: spring-framework
|
|
38
|
+
name: Spring Framework
|
|
39
|
+
version: "7.x"
|
|
40
|
+
description: "Spring Framework - The core of the Spring ecosystem"
|
|
41
|
+
sources:
|
|
42
|
+
- type: antora
|
|
43
|
+
label: "Spring Framework Reference Docs"
|
|
44
|
+
repoUrl: "https://github.com/spring-projects/spring-framework.git"
|
|
45
|
+
|
|
46
|
+
- id: react
|
|
47
|
+
name: React
|
|
48
|
+
version: "19"
|
|
49
|
+
description: "React - The library for web and native user interfaces"
|
|
50
|
+
sources:
|
|
51
|
+
- type: github-markdown
|
|
52
|
+
label: "React Docs (Markdown)"
|
|
53
|
+
repoUrl: "https://github.com/reactjs/react.dev.git"
|
|
54
|
+
sourcePath: "src/content"
|
|
55
|
+
|
|
56
|
+
# --- Local/Offline mode examples (uncomment to use) ---
|
|
57
|
+
|
|
58
|
+
# - id: quarkus-local
|
|
59
|
+
# name: Quarkus (Local Clone)
|
|
60
|
+
# version: "3.35"
|
|
61
|
+
# description: "Quarkus docs from a pre-cloned repo"
|
|
62
|
+
# sources:
|
|
63
|
+
# - type: asciidoc
|
|
64
|
+
# label: "Quarkus Docs (local)"
|
|
65
|
+
# localPath: "/home/user/repos/quarkus"
|
|
66
|
+
# sourcePath: "docs/src/main/asciidoc"
|
|
67
|
+
|
|
68
|
+
# - id: quarkus-mvn
|
|
69
|
+
# name: Quarkus Core (Maven CLI)
|
|
70
|
+
# version: "3.35.2"
|
|
71
|
+
# description: "Quarkus Core via local Maven + settings.xml"
|
|
72
|
+
# sources:
|
|
73
|
+
# - type: maven
|
|
74
|
+
# label: "Quarkus Core Javadoc"
|
|
75
|
+
# groupId: "io.quarkus"
|
|
76
|
+
# artifactId: "quarkus-core"
|
|
77
|
+
# version: "3.35.2"
|
|
78
|
+
# useMavenCommand: true
|
|
79
|
+
|
|
80
|
+
# - id: spring-boot-local
|
|
81
|
+
# name: Spring Boot (Local)
|
|
82
|
+
# version: "3.4.x"
|
|
83
|
+
# description: "Spring Boot from a pre-cloned repo"
|
|
84
|
+
# sources:
|
|
85
|
+
# - type: antora
|
|
86
|
+
# label: "Spring Boot Reference"
|
|
87
|
+
# localPath: "/home/user/repos/spring-boot"
|
|
88
|
+
# - type: asciidoc
|
|
89
|
+
# label: "Spring Boot Docs"
|
|
90
|
+
# localPath: "/home/user/repos/spring-boot"
|
|
91
|
+
# sourcePath: "spring-boot-project/spring-boot-docs/src/docs/asciidoc"
|
|
92
|
+
|
|
93
|
+
# - id: company-docs
|
|
94
|
+
# name: Company Docs
|
|
95
|
+
# version: "1.0"
|
|
96
|
+
# description: "Internal documentation"
|
|
97
|
+
# sources:
|
|
98
|
+
# - type: zip
|
|
99
|
+
# label: "Docs ZIP"
|
|
100
|
+
# localPath: "/home/user/downloads/docs.zip"
|
|
101
|
+
|
|
102
|
+
# - id: react-local
|
|
103
|
+
# name: React (Local)
|
|
104
|
+
# version: "19"
|
|
105
|
+
# description: "React docs from a pre-cloned repo"
|
|
106
|
+
# sources:
|
|
107
|
+
# - type: github-markdown
|
|
108
|
+
# label: "React Docs (local)"
|
|
109
|
+
# localPath: "/home/user/repos/react.dev"
|
|
110
|
+
# sourcePath: "src/content"
|
|
111
|
+
|
|
112
|
+
# --- Source code entries (powered by Graphify) ---
|
|
113
|
+
|
|
114
|
+
- id: quarkus-code
|
|
115
|
+
name: Quarkus Source Code
|
|
116
|
+
version: "3.35"
|
|
117
|
+
description: "Quarkus framework source code — knowledge graph"
|
|
118
|
+
sources:
|
|
119
|
+
- type: source-code
|
|
120
|
+
label: "Quarkus Core"
|
|
121
|
+
repoUrl: "https://github.com/quarkusio/quarkus.git"
|
|
122
|
+
sourcePath: "core/src/main/java"
|
|
123
|
+
|
|
124
|
+
- id: quarkus-combined
|
|
125
|
+
name: Quarkus (Docs + Code)
|
|
126
|
+
version: "3.35"
|
|
127
|
+
description: "Quarkus with searchable docs and code knowledge graph"
|
|
128
|
+
sources:
|
|
129
|
+
- type: asciidoc
|
|
130
|
+
label: "Quarkus Docs"
|
|
131
|
+
repoUrl: "https://github.com/quarkusio/quarkus.git"
|
|
132
|
+
sourcePath: "docs/src/main/asciidoc"
|
|
133
|
+
- type: source-code
|
|
134
|
+
label: "Quarkus Core"
|
|
135
|
+
repoUrl: "https://github.com/quarkusio/quarkus.git"
|
|
136
|
+
sourcePath: "core/src/main/java"
|
|
137
|
+
|
|
138
|
+
- id: dockit-code
|
|
139
|
+
name: Dockit Source Code
|
|
140
|
+
version: "1.0"
|
|
141
|
+
description: "Dockit project source code — knowledge graph"
|
|
142
|
+
sources:
|
|
143
|
+
- type: source-code
|
|
144
|
+
label: "Dockit Code"
|
|
145
|
+
localPath: "/home/karthik/projects/ai/dockit"
|
|
146
|
+
sourcePath: "apps"
|
|
147
|
+
|
|
148
|
+
search:
|
|
149
|
+
engine: vector # 'vector' (default, semantic search) | 'json' (fallback, low-resource)
|
|
150
|
+
|
|
151
|
+
mcp:
|
|
152
|
+
toolPrefix: "dockit_"
|
|
153
|
+
maxSearchResults: 10
|
|
154
|
+
autoBuild: false
|