promptgraph-mcp 1.0.2 → 1.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/platform.js ADDED
@@ -0,0 +1,98 @@
1
+ import os from 'os';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+
5
+ const HOME = os.homedir();
6
+
7
+ export const PLATFORMS = {
8
+ 'claude-code': {
9
+ name: 'Claude Code',
10
+ configPath: path.join(HOME, '.claude', 'settings.json'),
11
+ addMcp: (config, serverPath) => {
12
+ const json = readJson(config.configPath);
13
+ json.mcpServers = json.mcpServers || {};
14
+ json.mcpServers.promptgraph = { command: 'npx', args: ['promptgraph-mcp'] };
15
+ writeJson(config.configPath, json);
16
+ },
17
+ },
18
+ 'claude-desktop': {
19
+ name: 'Claude Desktop',
20
+ configPath: getClaudeDesktopConfig(),
21
+ addMcp: (config, serverPath) => {
22
+ const json = readJson(config.configPath);
23
+ json.mcpServers = json.mcpServers || {};
24
+ json.mcpServers.promptgraph = { command: 'npx', args: ['promptgraph-mcp'] };
25
+ writeJson(config.configPath, json);
26
+ },
27
+ },
28
+ 'cline': {
29
+ name: 'Cline (VS Code)',
30
+ configPath: path.join(HOME, '.vscode', 'mcp.json'),
31
+ addMcp: (config, serverPath) => {
32
+ const json = readJson(config.configPath) || { servers: {} };
33
+ json.servers = json.servers || {};
34
+ json.servers.promptgraph = { command: 'npx', args: ['promptgraph-mcp'] };
35
+ fs.mkdirSync(path.dirname(config.configPath), { recursive: true });
36
+ writeJson(config.configPath, json);
37
+ },
38
+ },
39
+ 'codex': {
40
+ name: 'OpenAI Codex CLI',
41
+ configPath: path.join(HOME, '.codex', 'config.json'),
42
+ addMcp: (config, serverPath) => {
43
+ const json = readJson(config.configPath) || {};
44
+ json.mcpServers = json.mcpServers || {};
45
+ json.mcpServers.promptgraph = { command: 'npx', args: ['promptgraph-mcp'] };
46
+ fs.mkdirSync(path.dirname(config.configPath), { recursive: true });
47
+ writeJson(config.configPath, json);
48
+ },
49
+ },
50
+ 'cursor': {
51
+ name: 'Cursor',
52
+ configPath: path.join(HOME, '.cursor', 'mcp.json'),
53
+ addMcp: (config, serverPath) => {
54
+ const json = readJson(config.configPath) || { mcpServers: {} };
55
+ json.mcpServers.promptgraph = { command: 'npx', args: ['promptgraph-mcp'] };
56
+ fs.mkdirSync(path.dirname(config.configPath), { recursive: true });
57
+ writeJson(config.configPath, json);
58
+ },
59
+ },
60
+ 'windsurf': {
61
+ name: 'Windsurf',
62
+ configPath: path.join(HOME, '.codeium', 'windsurf', 'mcp_config.json'),
63
+ addMcp: (config, serverPath) => {
64
+ const json = readJson(config.configPath) || { mcpServers: {} };
65
+ json.mcpServers.promptgraph = { command: 'npx', args: ['promptgraph-mcp'] };
66
+ fs.mkdirSync(path.dirname(config.configPath), { recursive: true });
67
+ writeJson(config.configPath, json);
68
+ },
69
+ },
70
+ };
71
+
72
+ export function detectPlatforms() {
73
+ return Object.entries(PLATFORMS)
74
+ .filter(([, p]) => p.configPath && fs.existsSync(path.dirname(p.configPath)))
75
+ .map(([id, p]) => ({ id, ...p }));
76
+ }
77
+
78
+ function getClaudeDesktopConfig() {
79
+ if (process.platform === 'win32') {
80
+ const base = process.env.LOCALAPPDATA || '';
81
+ const packages = path.join(base, 'Packages');
82
+ if (fs.existsSync(packages)) {
83
+ const claudeDir = fs.readdirSync(packages).find(d => d.startsWith('Claude_'));
84
+ if (claudeDir) return path.join(packages, claudeDir, 'LocalCache', 'Roaming', 'Claude', 'claude_desktop_config.json');
85
+ }
86
+ }
87
+ if (process.platform === 'darwin') return path.join(HOME, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
88
+ return path.join(HOME, '.config', 'Claude', 'claude_desktop_config.json');
89
+ }
90
+
91
+ function readJson(filePath) {
92
+ try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); } catch { return {}; }
93
+ }
94
+
95
+ function writeJson(filePath, data) {
96
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
97
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
98
+ }
package/search.js CHANGED
@@ -4,18 +4,26 @@ import { getDb } from './db.js';
4
4
  export async function search(query, topK = 5) {
5
5
  const db = getDb();
6
6
  const queryVec = await embed(query);
7
- const skills = db.prepare('SELECT name, description, path, source, embedding FROM skills').all();
8
-
9
- return skills
10
- .map(skill => ({
11
- name: skill.name,
12
- description: skill.description,
13
- path: skill.path,
14
- source: skill.source,
15
- score: cosineSimilarity(queryVec, JSON.parse(skill.embedding)),
16
- }))
17
- .sort((a, b) => b.score - a.score)
18
- .slice(0, topK);
7
+
8
+ // RAG: search over chunks, deduplicate by skill
9
+ const chunks = db.prepare('SELECT skill_name, embedding FROM chunks').all();
10
+
11
+ const bestBySkill = new Map();
12
+ for (const chunk of chunks) {
13
+ const score = cosineSimilarity(queryVec, JSON.parse(chunk.embedding));
14
+ const prev = bestBySkill.get(chunk.skill_name);
15
+ if (!prev || score > prev) bestBySkill.set(chunk.skill_name, score);
16
+ }
17
+
18
+ const skillNames = [...bestBySkill.entries()]
19
+ .sort((a, b) => b[1] - a[1])
20
+ .slice(0, topK)
21
+ .map(([name]) => name);
22
+
23
+ return skillNames.map(name => {
24
+ const skill = db.prepare('SELECT name, description, path, source FROM skills WHERE name = ?').get(name);
25
+ return { ...skill, score: bestBySkill.get(name) };
26
+ });
19
27
  }
