@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.
Files changed (78) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +496 -0
  3. package/SKILL.md +154 -0
  4. package/apps/client/dist/assets/index-CqOXxsEZ.js +240 -0
  5. package/apps/client/dist/assets/index-DwvaANnI.css +1 -0
  6. package/apps/client/dist/index.html +13 -0
  7. package/apps/server/src/core/domain/entry.ts +22 -0
  8. package/apps/server/src/core/domain/errors.ts +27 -0
  9. package/apps/server/src/core/domain/knowledge-graph.ts +51 -0
  10. package/apps/server/src/core/domain/types.ts +168 -0
  11. package/apps/server/src/core/ports/IBuildRepository.ts +7 -0
  12. package/apps/server/src/core/ports/IDocumentNormalizer.ts +6 -0
  13. package/apps/server/src/core/ports/IDocumentStore.ts +4 -0
  14. package/apps/server/src/core/ports/IEntryReadModel.ts +9 -0
  15. package/apps/server/src/core/ports/IEntryRepository.ts +11 -0
  16. package/apps/server/src/core/ports/IKnowledgeGraph.ts +10 -0
  17. package/apps/server/src/core/ports/IPathResolver.ts +3 -0
  18. package/apps/server/src/core/ports/ISearchEngine.ts +9 -0
  19. package/apps/server/src/core/ports/ISourceProcessor.ts +7 -0
  20. package/apps/server/src/core/ports/ISourceRepository.ts +11 -0
  21. package/apps/server/src/core/usecases/BuildUseCase.ts +98 -0
  22. package/apps/server/src/core/usecases/ConfigUseCase.ts +64 -0
  23. package/apps/server/src/core/usecases/SearchUseCase.ts +16 -0
  24. package/apps/server/src/index.ts +98 -0
  25. package/apps/server/src/infrastructure/filesystem/FileSystemDocumentStore.ts +27 -0
  26. package/apps/server/src/infrastructure/graph/GraphSearchDecorator.ts +53 -0
  27. package/apps/server/src/infrastructure/graph/GraphifyKnowledgeGraph.ts +172 -0
  28. package/apps/server/src/infrastructure/graph/index.ts +2 -0
  29. package/apps/server/src/infrastructure/persistence/sqlite/SqliteBuildRepository.ts +34 -0
  30. package/apps/server/src/infrastructure/persistence/sqlite/SqliteEntryReadModel.ts +17 -0
  31. package/apps/server/src/infrastructure/persistence/sqlite/SqliteEntryRepository.ts +81 -0
  32. package/apps/server/src/infrastructure/persistence/sqlite/SqliteSourceRepository.ts +65 -0
  33. package/apps/server/src/infrastructure/persistence/sqlite/connection.ts +52 -0
  34. package/apps/server/src/infrastructure/search/SearchEngineFactory.ts +43 -0
  35. package/apps/server/src/infrastructure/search/json/JsonSearchEngine.ts +164 -0
  36. package/apps/server/src/infrastructure/search/vector/EmbeddingService.ts +23 -0
  37. package/apps/server/src/infrastructure/search/vector/VectorSearchEngine.ts +480 -0
  38. package/apps/server/src/infrastructure/source-processors/AntoraSourceProcessor.ts +14 -0
  39. package/apps/server/src/infrastructure/source-processors/AsciidocSourceProcessor.ts +12 -0
  40. package/apps/server/src/infrastructure/source-processors/DocumentNormalizer.ts +16 -0
  41. package/apps/server/src/infrastructure/source-processors/GithubMarkdownSourceProcessor.ts +12 -0
  42. package/apps/server/src/infrastructure/source-processors/MavenSourceProcessor.ts +12 -0
  43. package/apps/server/src/infrastructure/source-processors/PathResolver.ts +6 -0
  44. package/apps/server/src/infrastructure/source-processors/SourceCodeSourceProcessor.ts +260 -0
  45. package/apps/server/src/infrastructure/source-processors/ZipSourceProcessor.ts +12 -0
  46. package/apps/server/src/mcp-http.ts +102 -0
  47. package/apps/server/src/mcp.ts +432 -0
  48. package/apps/server/src/routes/build.ts +105 -0
  49. package/apps/server/src/routes/entries.ts +62 -0
  50. package/apps/server/src/routes/graph.ts +57 -0
  51. package/apps/server/src/routes/search.ts +28 -0
  52. package/apps/server/src/routes/sources.ts +105 -0
  53. package/apps/server/src/routes/viewer.ts +28 -0
  54. package/apps/server/src/services/antora.ts +238 -0
  55. package/apps/server/src/services/asciidoc.ts +221 -0
  56. package/apps/server/src/services/configLoader.ts +207 -0
  57. package/apps/server/src/services/githubMarkdown.ts +236 -0
  58. package/apps/server/src/services/maven.ts +178 -0
  59. package/apps/server/src/services/normalizer.ts +63 -0
  60. package/apps/server/src/services/paths.ts +5 -0
  61. package/apps/server/src/services/textExtractor.ts +49 -0
  62. package/apps/server/src/services/zip.ts +84 -0
  63. package/bin/commands/build.ts +85 -0
  64. package/bin/commands/dev.ts +36 -0
  65. package/bin/commands/get.ts +36 -0
  66. package/bin/commands/graph.ts +153 -0
  67. package/bin/commands/init.ts +170 -0
  68. package/bin/commands/list.ts +47 -0
  69. package/bin/commands/mcp.ts +32 -0
  70. package/bin/commands/search.ts +185 -0
  71. package/bin/commands/serve.ts +23 -0
  72. package/bin/commands/status.ts +46 -0
  73. package/bin/dockit-cli.ts +92 -0
  74. package/bin/dockit.js +17 -0
  75. package/bin/utils.ts +85 -0
  76. package/dockit.yaml +154 -0
  77. package/package.json +60 -0
  78. 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