docs-hub-mcp 1.0.14
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/README.md +171 -0
- package/dhm.js +2 -0
- package/dist/cli/commands.d.ts +11 -0
- package/dist/cli/commands.d.ts.map +1 -0
- package/dist/cli/commands.js +112 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/config.d.ts +120 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +76 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/index/builder.d.ts +25 -0
- package/dist/index/builder.d.ts.map +1 -0
- package/dist/index/builder.js +40 -0
- package/dist/index/builder.js.map +1 -0
- package/dist/index/parser.d.ts +20 -0
- package/dist/index/parser.d.ts.map +1 -0
- package/dist/index/parser.js +162 -0
- package/dist/index/parser.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +92 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/server.d.ts +3 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +97 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +57 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +70 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/search/cache.d.ts +12 -0
- package/dist/search/cache.d.ts.map +1 -0
- package/dist/search/cache.js +36 -0
- package/dist/search/cache.js.map +1 -0
- package/dist/search/exact.d.ts +3 -0
- package/dist/search/exact.d.ts.map +1 -0
- package/dist/search/exact.js +56 -0
- package/dist/search/exact.js.map +1 -0
- package/dist/search/graph.d.ts +6 -0
- package/dist/search/graph.d.ts.map +1 -0
- package/dist/search/graph.js +137 -0
- package/dist/search/graph.js.map +1 -0
- package/dist/search/pipeline.d.ts +18 -0
- package/dist/search/pipeline.d.ts.map +1 -0
- package/dist/search/pipeline.js +99 -0
- package/dist/search/pipeline.js.map +1 -0
- package/dist/search/semantic.d.ts +4 -0
- package/dist/search/semantic.d.ts.map +1 -0
- package/dist/search/semantic.js +120 -0
- package/dist/search/semantic.js.map +1 -0
- package/dist/sync/base.d.ts +21 -0
- package/dist/sync/base.d.ts.map +1 -0
- package/dist/sync/base.js +8 -0
- package/dist/sync/base.js.map +1 -0
- package/dist/sync/confluence.d.ts +3 -0
- package/dist/sync/confluence.d.ts.map +1 -0
- package/dist/sync/confluence.js +111 -0
- package/dist/sync/confluence.js.map +1 -0
- package/dist/sync/git-wiki.d.ts +3 -0
- package/dist/sync/git-wiki.d.ts.map +1 -0
- package/dist/sync/git-wiki.js +103 -0
- package/dist/sync/git-wiki.js.map +1 -0
- package/dist/sync/google-docs.d.ts +3 -0
- package/dist/sync/google-docs.d.ts.map +1 -0
- package/dist/sync/google-docs.js +87 -0
- package/dist/sync/google-docs.js.map +1 -0
- package/dist/sync/notion.d.ts +3 -0
- package/dist/sync/notion.d.ts.map +1 -0
- package/dist/sync/notion.js +126 -0
- package/dist/sync/notion.js.map +1 -0
- package/dist/sync/slack.d.ts +3 -0
- package/dist/sync/slack.d.ts.map +1 -0
- package/dist/sync/slack.js +92 -0
- package/dist/sync/slack.js.map +1 -0
- package/dist/sync/state.d.ts +25 -0
- package/dist/sync/state.d.ts.map +1 -0
- package/dist/sync/state.js +80 -0
- package/dist/sync/state.js.map +1 -0
- package/dist/utils/fs.d.ts +8 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +43 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/git.d.ts +7 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +39 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/logger.d.ts +16 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +21 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/web/server.d.ts +2 -0
- package/dist/web/server.d.ts.map +1 -0
- package/dist/web/server.js +82 -0
- package/dist/web/server.js.map +1 -0
- package/dist/web/ui.html +210 -0
- package/package.json +38 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/utils/fs.ts"],"names":[],"mappings":"AAGA,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAExD;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAE7D;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAE5C;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhD;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAc5D;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtE"}
|
package/dist/utils/fs.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import { join, relative } from 'node:path';
|
|
3
|
+
export function readFile(path) {
|
|
4
|
+
return readFileSync(path, 'utf-8');
|
|
5
|
+
}
|
|
6
|
+
export function readFileSafe(path) {
|
|
7
|
+
try {
|
|
8
|
+
return readFileSync(path, 'utf-8');
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function writeFile(path, content) {
|
|
15
|
+
writeFileSync(path, content, 'utf-8');
|
|
16
|
+
}
|
|
17
|
+
export function ensureDir(path) {
|
|
18
|
+
if (!existsSync(path))
|
|
19
|
+
mkdirSync(path, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
export function fileExists(path) {
|
|
22
|
+
return existsSync(path);
|
|
23
|
+
}
|
|
24
|
+
export function listFiles(dir, ext) {
|
|
25
|
+
const results = [];
|
|
26
|
+
if (!existsSync(dir))
|
|
27
|
+
return results;
|
|
28
|
+
for (const entry of readdirSync(dir)) {
|
|
29
|
+
const fullPath = join(dir, entry);
|
|
30
|
+
const stat = statSync(fullPath);
|
|
31
|
+
if (stat.isDirectory()) {
|
|
32
|
+
results.push(...listFiles(fullPath, ext));
|
|
33
|
+
}
|
|
34
|
+
else if (entry.endsWith(ext)) {
|
|
35
|
+
results.push(fullPath);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return results;
|
|
39
|
+
}
|
|
40
|
+
export function getRelativePath(fullPath, base) {
|
|
41
|
+
return relative(base, fullPath);
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=fs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../../src/utils/fs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACpG,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE3C,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,OAAO,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,IAAI,CAAC;QAAC,OAAO,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,OAAe;IACrD,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,SAAS,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAC9D,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,GAAW,EAAE,GAAW;IAChD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,OAAO,CAAC;IAErC,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;QACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAClC,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAChC,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;YACvB,OAAO,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;QAC5C,CAAC;aAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC/B,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,QAAgB,EAAE,IAAY;IAC5D,OAAO,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { SimpleGit } from 'simple-git';
|
|
2
|
+
export declare function getGit(cwd: string): SimpleGit;
|
|
3
|
+
export declare function resetGit(): void;
|
|
4
|
+
export declare function hasChanges(cwd: string): Promise<boolean>;
|
|
5
|
+
export declare function autoCommit(cwd: string, message: string): Promise<void>;
|
|
6
|
+
export declare function initRepo(cwd: string): Promise<void>;
|
|
7
|
+
//# sourceMappingURL=git.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/utils/git.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,SAAS,EAAE,MAAM,YAAY,CAAC;AAKlD,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAG7C;AAED,wBAAgB,QAAQ,IAAI,IAAI,CAE/B;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAI9D;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAS5E;AAED,wBAAsB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CASzD"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { simpleGit } from 'simple-git';
|
|
2
|
+
import { logger } from './logger.js';
|
|
3
|
+
let git = null;
|
|
4
|
+
export function getGit(cwd) {
|
|
5
|
+
if (!git)
|
|
6
|
+
git = simpleGit(cwd);
|
|
7
|
+
return git;
|
|
8
|
+
}
|
|
9
|
+
export function resetGit() {
|
|
10
|
+
git = null;
|
|
11
|
+
}
|
|
12
|
+
export async function hasChanges(cwd) {
|
|
13
|
+
const g = getGit(cwd);
|
|
14
|
+
const status = await g.status();
|
|
15
|
+
return !status.isClean();
|
|
16
|
+
}
|
|
17
|
+
export async function autoCommit(cwd, message) {
|
|
18
|
+
const g = getGit(cwd);
|
|
19
|
+
try {
|
|
20
|
+
await g.add('.');
|
|
21
|
+
await g.commit(message);
|
|
22
|
+
logger.info('git auto-commit', { message });
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
logger.warn('git commit skipped', { error: String(err) });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export async function initRepo(cwd) {
|
|
29
|
+
const g = getGit(cwd);
|
|
30
|
+
try {
|
|
31
|
+
await g.init();
|
|
32
|
+
logger.info('git repo initialized', { cwd });
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
logger.error('git init failed', { error: String(err) });
|
|
36
|
+
throw err;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=git.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/utils/git.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAa,MAAM,YAAY,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,IAAI,GAAG,GAAqB,IAAI,CAAC;AAEjC,MAAM,UAAU,MAAM,CAAC,GAAW;IAChC,IAAI,CAAC,GAAG;QAAE,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC/B,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,QAAQ;IACtB,GAAG,GAAG,IAAI,CAAC;AACb,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAW;IAC1C,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACtB,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC;IAChC,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;AAC3B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAW,EAAE,OAAe;IAC3D,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACtB,IAAI,CAAC;QACH,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACjB,MAAM,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACxB,MAAM,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IAC9C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC5D,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,GAAW;IACxC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACtB,IAAI,CAAC;QACH,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QACf,MAAM,CAAC,IAAI,CAAC,sBAAsB,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;IAC/C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,KAAK,CAAC,iBAAiB,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACxD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
declare const LOG_LEVELS: {
|
|
2
|
+
readonly debug: 0;
|
|
3
|
+
readonly info: 1;
|
|
4
|
+
readonly warn: 2;
|
|
5
|
+
readonly error: 3;
|
|
6
|
+
};
|
|
7
|
+
type LogLevel = keyof typeof LOG_LEVELS;
|
|
8
|
+
export declare function setLogLevel(level: LogLevel): void;
|
|
9
|
+
export declare const logger: {
|
|
10
|
+
debug: (msg: string, data?: Record<string, unknown>) => void;
|
|
11
|
+
info: (msg: string, data?: Record<string, unknown>) => void;
|
|
12
|
+
warn: (msg: string, data?: Record<string, unknown>) => void;
|
|
13
|
+
error: (msg: string, data?: Record<string, unknown>) => void;
|
|
14
|
+
};
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA,QAAA,MAAM,UAAU;;;;;CAAoD,CAAC;AACrE,KAAK,QAAQ,GAAG,MAAM,OAAO,UAAU,CAAC;AAIxC,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,QAE1C;AASD,eAAO,MAAM,MAAM;iBACJ,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;gBACvC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;gBACtC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;iBACrC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CACpD,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
2
|
+
let currentLevel = 'info';
|
|
3
|
+
export function setLogLevel(level) {
|
|
4
|
+
currentLevel = level;
|
|
5
|
+
}
|
|
6
|
+
function log(level, msg, data) {
|
|
7
|
+
if (LOG_LEVELS[level] < LOG_LEVELS[currentLevel])
|
|
8
|
+
return;
|
|
9
|
+
const entry = { ts: new Date().toISOString(), level, msg, ...data };
|
|
10
|
+
if (level === 'error')
|
|
11
|
+
console.error(JSON.stringify(entry));
|
|
12
|
+
else
|
|
13
|
+
console.log(JSON.stringify(entry));
|
|
14
|
+
}
|
|
15
|
+
export const logger = {
|
|
16
|
+
debug: (msg, data) => log('debug', msg, data),
|
|
17
|
+
info: (msg, data) => log('info', msg, data),
|
|
18
|
+
warn: (msg, data) => log('warn', msg, data),
|
|
19
|
+
error: (msg, data) => log('error', msg, data),
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAW,CAAC;AAGrE,IAAI,YAAY,GAAa,MAAM,CAAC;AAEpC,MAAM,UAAU,WAAW,CAAC,KAAe;IACzC,YAAY,GAAG,KAAK,CAAC;AACvB,CAAC;AAED,SAAS,GAAG,CAAC,KAAe,EAAE,GAAW,EAAE,IAA8B;IACvE,IAAI,UAAU,CAAC,KAAK,CAAC,GAAG,UAAU,CAAC,YAAY,CAAC;QAAE,OAAO;IACzD,MAAM,KAAK,GAAG,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;IACpE,IAAI,KAAK,KAAK,OAAO;QAAE,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;;QACvD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,KAAK,EAAE,CAAC,GAAW,EAAE,IAA8B,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC;IAC/E,IAAI,EAAE,CAAC,GAAW,EAAE,IAA8B,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC;IAC7E,IAAI,EAAE,CAAC,GAAW,EAAE,IAA8B,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC;IAC7E,KAAK,EAAE,CAAC,GAAW,EAAE,IAA8B,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC;CAChF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/web/server.ts"],"names":[],"mappings":"AAmBA,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuE/D"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { logger } from '../utils/logger.js';
|
|
6
|
+
import { SearchPipeline } from '../search/pipeline.js';
|
|
7
|
+
import { loadConfig, resolveKbPath } from '../cli/config.js';
|
|
8
|
+
import { readFileSafe, listFiles } from '../utils/fs.js';
|
|
9
|
+
import { loadSyncState } from '../sync/state.js';
|
|
10
|
+
import { cmdSync } from '../cli/commands.js';
|
|
11
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
12
|
+
function renderHtml() {
|
|
13
|
+
return readFileSync(resolve(__dirname, 'ui.html'), 'utf-8');
|
|
14
|
+
}
|
|
15
|
+
export async function startWebServer(cwd) {
|
|
16
|
+
const config = loadConfig(cwd);
|
|
17
|
+
const kbPath = resolveKbPath(config, cwd);
|
|
18
|
+
const pipeline = new SearchPipeline(kbPath, config);
|
|
19
|
+
const app = express();
|
|
20
|
+
app.use(express.json());
|
|
21
|
+
// UI
|
|
22
|
+
app.get('/', (_req, res) => {
|
|
23
|
+
res.type('html').send(renderHtml());
|
|
24
|
+
});
|
|
25
|
+
// Search
|
|
26
|
+
app.get('/api/search', async (req, res) => {
|
|
27
|
+
const q = String(req.query.q || '');
|
|
28
|
+
const max = Number(req.query.max) || 10;
|
|
29
|
+
try {
|
|
30
|
+
const result = await pipeline.search(q, { maxResults: max });
|
|
31
|
+
res.json(result);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
res.status(500).json({ error: String(err) });
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
// Read document
|
|
38
|
+
app.get('/api/document', (req, res) => {
|
|
39
|
+
const path = String(req.query.path || '');
|
|
40
|
+
const fullPath = resolve(kbPath, path);
|
|
41
|
+
const content = readFileSafe(fullPath);
|
|
42
|
+
if (content === null)
|
|
43
|
+
return res.status(404).json({ error: 'Not found' });
|
|
44
|
+
res.json({ path, content });
|
|
45
|
+
});
|
|
46
|
+
// File tree
|
|
47
|
+
app.get('/api/structure', (_req, res) => {
|
|
48
|
+
const files = listFiles(kbPath, '.md').map(f => ({
|
|
49
|
+
path: f.replace(kbPath + '/', ''),
|
|
50
|
+
name: f.split('/').pop() || f,
|
|
51
|
+
}));
|
|
52
|
+
const sources = config.sources.map(s => ({ name: s.name, type: s.type, enabled: s.enabled ?? true }));
|
|
53
|
+
res.json({ files, sources, kbPath });
|
|
54
|
+
});
|
|
55
|
+
// Sync status
|
|
56
|
+
app.get('/api/sync-status', (_req, res) => {
|
|
57
|
+
const state = loadSyncState(kbPath);
|
|
58
|
+
res.json(state);
|
|
59
|
+
});
|
|
60
|
+
// Trigger sync
|
|
61
|
+
app.post('/api/sync', async (req, res) => {
|
|
62
|
+
const incremental = req.body?.incremental ?? false;
|
|
63
|
+
try {
|
|
64
|
+
await cmdSync(cwd, incremental);
|
|
65
|
+
// Reload config
|
|
66
|
+
const freshConfig = loadConfig(cwd);
|
|
67
|
+
const freshKbPath = resolveKbPath(freshConfig, cwd);
|
|
68
|
+
const freshPipeline = new SearchPipeline(freshKbPath, freshConfig);
|
|
69
|
+
// Update pipeline reference
|
|
70
|
+
app.locals.pipeline = freshPipeline;
|
|
71
|
+
res.json({ ok: true, incremental });
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
res.status(500).json({ error: String(err) });
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
const port = parseInt(process.env.DHM_PORT || '3456', 10);
|
|
78
|
+
app.listen(port, () => {
|
|
79
|
+
logger.info('Web UI started', { url: `http://localhost:${port}` });
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/web/server.ts"],"names":[],"mappings":"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"}
|
package/dist/web/ui.html
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>doc-hub-mcp</title>
|
|
7
|
+
<style>
|
|
8
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
9
|
+
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#0d1117;color:#c9d1d9;display:flex;height:100vh}
|
|
10
|
+
nav{width:260px;background:#161b22;border-right:1px solid #30363d;padding:16px;overflow-y:auto;flex-shrink:0}
|
|
11
|
+
nav h2{font-size:14px;color:#58a6ff;margin-bottom:16px;text-transform:uppercase;letter-spacing:.5px}
|
|
12
|
+
nav .section{margin-bottom:20px}
|
|
13
|
+
nav .section-title{font-size:11px;color:#8b949e;text-transform:uppercase;margin-bottom:8px;letter-spacing:.5px}
|
|
14
|
+
nav a,nav button{display:block;padding:6px 12px;color:#c9d1d9;text-decoration:none;border-radius:6px;font-size:13px;cursor:pointer;border:none;background:none;text-align:left;width:100%}
|
|
15
|
+
nav a:hover,nav button:hover{background:#1f242e}
|
|
16
|
+
nav a.active{background:#1f6feb22;color:#58a6ff}
|
|
17
|
+
nav .file{font-size:12px;color:#8b949e;font-family:monospace}
|
|
18
|
+
main{flex:1;display:flex;flex-direction:column;overflow:hidden}
|
|
19
|
+
header{background:#161b22;border-bottom:1px solid #30363d;padding:12px 20px;display:flex;align-items:center;gap:12px}
|
|
20
|
+
header input{flex:1;background:#0d1117;border:1px solid #30363d;border-radius:6px;padding:8px 16px;color:#c9d1d9;font-size:14px;outline:none}
|
|
21
|
+
header input:focus{border-color:#58a6ff}
|
|
22
|
+
header button{background:#238636;color:white;border:none;padding:8px 16px;border-radius:6px;font-size:13px;cursor:pointer;white-space:nowrap}
|
|
23
|
+
header button.sync-btn{background:#1f6feb}
|
|
24
|
+
.content{flex:1;overflow-y:auto;padding:24px}
|
|
25
|
+
.result-item{background:#161b22;border:1px solid #30363d;border-radius:8px;padding:16px;margin-bottom:12px}
|
|
26
|
+
.result-item h3{font-size:14px;color:#58a6ff;margin-bottom:4px}
|
|
27
|
+
.result-item .meta{font-size:11px;color:#8b949e;margin-bottom:8px}
|
|
28
|
+
.result-item .meta span{display:inline-block;margin-right:12px}
|
|
29
|
+
.result-item pre{background:#0d1117;border-radius:4px;padding:12px;font-size:12px;color:#c9d1d9;overflow-x:auto;max-height:200px;overflow-y:auto}
|
|
30
|
+
.doc-content{line-height:1.6}
|
|
31
|
+
.doc-content h1{font-size:24px;border-bottom:1px solid #30363d;padding-bottom:8px;margin-bottom:16px}
|
|
32
|
+
.doc-content h2{font-size:18px;margin:16px 0 8px}
|
|
33
|
+
.doc-content code{background:#161b22;padding:2px 6px;border-radius:3px;font-size:12px}
|
|
34
|
+
.doc-content pre{background:#161b22;padding:12px;border-radius:6px;overflow-x:auto;margin:8px 0}
|
|
35
|
+
.doc-content a{color:#58a6ff}
|
|
36
|
+
.empty{text-align:center;color:#8b949e;padding:60px 20px}
|
|
37
|
+
.empty h3{font-size:16px;margin-bottom:8px}
|
|
38
|
+
.badge{display:inline-block;padding:2px 6px;border-radius:4px;font-size:10px;font-weight:600}
|
|
39
|
+
.badge-tier1{background:#23863622;color:#3fb950}
|
|
40
|
+
.badge-tier2{background:#d2992222;color:#d29922}
|
|
41
|
+
.badge-tier3{background:#1f6feb22;color:#58a6ff}
|
|
42
|
+
.sync-panel{margin-bottom:16px}
|
|
43
|
+
.sync-source{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:#161b22;border:1px solid #30363d;border-radius:6px;margin-bottom:8px}
|
|
44
|
+
.sync-source .name{font-weight:600}
|
|
45
|
+
.sync-source .info{font-size:11px;color:#8b949e}
|
|
46
|
+
.sync-source button{font-size:11px;padding:4px 8px;margin-left:8px}
|
|
47
|
+
.stats{display:flex;gap:16px;margin-bottom:20px}
|
|
48
|
+
.stat-card{flex:1;background:#161b22;border:1px solid #30363d;border-radius:8px;padding:16px;text-align:center}
|
|
49
|
+
.stat-card .num{font-size:28px;font-weight:700;color:#58a6ff}
|
|
50
|
+
.stat-card .label{font-size:11px;color:#8b949e;margin-top:4px}
|
|
51
|
+
.search-info{margin-bottom:16px;font-size:12px;color:#8b949e}
|
|
52
|
+
.spinner{display:inline-block;width:16px;height:16px;border:2px solid #30363d;border-top-color:#58a6ff;border-radius:50%;animation:spin .6s linear infinite;margin-right:8px}
|
|
53
|
+
@keyframes spin{to{transform:rotate(360deg)}}
|
|
54
|
+
</style>
|
|
55
|
+
</head>
|
|
56
|
+
<body>
|
|
57
|
+
<nav>
|
|
58
|
+
<h2>doc-hub-mcp</h2>
|
|
59
|
+
<div class="section">
|
|
60
|
+
<div class="section-title">Menu</div>
|
|
61
|
+
<a href="#" data-page="search" class="active">Search</a>
|
|
62
|
+
<a href="#" data-page="sync">Sync Status</a>
|
|
63
|
+
</div>
|
|
64
|
+
<div class="section">
|
|
65
|
+
<div class="section-title">Knowledge Base</div>
|
|
66
|
+
<div id="file-tree">Loading...</div>
|
|
67
|
+
</div>
|
|
68
|
+
</nav>
|
|
69
|
+
<main>
|
|
70
|
+
<header>
|
|
71
|
+
<input type="text" id="search-input" placeholder="Search knowledge base (e.g., deploy, architecture, how to...)">
|
|
72
|
+
<button onclick="doSearch()">Search</button>
|
|
73
|
+
<button class="sync-btn" onclick="triggerSync(false)">Sync All</button>
|
|
74
|
+
<button class="sync-btn" onclick="triggerSync(true)">Sync (Incremental)</button>
|
|
75
|
+
</header>
|
|
76
|
+
<div class="content" id="content">
|
|
77
|
+
<div class="empty"><h3>Start searching</h3><p>Type a query to search the knowledge base</p></div>
|
|
78
|
+
</div>
|
|
79
|
+
</main>
|
|
80
|
+
<script>
|
|
81
|
+
let currentPage = 'search';
|
|
82
|
+
let files = [];
|
|
83
|
+
|
|
84
|
+
async function api(url, opts) {
|
|
85
|
+
const res = await fetch(url, opts);
|
|
86
|
+
if (!res.ok) return { error: res.statusText };
|
|
87
|
+
return res.json();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function loadFiles() {
|
|
91
|
+
const data = await api('/api/structure');
|
|
92
|
+
if (data.files) {
|
|
93
|
+
files = data.files;
|
|
94
|
+
const tree = document.getElementById('file-tree');
|
|
95
|
+
tree.innerHTML = files.map(f => `<a href="#" class="file" onclick="viewDoc('${f.path}')">${f.path}</a>`).join('') || '<span class="file">No files</span>';
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function doSearch() {
|
|
100
|
+
const q = document.getElementById('search-input').value.trim();
|
|
101
|
+
if (!q) return;
|
|
102
|
+
setPage('search');
|
|
103
|
+
const content = document.getElementById('content');
|
|
104
|
+
content.innerHTML = '<div class="search-info"><span class="spinner"></span>Searching...</div>';
|
|
105
|
+
const data = await api(`/api/search?q=${encodeURIComponent(q)}&max=20`);
|
|
106
|
+
if (data.error) { content.innerHTML = `<div class="empty"><h3>Error</h3><p>${data.error}</p></div>`; return; }
|
|
107
|
+
if (!data.results || data.results.length === 0) {
|
|
108
|
+
content.innerHTML = '<div class="empty"><h3>No results</h3><p>Try a different query</p></div>';
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
content.innerHTML = `<div class="search-info">${data.results.length} results in ${data.totalTime.toFixed(1)}ms (Tier ${data.tierUsed})</div>` +
|
|
112
|
+
data.results.map(r => `
|
|
113
|
+
<div class="result-item">
|
|
114
|
+
<h3><a href="#" onclick="viewDoc('${r.path}')">${r.path}</a></h3>
|
|
115
|
+
<div class="meta">
|
|
116
|
+
<span>Score: ${r.score.toFixed(2)}</span>
|
|
117
|
+
<span>Match: ${r.matchType}</span>
|
|
118
|
+
<span class="badge badge-tier${r.tier}">Tier ${r.tier}</span>
|
|
119
|
+
</div>
|
|
120
|
+
<pre>${escapeHtml(r.snippet)}</pre>
|
|
121
|
+
</div>`).join('');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function viewDoc(path) {
|
|
125
|
+
setPage('');
|
|
126
|
+
const content = document.getElementById('content');
|
|
127
|
+
content.innerHTML = '<div class="search-info"><span class="spinner"></span>Loading...</div>';
|
|
128
|
+
const data = await api(`/api/document?path=${encodeURIComponent(path)}`);
|
|
129
|
+
if (data.error) { content.innerHTML = `<div class="empty"><h3>Not Found</h3><p>${data.error}</p></div>`; return; }
|
|
130
|
+
content.innerHTML = `<div class="doc-content">${renderMd(data.content)}</div>`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function renderMd(text) {
|
|
134
|
+
return text
|
|
135
|
+
.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')
|
|
136
|
+
.replace(/^### (.+)$/gm,'<h3>$1</h3>')
|
|
137
|
+
.replace(/^## (.+)$/gm,'<h2>$1</h2>')
|
|
138
|
+
.replace(/^# (.+)$/gm,'<h1>$1</h1>')
|
|
139
|
+
.replace(/```(\w*)\n([\s\S]*?)```/g,'<pre><code>$2</code></pre>')
|
|
140
|
+
.replace(/`([^`]+)`/g,'<code>$1</code>')
|
|
141
|
+
.replace(/\*\*(.+?)\*\*/g,'<strong>$1</strong>')
|
|
142
|
+
.replace(/\*(.+?)\*/g,'<em>$1</em>')
|
|
143
|
+
.replace(/\[([^\]]+)\]\(([^)]+)\)/g,'<a href="$2">$1</a>')
|
|
144
|
+
.replace(/\n/g,'<br>');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function escapeHtml(s) {
|
|
148
|
+
return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function loadSyncPanel() {
|
|
152
|
+
const content = document.getElementById('content');
|
|
153
|
+
content.innerHTML = '<div class="search-info"><span class="spinner"></span>Loading...</div>';
|
|
154
|
+
const state = await api('/api/sync-status');
|
|
155
|
+
const sources = await api('/api/structure');
|
|
156
|
+
const statsHtml = sources.sources ? `
|
|
157
|
+
<div class="stats">
|
|
158
|
+
<div class="stat-card"><div class="num">${sources.files?.length || 0}</div><div class="label">Documents</div></div>
|
|
159
|
+
<div class="stat-card"><div class="num">${sources.sources?.length || 0}</div><div class="label">Sources</div></div>
|
|
160
|
+
<div class="stat-card"><div class="num">${state.updated ? new Date(state.updated).toLocaleDateString() : '-'}</div><div class="label">Last Sync</div></div>
|
|
161
|
+
</div>` : '';
|
|
162
|
+
const sourcesHtml = (sources.sources || []).map(s => {
|
|
163
|
+
const ss = state.sources?.[s.name];
|
|
164
|
+
return `<div class="sync-source">
|
|
165
|
+
<div><div class="name">${s.name}</div><div class="info">Type: ${s.type} | Synced: ${ss?.totalSynced || 0} files | Last: ${ss?.lastSync ? new Date(ss.lastSync).toLocaleString() : 'never'}</div></div>
|
|
166
|
+
<div><button onclick="triggerSingleSync('${s.name}')" class="sync-btn">Sync Now</button></div>
|
|
167
|
+
</div>`;
|
|
168
|
+
}).join('');
|
|
169
|
+
content.innerHTML = statsHtml + '<h3 style="margin-bottom:12px">Sources</h3>' + (sourcesHtml || '<p class="empty">No sources configured</p>');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function triggerSync(incremental) {
|
|
173
|
+
const btn = event?.target;
|
|
174
|
+
if (btn) { btn.disabled = true; btn.textContent = 'Syncing...'; }
|
|
175
|
+
const data = await api('/api/sync', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({incremental}) });
|
|
176
|
+
if (btn) { btn.disabled = false; btn.textContent = incremental ? 'Sync (Incremental)' : 'Sync All'; }
|
|
177
|
+
if (data.ok) { loadFiles(); alert(`Sync complete (${incremental ? 'incremental' : 'full'})`); }
|
|
178
|
+
else alert(`Sync failed: ${data.error}`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function triggerSingleSync(sourceName) {
|
|
182
|
+
// Trigger sync for a single source via full sync call
|
|
183
|
+
const data = await api('/api/sync', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({incremental:false}) });
|
|
184
|
+
if (data.ok) { loadFiles(); alert(`Sync of ${sourceName} complete`); }
|
|
185
|
+
else alert(`Sync failed: ${data.error}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function setPage(name) {
|
|
189
|
+
currentPage = name;
|
|
190
|
+
document.querySelectorAll('nav a').forEach(a => a.classList.remove('active'));
|
|
191
|
+
if (name) document.querySelector(`[data-page="${name}"]`)?.classList.add('active');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
195
|
+
loadFiles();
|
|
196
|
+
document.addEventListener('click', e => {
|
|
197
|
+
const page = e.target.dataset.page;
|
|
198
|
+
if (page) {
|
|
199
|
+
e.preventDefault();
|
|
200
|
+
if (page === 'sync') loadSyncPanel();
|
|
201
|
+
else { setPage(page); document.getElementById('content').innerHTML = '<div class="empty"><h3>Search</h3><p>Type a query above</p></div>'; }
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
document.getElementById('search-input').addEventListener('keydown', e => {
|
|
205
|
+
if (e.key === 'Enter') doSearch();
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
</script>
|
|
209
|
+
</body>
|
|
210
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "docs-hub-mcp",
|
|
3
|
+
"version": "1.0.14",
|
|
4
|
+
"description": "MCP server for documentation hub — sync from Wiki, Slack, Google Docs, Notion, Confluence to .md, git versioned, with 3-tier search",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"dhm": "./dhm.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc && node -e \"const fs=require('fs');fs.mkdirSync('dist/web',{recursive:true});fs.cpSync('src/web/ui.html','dist/web/ui.html')\"",
|
|
12
|
+
"dev": "tsc --watch",
|
|
13
|
+
"start": "node dist/index.js",
|
|
14
|
+
"serve": "node dist/index.js serve",
|
|
15
|
+
"sync": "node dist/index.js sync",
|
|
16
|
+
"index": "node dist/index.js index",
|
|
17
|
+
"search": "node dist/index.js search",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"test:watch": "vitest"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@modelcontextprotocol/sdk": "^1.13.0",
|
|
23
|
+
"@types/express": "^5.0.6",
|
|
24
|
+
"express": "^5.2.1",
|
|
25
|
+
"js-yaml": "^4.1.0",
|
|
26
|
+
"simple-git": "^3.27.0",
|
|
27
|
+
"zod": "^3.24.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/js-yaml": "^4.0.9",
|
|
31
|
+
"@types/node": "^22.0.0",
|
|
32
|
+
"typescript": "^5.7.0",
|
|
33
|
+
"vitest": "^3.0.0"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=20"
|
|
37
|
+
}
|
|
38
|
+
}
|