@compilr-dev/sdk 0.9.7 → 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/detection/common.d.ts +24 -0
- package/dist/detection/common.js +247 -0
- package/dist/detection/index.d.ts +6 -0
- package/dist/detection/index.js +5 -0
- package/dist/detection/project-detector.d.ts +29 -0
- package/dist/detection/project-detector.js +106 -0
- package/dist/detection/strategies/book.d.ts +7 -0
- package/dist/detection/strategies/book.js +48 -0
- package/dist/detection/strategies/business.d.ts +7 -0
- package/dist/detection/strategies/business.js +22 -0
- package/dist/detection/strategies/content.d.ts +7 -0
- package/dist/detection/strategies/content.js +21 -0
- package/dist/detection/strategies/course.d.ts +7 -0
- package/dist/detection/strategies/course.js +30 -0
- package/dist/detection/strategies/general.d.ts +8 -0
- package/dist/detection/strategies/general.js +10 -0
- package/dist/detection/strategies/research.d.ts +7 -0
- package/dist/detection/strategies/research.js +44 -0
- package/dist/detection/strategies/software.d.ts +8 -0
- package/dist/detection/strategies/software.js +199 -0
- package/dist/detection/strategies/tech-docs.d.ts +7 -0
- package/dist/detection/strategies/tech-docs.js +51 -0
- package/dist/detection/types.d.ts +80 -0
- package/dist/detection/types.js +7 -0
- package/dist/guide/guide-tool.d.ts +31 -0
- package/dist/guide/guide-tool.js +90 -0
- package/dist/guide/index.d.ts +7 -0
- package/dist/guide/index.js +6 -0
- package/dist/guide/search.d.ts +21 -0
- package/dist/guide/search.js +62 -0
- package/dist/guide/shared-content.d.ts +8 -0
- package/dist/guide/shared-content.js +267 -0
- package/dist/guide/types.d.ts +34 -0
- package/dist/guide/types.js +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +8 -0
- package/package.json +1 -1
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Software Detection Strategy
|
|
3
|
+
*
|
|
4
|
+
* Detects: language, framework, package manager, entry points, test framework.
|
|
5
|
+
* Uses file-based heuristics (package.json, go.mod, pyproject.toml, etc.).
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { findFiles, readFileIfExists } from '../common.js';
|
|
10
|
+
export function detectSoftware(path, base) {
|
|
11
|
+
const result = { ...base };
|
|
12
|
+
// ── Node.js / JavaScript / TypeScript ─────────────────────────────
|
|
13
|
+
const pkgJsonPath = join(path, 'package.json');
|
|
14
|
+
if (existsSync(pkgJsonPath)) {
|
|
15
|
+
try {
|
|
16
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
|
|
17
|
+
const rawDeps = (pkg.dependencies ?? {});
|
|
18
|
+
const rawDevDeps = (pkg.devDependencies ?? {});
|
|
19
|
+
const deps = { ...rawDeps, ...rawDevDeps };
|
|
20
|
+
// Language
|
|
21
|
+
result.language =
|
|
22
|
+
deps.typescript || existsSync(join(path, 'tsconfig.json')) ? 'typescript' : 'javascript';
|
|
23
|
+
// Framework
|
|
24
|
+
if (deps.next)
|
|
25
|
+
result.framework = 'next';
|
|
26
|
+
else if (deps.react)
|
|
27
|
+
result.framework = deps.express ? 'react + express' : 'react';
|
|
28
|
+
else if (deps.vue)
|
|
29
|
+
result.framework = deps.express ? 'vue + express' : 'vue';
|
|
30
|
+
else if (deps.svelte || deps['@sveltejs/kit'])
|
|
31
|
+
result.framework = 'svelte';
|
|
32
|
+
else if (deps.express)
|
|
33
|
+
result.framework = 'express';
|
|
34
|
+
else if (deps.fastify)
|
|
35
|
+
result.framework = 'fastify';
|
|
36
|
+
else if (deps.hono)
|
|
37
|
+
result.framework = 'hono';
|
|
38
|
+
// Package manager
|
|
39
|
+
if (existsSync(join(path, 'pnpm-lock.yaml')))
|
|
40
|
+
result.packageManager = 'pnpm';
|
|
41
|
+
else if (existsSync(join(path, 'yarn.lock')))
|
|
42
|
+
result.packageManager = 'yarn';
|
|
43
|
+
else if (existsSync(join(path, 'bun.lockb')))
|
|
44
|
+
result.packageManager = 'bun';
|
|
45
|
+
else
|
|
46
|
+
result.packageManager = 'npm';
|
|
47
|
+
// Test framework
|
|
48
|
+
if (deps.vitest)
|
|
49
|
+
result.testFramework = 'vitest';
|
|
50
|
+
else if (deps.jest)
|
|
51
|
+
result.testFramework = 'jest';
|
|
52
|
+
else if (deps.mocha)
|
|
53
|
+
result.testFramework = 'mocha';
|
|
54
|
+
else if (deps['@playwright/test'])
|
|
55
|
+
result.testFramework = 'playwright';
|
|
56
|
+
// Entry points
|
|
57
|
+
const entries = [];
|
|
58
|
+
for (const candidate of [
|
|
59
|
+
'src/index.ts',
|
|
60
|
+
'src/index.js',
|
|
61
|
+
'src/main.ts',
|
|
62
|
+
'src/main.js',
|
|
63
|
+
'src/app.ts',
|
|
64
|
+
'src/app.js',
|
|
65
|
+
'index.ts',
|
|
66
|
+
'index.js',
|
|
67
|
+
]) {
|
|
68
|
+
if (existsSync(join(path, candidate)))
|
|
69
|
+
entries.push(candidate);
|
|
70
|
+
}
|
|
71
|
+
if (entries.length > 0)
|
|
72
|
+
result.entryPoints = entries;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
/* invalid package.json */
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
// ── Python ────────────────────────────────────────────────────────
|
|
80
|
+
const hasPyproject = existsSync(join(path, 'pyproject.toml'));
|
|
81
|
+
const hasRequirements = existsSync(join(path, 'requirements.txt'));
|
|
82
|
+
const hasSetupPy = existsSync(join(path, 'setup.py'));
|
|
83
|
+
if (hasPyproject || hasRequirements || hasSetupPy) {
|
|
84
|
+
result.language = 'python';
|
|
85
|
+
if (hasPyproject) {
|
|
86
|
+
const content = readFileIfExists(join(path, 'pyproject.toml'));
|
|
87
|
+
if (content) {
|
|
88
|
+
if (content.includes('poetry'))
|
|
89
|
+
result.packageManager = 'poetry';
|
|
90
|
+
else if (content.includes('pdm'))
|
|
91
|
+
result.packageManager = 'pdm';
|
|
92
|
+
else
|
|
93
|
+
result.packageManager = 'pip';
|
|
94
|
+
if (content.includes('fastapi'))
|
|
95
|
+
result.framework = 'fastapi';
|
|
96
|
+
else if (content.includes('django'))
|
|
97
|
+
result.framework = 'django';
|
|
98
|
+
else if (content.includes('flask'))
|
|
99
|
+
result.framework = 'flask';
|
|
100
|
+
if (content.includes('pytest'))
|
|
101
|
+
result.testFramework = 'pytest';
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
result.packageManager = 'pip';
|
|
106
|
+
const reqs = readFileIfExists(join(path, 'requirements.txt'));
|
|
107
|
+
if (reqs) {
|
|
108
|
+
if (reqs.includes('fastapi'))
|
|
109
|
+
result.framework = 'fastapi';
|
|
110
|
+
else if (reqs.includes('django'))
|
|
111
|
+
result.framework = 'django';
|
|
112
|
+
else if (reqs.includes('flask'))
|
|
113
|
+
result.framework = 'flask';
|
|
114
|
+
if (reqs.includes('pytest'))
|
|
115
|
+
result.testFramework = 'pytest';
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const entries = [];
|
|
119
|
+
for (const candidate of ['main.py', 'app.py', 'server.py', 'src/main.py', 'src/app.py']) {
|
|
120
|
+
if (existsSync(join(path, candidate)))
|
|
121
|
+
entries.push(candidate);
|
|
122
|
+
}
|
|
123
|
+
if (entries.length > 0)
|
|
124
|
+
result.entryPoints = entries;
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
// ── Go ────────────────────────────────────────────────────────────
|
|
128
|
+
if (existsSync(join(path, 'go.mod'))) {
|
|
129
|
+
result.language = 'go';
|
|
130
|
+
result.packageManager = 'go mod';
|
|
131
|
+
const goMod = readFileIfExists(join(path, 'go.mod'));
|
|
132
|
+
if (goMod) {
|
|
133
|
+
if (goMod.includes('github.com/gin-gonic/gin'))
|
|
134
|
+
result.framework = 'gin';
|
|
135
|
+
else if (goMod.includes('github.com/labstack/echo'))
|
|
136
|
+
result.framework = 'echo';
|
|
137
|
+
else if (goMod.includes('github.com/gofiber/fiber'))
|
|
138
|
+
result.framework = 'fiber';
|
|
139
|
+
}
|
|
140
|
+
const entries = [];
|
|
141
|
+
for (const candidate of ['main.go', 'cmd/main.go', 'cmd/server/main.go']) {
|
|
142
|
+
if (existsSync(join(path, candidate)))
|
|
143
|
+
entries.push(candidate);
|
|
144
|
+
}
|
|
145
|
+
if (entries.length > 0)
|
|
146
|
+
result.entryPoints = entries;
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
// ── Rust ──────────────────────────────────────────────────────────
|
|
150
|
+
if (existsSync(join(path, 'Cargo.toml'))) {
|
|
151
|
+
result.language = 'rust';
|
|
152
|
+
result.packageManager = 'cargo';
|
|
153
|
+
const cargo = readFileIfExists(join(path, 'Cargo.toml'));
|
|
154
|
+
if (cargo) {
|
|
155
|
+
if (cargo.includes('actix-web'))
|
|
156
|
+
result.framework = 'actix';
|
|
157
|
+
else if (cargo.includes('axum'))
|
|
158
|
+
result.framework = 'axum';
|
|
159
|
+
else if (cargo.includes('rocket'))
|
|
160
|
+
result.framework = 'rocket';
|
|
161
|
+
}
|
|
162
|
+
if (existsSync(join(path, 'src', 'main.rs')))
|
|
163
|
+
result.entryPoints = ['src/main.rs'];
|
|
164
|
+
else if (existsSync(join(path, 'src', 'lib.rs')))
|
|
165
|
+
result.entryPoints = ['src/lib.rs'];
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
// ── Java / Kotlin ─────────────────────────────────────────────────
|
|
169
|
+
if (existsSync(join(path, 'pom.xml'))) {
|
|
170
|
+
result.language = 'java';
|
|
171
|
+
result.packageManager = 'maven';
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
174
|
+
if (existsSync(join(path, 'build.gradle')) || existsSync(join(path, 'build.gradle.kts'))) {
|
|
175
|
+
result.language = existsSync(join(path, 'build.gradle.kts')) ? 'kotlin' : 'java';
|
|
176
|
+
result.packageManager = 'gradle';
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
// ── Fallback: check for common source files ───────────────────────
|
|
180
|
+
const sourceFiles = findFiles(path, (_name, ext) => ['.ts', '.js', '.py', '.go', '.rs', '.java', '.kt', '.rb', '.php', '.cs', '.swift'].includes(ext), 1);
|
|
181
|
+
if (sourceFiles.length > 0) {
|
|
182
|
+
const ext = sourceFiles[0].split('.').pop();
|
|
183
|
+
const langMap = {
|
|
184
|
+
ts: 'typescript',
|
|
185
|
+
js: 'javascript',
|
|
186
|
+
py: 'python',
|
|
187
|
+
go: 'go',
|
|
188
|
+
rs: 'rust',
|
|
189
|
+
java: 'java',
|
|
190
|
+
kt: 'kotlin',
|
|
191
|
+
rb: 'ruby',
|
|
192
|
+
php: 'php',
|
|
193
|
+
cs: 'csharp',
|
|
194
|
+
swift: 'swift',
|
|
195
|
+
};
|
|
196
|
+
result.language = ext ? langMap[ext] : undefined;
|
|
197
|
+
}
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tech Docs Detection Strategy
|
|
3
|
+
*
|
|
4
|
+
* Detects: documentation frameworks (mkdocs, docusaurus, sphinx), docs folder.
|
|
5
|
+
*/
|
|
6
|
+
import type { ContentSummary } from '../types.js';
|
|
7
|
+
export declare function detectTechDocs(path: string, base: ContentSummary): ContentSummary;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tech Docs Detection Strategy
|
|
3
|
+
*
|
|
4
|
+
* Detects: documentation frameworks (mkdocs, docusaurus, sphinx), docs folder.
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { hasDirectory } from '../common.js';
|
|
9
|
+
export function detectTechDocs(path, base) {
|
|
10
|
+
const result = { ...base };
|
|
11
|
+
// MkDocs
|
|
12
|
+
if (existsSync(join(path, 'mkdocs.yml')) || existsSync(join(path, 'mkdocs.yaml'))) {
|
|
13
|
+
result.docsFramework = 'mkdocs';
|
|
14
|
+
result.docsConfigFile = existsSync(join(path, 'mkdocs.yml')) ? 'mkdocs.yml' : 'mkdocs.yaml';
|
|
15
|
+
return result;
|
|
16
|
+
}
|
|
17
|
+
// Docusaurus
|
|
18
|
+
if (existsSync(join(path, 'docusaurus.config.js')) ||
|
|
19
|
+
existsSync(join(path, 'docusaurus.config.ts'))) {
|
|
20
|
+
result.docsFramework = 'docusaurus';
|
|
21
|
+
result.docsConfigFile = existsSync(join(path, 'docusaurus.config.js'))
|
|
22
|
+
? 'docusaurus.config.js'
|
|
23
|
+
: 'docusaurus.config.ts';
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
// Sphinx
|
|
27
|
+
if (existsSync(join(path, 'conf.py')) || existsSync(join(path, 'source', 'conf.py'))) {
|
|
28
|
+
result.docsFramework = 'sphinx';
|
|
29
|
+
result.docsConfigFile = existsSync(join(path, 'conf.py')) ? 'conf.py' : 'source/conf.py';
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
// VitePress
|
|
33
|
+
if (existsSync(join(path, '.vitepress', 'config.ts')) ||
|
|
34
|
+
existsSync(join(path, '.vitepress', 'config.js'))) {
|
|
35
|
+
result.docsFramework = 'vitepress';
|
|
36
|
+
result.docsConfigFile = '.vitepress/config.ts';
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
// GitBook
|
|
40
|
+
if (existsSync(join(path, 'SUMMARY.md'))) {
|
|
41
|
+
result.docsFramework = 'gitbook';
|
|
42
|
+
result.docsConfigFile = 'SUMMARY.md';
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
// Generic docs/ folder
|
|
46
|
+
if (hasDirectory(path, 'docs')) {
|
|
47
|
+
result.docsFramework = 'custom';
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Detection Types
|
|
3
|
+
*
|
|
4
|
+
* Universal detection result that works across all project types.
|
|
5
|
+
* Type-specific fields are optional — only populated when relevant.
|
|
6
|
+
*/
|
|
7
|
+
export interface DetectProjectOptions {
|
|
8
|
+
/** Absolute path to the project folder */
|
|
9
|
+
path: string;
|
|
10
|
+
/** Project type — determines detection strategy */
|
|
11
|
+
type: string;
|
|
12
|
+
}
|
|
13
|
+
export interface DetectionResult {
|
|
14
|
+
/** Inferred project name (slug: lowercase, hyphens) */
|
|
15
|
+
name: string;
|
|
16
|
+
/** Display name (prettified from folder name or config) */
|
|
17
|
+
displayName: string;
|
|
18
|
+
/** Description (from README first paragraph, package.json, etc.) */
|
|
19
|
+
description?: string;
|
|
20
|
+
/** Git remote URL (if .git exists with a remote) */
|
|
21
|
+
gitRemote?: string;
|
|
22
|
+
/** Whether .git directory exists */
|
|
23
|
+
hasGit: boolean;
|
|
24
|
+
/** Whether COMPILR.md exists in the project root */
|
|
25
|
+
hasCompilrMd: boolean;
|
|
26
|
+
/** Type-specific detection results */
|
|
27
|
+
content: ContentSummary;
|
|
28
|
+
}
|
|
29
|
+
export interface ContentSummary {
|
|
30
|
+
/** Total files found (excluding ignored dirs) */
|
|
31
|
+
fileCount: number;
|
|
32
|
+
/** Total size in KB */
|
|
33
|
+
totalSizeKB: number;
|
|
34
|
+
/** Markdown files count */
|
|
35
|
+
markdownFiles: number;
|
|
36
|
+
/** Image files count */
|
|
37
|
+
imageFiles: number;
|
|
38
|
+
/** Detected language (typescript, python, go, rust, java, etc.) */
|
|
39
|
+
language?: string;
|
|
40
|
+
/** Detected framework (react, next, express, fastapi, gin, etc.) */
|
|
41
|
+
framework?: string;
|
|
42
|
+
/** Package manager (npm, yarn, pnpm, pip, poetry, go mod, cargo) */
|
|
43
|
+
packageManager?: string;
|
|
44
|
+
/** Main entry points found */
|
|
45
|
+
entryPoints?: string[];
|
|
46
|
+
/** Test framework detected */
|
|
47
|
+
testFramework?: string;
|
|
48
|
+
/** Path to .bib file */
|
|
49
|
+
bibliographyFile?: string;
|
|
50
|
+
/** Number of bib entries */
|
|
51
|
+
bibliographyEntries?: number;
|
|
52
|
+
/** LaTeX files found */
|
|
53
|
+
latexFiles?: string[];
|
|
54
|
+
/** Data files (.csv, .xlsx, .json in data/ or root) */
|
|
55
|
+
dataFiles?: number;
|
|
56
|
+
/** Number of chapter files detected */
|
|
57
|
+
chapterCount?: number;
|
|
58
|
+
/** Path to outline file */
|
|
59
|
+
outlineFile?: string;
|
|
60
|
+
/** Manuscript/draft files */
|
|
61
|
+
draftFiles?: string[];
|
|
62
|
+
/** Spreadsheet files (.xlsx, .csv) */
|
|
63
|
+
spreadsheetFiles?: number;
|
|
64
|
+
/** Presentation files (.pptx, .pdf) */
|
|
65
|
+
presentationFiles?: number;
|
|
66
|
+
/** Documentation framework (mkdocs, docusaurus, sphinx, etc.) */
|
|
67
|
+
docsFramework?: string;
|
|
68
|
+
/** Config file path */
|
|
69
|
+
docsConfigFile?: string;
|
|
70
|
+
/** Lesson/module directories */
|
|
71
|
+
lessonCount?: number;
|
|
72
|
+
/** Exercise files */
|
|
73
|
+
exerciseFiles?: number;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Detection strategy function signature.
|
|
77
|
+
* Each strategy receives the project path and a base summary,
|
|
78
|
+
* then adds type-specific fields.
|
|
79
|
+
*/
|
|
80
|
+
export type DetectionStrategy = (path: string, base: ContentSummary) => ContentSummary;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guide Tool Factory — creates an environment-aware compilr_guide tool.
|
|
3
|
+
*
|
|
4
|
+
* The tool definition lives in the SDK. Each host app (CLI, Desktop)
|
|
5
|
+
* registers environment-specific content via additionalEntries.
|
|
6
|
+
*/
|
|
7
|
+
import type { GuideToolConfig } from './types.js';
|
|
8
|
+
interface GuideInput {
|
|
9
|
+
query?: string;
|
|
10
|
+
list_topics?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Create the compilr_guide tool for the given environment.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* // In CLI
|
|
18
|
+
* const guideTool = createGuideTool({
|
|
19
|
+
* environment: 'cli',
|
|
20
|
+
* additionalEntries: CLI_GUIDE_ENTRIES,
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* // In Desktop
|
|
24
|
+
* const guideTool = createGuideTool({
|
|
25
|
+
* environment: 'desktop',
|
|
26
|
+
* additionalEntries: DESKTOP_GUIDE_ENTRIES,
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare function createGuideTool(config: GuideToolConfig): import("@compilr-dev/agents").Tool<GuideInput>;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guide Tool Factory — creates an environment-aware compilr_guide tool.
|
|
3
|
+
*
|
|
4
|
+
* The tool definition lives in the SDK. Each host app (CLI, Desktop)
|
|
5
|
+
* registers environment-specific content via additionalEntries.
|
|
6
|
+
*/
|
|
7
|
+
import { defineTool } from '@compilr-dev/agents';
|
|
8
|
+
import { SHARED_GUIDE_ENTRIES } from './shared-content.js';
|
|
9
|
+
import { searchGuideEntries } from './search.js';
|
|
10
|
+
/**
|
|
11
|
+
* Create the compilr_guide tool for the given environment.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* // In CLI
|
|
16
|
+
* const guideTool = createGuideTool({
|
|
17
|
+
* environment: 'cli',
|
|
18
|
+
* additionalEntries: CLI_GUIDE_ENTRIES,
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* // In Desktop
|
|
22
|
+
* const guideTool = createGuideTool({
|
|
23
|
+
* environment: 'desktop',
|
|
24
|
+
* additionalEntries: DESKTOP_GUIDE_ENTRIES,
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
29
|
+
export function createGuideTool(config) {
|
|
30
|
+
// Merge shared + environment-specific content
|
|
31
|
+
const allEntries = [...SHARED_GUIDE_ENTRIES, ...(config.additionalEntries ?? [])];
|
|
32
|
+
const envLabel = config.environment === 'desktop' ? 'Desktop' : 'CLI';
|
|
33
|
+
return defineTool({
|
|
34
|
+
name: 'compilr_guide',
|
|
35
|
+
description: `Get documentation about Compilr Dev ${envLabel} features and capabilities. ` +
|
|
36
|
+
`Use this to answer user questions about how to use the ${envLabel.toLowerCase()}. ` +
|
|
37
|
+
'Query by topic (e.g., "creating-project", "team-agents", "image-support") or ' +
|
|
38
|
+
'set list_topics=true to see all available topics.',
|
|
39
|
+
inputSchema: {
|
|
40
|
+
type: 'object',
|
|
41
|
+
properties: {
|
|
42
|
+
query: {
|
|
43
|
+
type: 'string',
|
|
44
|
+
description: 'Topic or keyword to look up. Examples: "creating-project", "team-agents", "permissions", "image-support"',
|
|
45
|
+
},
|
|
46
|
+
list_topics: {
|
|
47
|
+
type: 'boolean',
|
|
48
|
+
description: 'Set to true to list all available guide topics',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
execute: (input) => {
|
|
53
|
+
if (input.list_topics) {
|
|
54
|
+
const topics = allEntries.map((e) => `- ${e.id} — ${e.title}`).join('\n');
|
|
55
|
+
return Promise.resolve({
|
|
56
|
+
success: true,
|
|
57
|
+
result: {
|
|
58
|
+
output: `Available guide topics (${envLabel}):\n\n${topics}`,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (!input.query) {
|
|
63
|
+
return Promise.resolve({
|
|
64
|
+
success: true,
|
|
65
|
+
result: {
|
|
66
|
+
output: 'Provide a query (topic or keyword) or set list_topics=true to see all topics.',
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
const results = searchGuideEntries(allEntries, input.query);
|
|
71
|
+
if (results.length === 0) {
|
|
72
|
+
const topics = allEntries.map((e) => e.id).join(', ');
|
|
73
|
+
return Promise.resolve({
|
|
74
|
+
success: true,
|
|
75
|
+
result: {
|
|
76
|
+
output: `No guide found for "${input.query}".\n\nAvailable topics: ${topics}`,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
const formatted = results
|
|
81
|
+
.map((entry) => `## ${entry.title}\n\n${entry.content}`)
|
|
82
|
+
.join('\n\n---\n\n');
|
|
83
|
+
return Promise.resolve({
|
|
84
|
+
success: true,
|
|
85
|
+
result: { output: formatted },
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
silent: true,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guide module — environment-aware documentation tool.
|
|
3
|
+
*/
|
|
4
|
+
export { createGuideTool } from './guide-tool.js';
|
|
5
|
+
export { SHARED_GUIDE_ENTRIES } from './shared-content.js';
|
|
6
|
+
export { searchGuideEntries, topicToGuideEntry } from './search.js';
|
|
7
|
+
export type { GuideEntry, ContentTopic, ContentSection, GuideToolConfig } from './types.js';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guide search — finds relevant entries by query.
|
|
3
|
+
*/
|
|
4
|
+
import type { GuideEntry } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Search guide entries by keyword, topic ID, or free text.
|
|
7
|
+
* Returns up to maxResults matches, ranked by relevance.
|
|
8
|
+
*/
|
|
9
|
+
export declare function searchGuideEntries(entries: GuideEntry[], query: string, maxResults?: number): GuideEntry[];
|
|
10
|
+
/**
|
|
11
|
+
* Convert a ContentTopic to a GuideEntry (plain text).
|
|
12
|
+
*/
|
|
13
|
+
export declare function topicToGuideEntry(topic: {
|
|
14
|
+
id: string;
|
|
15
|
+
title: string;
|
|
16
|
+
keywords: string[];
|
|
17
|
+
sections: Array<{
|
|
18
|
+
title?: string;
|
|
19
|
+
items: string[];
|
|
20
|
+
}>;
|
|
21
|
+
}): GuideEntry;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guide search — finds relevant entries by query.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Search guide entries by keyword, topic ID, or free text.
|
|
6
|
+
* Returns up to maxResults matches, ranked by relevance.
|
|
7
|
+
*/
|
|
8
|
+
export function searchGuideEntries(entries, query, maxResults = 3) {
|
|
9
|
+
const q = query.toLowerCase().trim();
|
|
10
|
+
if (!q)
|
|
11
|
+
return [];
|
|
12
|
+
// 1. Exact ID match
|
|
13
|
+
const exactMatch = entries.find((e) => e.id === q);
|
|
14
|
+
if (exactMatch)
|
|
15
|
+
return [exactMatch];
|
|
16
|
+
// 2. Command match (e.g., "/design" → "skill-design", or "design")
|
|
17
|
+
const cmdQuery = q.startsWith('/') ? q : `/${q}`;
|
|
18
|
+
const cmdMatch = entries.find((e) => e.keywords.includes(cmdQuery) || e.keywords.includes(q));
|
|
19
|
+
if (cmdMatch)
|
|
20
|
+
return [cmdMatch];
|
|
21
|
+
// 3. Keyword + title + content search with scoring
|
|
22
|
+
const scored = entries
|
|
23
|
+
.map((e) => {
|
|
24
|
+
let score = 0;
|
|
25
|
+
// Keyword match (strongest signal)
|
|
26
|
+
if (e.keywords.some((k) => k === q))
|
|
27
|
+
score += 10;
|
|
28
|
+
if (e.keywords.some((k) => k.includes(q)))
|
|
29
|
+
score += 5;
|
|
30
|
+
if (e.keywords.some((k) => q.includes(k) && k.length > 2))
|
|
31
|
+
score += 3;
|
|
32
|
+
// Title match
|
|
33
|
+
if (e.title.toLowerCase().includes(q))
|
|
34
|
+
score += 4;
|
|
35
|
+
// Content match (weakest signal)
|
|
36
|
+
if (e.content.toLowerCase().includes(q))
|
|
37
|
+
score += 1;
|
|
38
|
+
return { entry: e, score };
|
|
39
|
+
})
|
|
40
|
+
.filter((s) => s.score > 0)
|
|
41
|
+
.sort((a, b) => b.score - a.score);
|
|
42
|
+
return scored.slice(0, maxResults).map((s) => s.entry);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Convert a ContentTopic to a GuideEntry (plain text).
|
|
46
|
+
*/
|
|
47
|
+
export function topicToGuideEntry(topic) {
|
|
48
|
+
const parts = [];
|
|
49
|
+
for (const section of topic.sections) {
|
|
50
|
+
if (section.title)
|
|
51
|
+
parts.push(section.title);
|
|
52
|
+
for (const item of section.items) {
|
|
53
|
+
parts.push(` ${item}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
id: topic.id,
|
|
58
|
+
title: topic.title,
|
|
59
|
+
keywords: topic.keywords,
|
|
60
|
+
content: parts.join('\n'),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Guide Content — environment-agnostic topics.
|
|
3
|
+
*
|
|
4
|
+
* These apply to both CLI and Desktop. Each entry describes a feature
|
|
5
|
+
* that works the same way regardless of the host application.
|
|
6
|
+
*/
|
|
7
|
+
import type { GuideEntry } from './types.js';
|
|
8
|
+
export declare const SHARED_GUIDE_ENTRIES: GuideEntry[];
|