promptgraph-mcp 2.2.8 → 2.2.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/github-import.js +138 -42
- package/package.json +1 -1
package/github-import.js
CHANGED
|
@@ -1,43 +1,121 @@
|
|
|
1
1
|
import { spawnSync } from 'child_process';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import os from 'os';
|
|
4
3
|
import fs from 'fs';
|
|
5
4
|
import https from 'https';
|
|
6
5
|
import { globSync } from 'glob';
|
|
7
6
|
import { indexAll, indexSource } from './indexer.js';
|
|
8
7
|
import { loadConfig, saveConfig, SKILLS_STORE_DIR } from './config.js';
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
const SKILL_DIRS = ['skills', 'commands', 'prompts', 'agents', 'skills-store', 'slash-commands', 'custom-commands', 'templates'];
|
|
10
|
+
|
|
11
|
+
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
function httpsGet(url) {
|
|
14
|
+
return new Promise((res, rej) => {
|
|
15
|
+
const req = https.get(url, { headers: { 'User-Agent': 'promptgraph-mcp' } }, r => {
|
|
16
|
+
if (r.statusCode >= 300 && r.statusCode < 400 && r.headers.location)
|
|
17
|
+
return httpsGet(r.headers.location).then(res, rej);
|
|
18
|
+
if (r.statusCode !== 200) { r.resume(); return rej(new Error(`HTTP ${r.statusCode}`)); }
|
|
19
|
+
let d = ''; r.setEncoding('utf8'); r.on('data', c => d += c); r.on('end', () => res(d));
|
|
20
|
+
});
|
|
21
|
+
req.on('error', rej);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function repoExists(ownerRepo) {
|
|
14
26
|
return new Promise(resolve => {
|
|
15
27
|
const req = https.request(
|
|
16
|
-
{ host: 'github.com', path: `/${
|
|
17
|
-
|
|
28
|
+
{ host: 'github.com', path: `/${ownerRepo}`, method: 'HEAD', headers: { 'User-Agent': 'promptgraph-mcp' } },
|
|
29
|
+
r => resolve(r.statusCode < 400)
|
|
18
30
|
);
|
|
19
31
|
req.on('error', () => resolve(false));
|
|
20
32
|
req.end();
|
|
21
33
|
});
|
|
22
34
|
}
|
|
23
35
|
|
|
24
|
-
//
|
|
25
|
-
|
|
36
|
+
// Ask GitHub API which skill subdir exists (without cloning anything)
|
|
37
|
+
async function detectSkillsDirFromAPI(ownerRepo) {
|
|
38
|
+
try {
|
|
39
|
+
const json = await httpsGet(`https://api.github.com/repos/${ownerRepo}/contents`);
|
|
40
|
+
const entries = JSON.parse(json);
|
|
41
|
+
const dirs = new Set(entries.filter(e => e.type === 'dir').map(e => e.name.toLowerCase()));
|
|
42
|
+
for (const d of SKILL_DIRS) {
|
|
43
|
+
if (dirs.has(d)) return d;
|
|
44
|
+
}
|
|
45
|
+
} catch {}
|
|
46
|
+
return null; // use repo root
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function git(args, cwd, stdio = 'inherit') {
|
|
50
|
+
return spawnSync('git', args, { cwd, stdio });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Clone only the skills subdir via sparse-checkout
|
|
54
|
+
function sparseClone(url, dest, subdir) {
|
|
55
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
56
|
+
|
|
57
|
+
// 1. init + add remote
|
|
58
|
+
if (git(['init'], dest, 'pipe').status !== 0) return false;
|
|
59
|
+
if (git(['remote', 'add', 'origin', url], dest, 'pipe').status !== 0) return false;
|
|
60
|
+
|
|
61
|
+
// 2. sparse-checkout config
|
|
62
|
+
git(['sparse-checkout', 'init', '--cone'], dest, 'pipe');
|
|
63
|
+
git(['sparse-checkout', 'set', subdir], dest, 'pipe');
|
|
64
|
+
|
|
65
|
+
// 3. fetch + checkout (depth=1)
|
|
66
|
+
const fetch = git(['fetch', '--depth=1', 'origin'], dest);
|
|
67
|
+
if (fetch.status !== 0) return false;
|
|
68
|
+
|
|
69
|
+
// Try HEAD, then main, then master
|
|
70
|
+
for (const branch of ['HEAD', 'main', 'master']) {
|
|
71
|
+
const r = git(['checkout', branch === 'HEAD' ? 'FETCH_HEAD' : branch], dest, 'pipe');
|
|
72
|
+
if (r.status === 0) return true;
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Update sparse repo — fetch + reset
|
|
78
|
+
function sparseUpdate(dest, subdir) {
|
|
79
|
+
const fetch = git(['fetch', '--depth=1', 'origin'], dest);
|
|
80
|
+
if (fetch.status !== 0) return false;
|
|
26
81
|
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
82
|
+
// Ensure sparse-checkout still set correctly
|
|
83
|
+
git(['sparse-checkout', 'set', subdir], dest, 'pipe');
|
|
84
|
+
|
|
85
|
+
for (const ref of ['origin/HEAD', 'origin/main', 'origin/master']) {
|
|
86
|
+
const r = git(['reset', '--hard', ref], dest, 'pipe');
|
|
87
|
+
if (r.status === 0) return true;
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Fallback: full clone (when no subdir found)
|
|
93
|
+
function fullClone(url, dest) {
|
|
94
|
+
if (fs.existsSync(dest)) {
|
|
95
|
+
const fetch = git(['fetch', '--depth=1', 'origin'], dest);
|
|
96
|
+
if (fetch.status !== 0) return false;
|
|
97
|
+
for (const ref of ['origin/HEAD', 'origin/main', 'origin/master']) {
|
|
98
|
+
if (git(['reset', '--hard', ref], dest, 'pipe').status === 0) return true;
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
return git(['clone', '--depth=1', url, dest]).status === 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// After clone: detect actual skills dir on disk
|
|
106
|
+
function detectSkillsDirLocal(repoRoot) {
|
|
31
107
|
for (const dir of SKILL_DIRS) {
|
|
32
108
|
const candidate = path.join(repoRoot, dir);
|
|
33
109
|
if (fs.existsSync(candidate)) {
|
|
34
110
|
const files = globSync(`${candidate}/**/*.md`);
|
|
35
|
-
if (files.length >= 2) return { dir: candidate,
|
|
111
|
+
if (files.length >= 2) return { dir: candidate, label: dir, sparse: true };
|
|
36
112
|
}
|
|
37
113
|
}
|
|
38
|
-
return { dir: repoRoot,
|
|
114
|
+
return { dir: repoRoot, label: '(root)', sparse: false };
|
|
39
115
|
}
|
|
40
116
|
|
|
117
|
+
// ── main export ───────────────────────────────────────────────────────────────
|
|
118
|
+
|
|
41
119
|
export async function importFromGitHub(repoUrl) {
|
|
42
120
|
if (!repoUrl) {
|
|
43
121
|
console.error('Usage: promptgraph-mcp import <github-url-or-owner/repo>');
|
|
@@ -45,58 +123,76 @@ export async function importFromGitHub(repoUrl) {
|
|
|
45
123
|
}
|
|
46
124
|
|
|
47
125
|
const url = repoUrl.startsWith('http') ? repoUrl : `https://github.com/${repoUrl}`;
|
|
48
|
-
const
|
|
126
|
+
const ownerRepo = url.replace(/^https?:\/\/github\.com\//, '').replace(/\.git$/, '');
|
|
127
|
+
const repoName = ownerRepo.replace('/', '-');
|
|
49
128
|
const dest = path.join(SKILLS_STORE_DIR, 'github', repoName);
|
|
50
129
|
|
|
51
|
-
|
|
52
|
-
|
|
130
|
+
const isNew = !fs.existsSync(dest);
|
|
131
|
+
|
|
132
|
+
if (isNew) {
|
|
133
|
+
const exists = await repoExists(ownerRepo);
|
|
53
134
|
if (!exists) throw new Error(`Repository not found (404): ${url}`);
|
|
54
135
|
}
|
|
55
136
|
|
|
56
137
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
57
138
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
139
|
+
let skillsSubdir = null;
|
|
140
|
+
let cloneOk = false;
|
|
141
|
+
|
|
142
|
+
if (isNew) {
|
|
143
|
+
// Detect skills dir via API before cloning
|
|
144
|
+
console.log(`Detecting skills directory for ${ownerRepo}...`);
|
|
145
|
+
skillsSubdir = await detectSkillsDirFromAPI(ownerRepo);
|
|
146
|
+
|
|
147
|
+
if (skillsSubdir) {
|
|
148
|
+
console.log(`Sparse-cloning ${url} (${skillsSubdir}/ only)...`);
|
|
149
|
+
cloneOk = sparseClone(url, dest, skillsSubdir);
|
|
150
|
+
if (!cloneOk) {
|
|
151
|
+
// Sparse failed — fall back to full clone
|
|
152
|
+
console.log('Sparse-checkout failed, falling back to full clone...');
|
|
153
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
154
|
+
cloneOk = fullClone(url, dest);
|
|
155
|
+
skillsSubdir = null;
|
|
70
156
|
}
|
|
157
|
+
} else {
|
|
158
|
+
console.log(`Cloning ${url}...`);
|
|
159
|
+
cloneOk = fullClone(url, dest);
|
|
71
160
|
}
|
|
161
|
+
|
|
162
|
+
if (!cloneOk) throw new Error(`Clone failed for ${url}`);
|
|
72
163
|
} else {
|
|
73
|
-
console.log(`
|
|
74
|
-
|
|
75
|
-
|
|
164
|
+
console.log(`Updating ${repoName}...`);
|
|
165
|
+
// Detect existing sparse subdir
|
|
166
|
+
const isSparse = git(['sparse-checkout', 'list'], dest, 'pipe').status === 0;
|
|
167
|
+
const sparseList = isSparse
|
|
168
|
+
? spawnSync('git', ['sparse-checkout', 'list'], { cwd: dest, encoding: 'utf8' }).stdout.trim()
|
|
169
|
+
: '';
|
|
170
|
+
skillsSubdir = sparseList.split('\n').find(l => SKILL_DIRS.includes(l.trim())) || null;
|
|
171
|
+
|
|
172
|
+
cloneOk = skillsSubdir
|
|
173
|
+
? sparseUpdate(dest, skillsSubdir)
|
|
174
|
+
: fullClone(url, dest);
|
|
175
|
+
if (!cloneOk) throw new Error(`Update failed for ${repoName}`);
|
|
76
176
|
}
|
|
77
177
|
|
|
78
|
-
const { dir: skillsDir,
|
|
178
|
+
const { dir: skillsDir, label } = detectSkillsDirLocal(dest);
|
|
79
179
|
const mdFiles = globSync(`${skillsDir}/**/*.md`);
|
|
80
180
|
|
|
81
|
-
if (
|
|
82
|
-
console.log(`
|
|
181
|
+
if (skillsSubdir) {
|
|
182
|
+
console.log(`Sparse-checkout: ${label}/ only (${mdFiles.length} .md files, no other repo files)`);
|
|
83
183
|
} else {
|
|
84
|
-
console.log(`
|
|
184
|
+
console.log(`Full clone: scanning ${label} (${mdFiles.length} .md files)`);
|
|
85
185
|
}
|
|
86
186
|
|
|
87
|
-
if (mdFiles.length < 1)
|
|
88
|
-
console.warn('Warning: no .md files found');
|
|
89
|
-
}
|
|
187
|
+
if (mdFiles.length < 1) console.warn('Warning: no .md files found');
|
|
90
188
|
|
|
91
189
|
const config = loadConfig();
|
|
92
190
|
const repoSource = `github:${repoName}`;
|
|
93
191
|
if (!config.sources.find(s => s.dir === skillsDir)) {
|
|
94
|
-
// Remove any old entry pointing at the full repo root if we now have a subdir
|
|
95
192
|
const oldIdx = config.sources.findIndex(s => s.source === repoSource);
|
|
96
193
|
if (oldIdx !== -1) config.sources.splice(oldIdx, 1);
|
|
97
194
|
config.sources.push({ dir: skillsDir, source: repoSource });
|
|
98
195
|
saveConfig(config);
|
|
99
|
-
console.log(`Indexing from: ${skillsDir}`);
|
|
100
196
|
}
|
|
101
197
|
|
|
102
198
|
console.log();
|