20
28
 
21
29
  export function getContext(name) {
@@ -24,7 +32,7 @@ export function getContext(name) {
24
32
  if (!skill) return null;
25
33
  const callees = db.prepare('SELECT to_skill FROM edges WHERE from_skill = ?').all(name).map(r => r.to_skill);
26
34
  const callers = db.prepare('SELECT from_skill FROM edges WHERE to_skill = ?').all(name).map(r => r.from_skill);
27
- return { ...skill, embedding: undefined, callees, callers };
35
+ return { ...skill, callees, callers };
28
36
  }
29
37
 
30
38
  export function getCallers(name) {
package/watcher.js CHANGED
@@ -1,50 +1,50 @@
1
- import chokidar from 'chokidar';
2
- import path from 'path';
3
- import os from 'os';
4
- import { indexFile } from './indexer.js';
5
- import { getDb } from './db.js';
6
-
7
- const SOURCES = [
8
- { dir: path.join(os.homedir(), '.claude', 'skills-store'), source: 'skills-store' },
9
- { dir: path.join(os.homedir(), '.claude', 'skills'), source: 'skills' },
10
- ];
11
-
12
- export function startWatcher() {
13
- const paths = SOURCES.map(s => s.dir);
14
-
15
- const watcher = chokidar.watch(paths, {
16
- ignored: /[/\\]\./,
17
- persistent: true,
18
- ignoreInitial: true,
19
- awaitWriteFinish: { stabilityThreshold: 500 },
20
- });
21
-
22
- watcher.on('add', filePath => reindex(filePath));
23
- watcher.on('change', filePath => reindex(filePath));
24
- watcher.on('unlink', filePath => {
25
- const name = path.basename(filePath, '.md');
26
- const db = getDb();
27
- db.prepare('DELETE FROM skills WHERE name = ?').run(name);
28
- db.prepare('DELETE FROM edges WHERE from_skill = ? OR to_skill = ?').run(name, name);
29
- console.error(`[PromptGraph] Removed: ${name}`);
30
- });
31
-
32
- console.error('[PromptGraph] Watcher started');
33
- }
34
-
35
- function getSource(filePath) {
36
- for (const { dir, source } of SOURCES) {
37
- if (filePath.startsWith(dir)) return source;
38
- }
39
- return 'unknown';
40
- }
41
-
42
- async function reindex(filePath) {
43
- if (!filePath.endsWith('.md')) return;
44
- try {
45
- await indexFile(filePath, getSource(filePath));
46
- console.error(`[PromptGraph] Reindexed: ${path.basename(filePath)}`);
47
- } catch (e) {
48
- console.error(`[PromptGraph] Error reindexing ${filePath}: ${e.message}`);
49
- }
50
- }
1
+ import chokidar from 'chokidar';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import { indexFile } from './indexer.js';
5
+ import { getDb } from './db.js';
6
+
7
+ const SOURCES = [
8
+ { dir: path.join(os.homedir(), '.claude', 'skills-store'), source: 'skills-store' },
9
+ { dir: path.join(os.homedir(), '.claude', 'skills'), source: 'skills' },
10
+ ];
11
+
12
+ export function startWatcher() {
13
+ const paths = SOURCES.map(s => s.dir);
14
+
15
+ const watcher = chokidar.watch(paths, {
16
+ ignored: /[/\\]\./,
17
+ persistent: true,
18
+ ignoreInitial: true,
19
+ awaitWriteFinish: { stabilityThreshold: 500 },
20
+ });
21
+
22
+ watcher.on('add', filePath => reindex(filePath));
23
+ watcher.on('change', filePath => reindex(filePath));
24
+ watcher.on('unlink', filePath => {
25
+ const name = path.basename(filePath, '.md');
26
+ const db = getDb();
27
+ db.prepare('DELETE FROM skills WHERE name = ?').run(name);
28
+ db.prepare('DELETE FROM edges WHERE from_skill = ? OR to_skill = ?').run(name, name);
29
+ console.error(`[PromptGraph] Removed: ${name}`);
30
+ });
31
+
32
+ console.error('[PromptGraph] Watcher started');
33
+ }
34
+
35
+ function getSource(filePath) {
36
+ for (const { dir, source } of SOURCES) {
37
+ if (filePath.startsWith(dir)) return source;
38
+ }
39
+ return 'unknown';
40
+ }
41
+
42
+ async function reindex(filePath) {
43
+ if (!filePath.endsWith('.md')) return;
44
+ try {
45
+ await indexFile(filePath, getSource(filePath));
46
+ console.error(`[PromptGraph] Reindexed: ${path.basename(filePath)}`);
47
+ } catch (e) {
48
+ console.error(`[PromptGraph] Error reindexing ${filePath}: ${e.message}`);
49
+ }
50
+ }