promptgraph-mcp 2.8.1 → 2.8.3
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 +205 -205
- package/ann.js +33 -33
- package/api.js +202 -202
- package/bundle-counts.js +111 -111
- package/chunker.js +28 -28
- package/cli.js +115 -115
- package/commands/bundle.js +150 -150
- package/commands/doctor.js +15 -15
- package/commands/import.js +7 -7
- package/commands/init.js +37 -37
- package/commands/marketplace.js +146 -146
- package/commands/reindex.js +10 -10
- package/commands/search.js +55 -55
- package/commands/setup.js +19 -19
- package/commands/status.js +110 -110
- package/commands/train.js +18 -18
- package/commands/update.js +49 -49
- package/commands/validate.js +63 -63
- package/config.js +72 -72
- package/db.js +157 -157
- package/doctor.js +48 -48
- package/embedder.js +54 -54
- package/github-import.js +750 -745
- package/indexer.js +310 -310
- package/package.json +61 -61
- package/parser.js +69 -69
- package/pg-hook.js +70 -70
- package/platform.js +120 -120
- package/search.js +216 -216
- package/src/filter/classifier.js +88 -88
- package/src/filter/hard-filter.js +62 -62
- package/src/filter/train.js +66 -66
- package/src/reranker/reranker.js +92 -92
- package/src/store/flat-store.js +61 -61
- package/src/store/hnsw-store.js +187 -187
- package/src/store/index.js +19 -19
- package/src/store/vector-store.js +9 -9
- package/src/utils/rate-limiter.js +33 -33
- package/tui.js +418 -418
- package/validate-repo-action.js +139 -139
- package/watcher.js +84 -84
package/chunker.js
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
const CHUNK_SIZE = 800;
|
|
2
|
-
const CHUNK_OVERLAP = 100;
|
|
3
|
-
const MAX_CHUNKS = parseInt(process.env.PG_MAX_CHUNKS, 10) || 32;
|
|
4
|
-
|
|
5
|
-
export function chunkText(text) {
|
|
6
|
-
// Split on markdown h1/h2/h3 headers to preserve semantic boundaries
|
|
7
|
-
const sections = text.split(/(?=\n#{1,3} )/);
|
|
8
|
-
const chunks = [];
|
|
9
|
-
|
|
10
|
-
for (const section of sections) {
|
|
11
|
-
const words = section.split(/\s+/).filter(Boolean);
|
|
12
|
-
if (words.length === 0) continue;
|
|
13
|
-
|
|
14
|
-
if (words.length <= CHUNK_SIZE) {
|
|
15
|
-
chunks.push(section.trim());
|
|
16
|
-
} else {
|
|
17
|
-
let i = 0;
|
|
18
|
-
while (i < words.length) {
|
|
19
|
-
chunks.push(words.slice(i, i + CHUNK_SIZE).join(' '));
|
|
20
|
-
if (i + CHUNK_SIZE >= words.length) break;
|
|
21
|
-
i += CHUNK_SIZE - CHUNK_OVERLAP;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const result = chunks.length > 0 ? chunks : [text];
|
|
27
|
-
return result.slice(0, MAX_CHUNKS);
|
|
28
|
-
}
|
|
1
|
+
const CHUNK_SIZE = 800;
|
|
2
|
+
const CHUNK_OVERLAP = 100;
|
|
3
|
+
const MAX_CHUNKS = parseInt(process.env.PG_MAX_CHUNKS, 10) || 32;
|
|
4
|
+
|
|
5
|
+
export function chunkText(text) {
|
|
6
|
+
// Split on markdown h1/h2/h3 headers to preserve semantic boundaries
|
|
7
|
+
const sections = text.split(/(?=\n#{1,3} )/);
|
|
8
|
+
const chunks = [];
|
|
9
|
+
|
|
10
|
+
for (const section of sections) {
|
|
11
|
+
const words = section.split(/\s+/).filter(Boolean);
|
|
12
|
+
if (words.length === 0) continue;
|
|
13
|
+
|
|
14
|
+
if (words.length <= CHUNK_SIZE) {
|
|
15
|
+
chunks.push(section.trim());
|
|
16
|
+
} else {
|
|
17
|
+
let i = 0;
|
|
18
|
+
while (i < words.length) {
|
|
19
|
+
chunks.push(words.slice(i, i + CHUNK_SIZE).join(' '));
|
|
20
|
+
if (i + CHUNK_SIZE >= words.length) break;
|
|
21
|
+
i += CHUNK_SIZE - CHUNK_OVERLAP;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const result = chunks.length > 0 ? chunks : [text];
|
|
27
|
+
return result.slice(0, MAX_CHUNKS);
|
|
28
|
+
}
|
package/cli.js
CHANGED
|
@@ -1,115 +1,115 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import ora from 'ora';
|
|
3
|
-
import boxen from 'boxen';
|
|
4
|
-
import fs from 'fs';
|
|
5
|
-
|
|
6
|
-
export const colors = {
|
|
7
|
-
primary: chalk.hex('#7C3AED'),
|
|
8
|
-
success: chalk.hex('#10B981'),
|
|
9
|
-
warning: chalk.hex('#F59E0B'),
|
|
10
|
-
error: chalk.hex('#EF4444'),
|
|
11
|
-
muted: chalk.hex('#6B7280'),
|
|
12
|
-
white: chalk.white,
|
|
13
|
-
bold: chalk.bold,
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
function getVersion() {
|
|
17
|
-
try {
|
|
18
|
-
const pkg = JSON.parse(fs.readFileSync(new URL('./package.json', import.meta.url), 'utf8'));
|
|
19
|
-
return pkg.version;
|
|
20
|
-
} catch { return ''; }
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function banner() {
|
|
24
|
-
console.log(
|
|
25
|
-
boxen(
|
|
26
|
-
colors.primary.bold('PromptGraph') + ' ' + colors.muted('v' + getVersion()) + '\n' +
|
|
27
|
-
colors.muted('Semantic skill router for Claude Code'),
|
|
28
|
-
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: '#7C3AED', dimBorder: true }
|
|
29
|
-
)
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function spinner(text) {
|
|
34
|
-
return ora({ text: colors.muted(text), spinner: 'dots', color: 'magenta' });
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Full clear including scrollback (console.clear leaves scrollback on Windows)
|
|
38
|
-
export function clearScreen() {
|
|
39
|
-
process.stdout.write('\x1b[2J\x1b[3J\x1b[H');
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function success(msg) {
|
|
43
|
-
console.log('\n' + colors.success('✓') + ' ' + msg);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function error(msg) {
|
|
47
|
-
console.log(colors.error('✗') + ' ' + msg);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function info(msg) {
|
|
51
|
-
console.log(colors.muted(' ' + msg));
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function section(title) {
|
|
55
|
-
console.log('\n' + colors.primary.bold(title));
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
let _progressActive = false;
|
|
59
|
-
|
|
60
|
-
export function progress(current, total, { skipped = 0, eta = '?', errors = 0 } = {}) {
|
|
61
|
-
const pct = Math.round(current / total * 100);
|
|
62
|
-
const bar = buildBar(pct);
|
|
63
|
-
|
|
64
|
-
const stats = [
|
|
65
|
-
colors.white.bold(String(pct).padStart(3) + '%'),
|
|
66
|
-
colors.muted(current + '/' + total),
|
|
67
|
-
skipped > 0 ? colors.muted('skip ' + skipped) : '',
|
|
68
|
-
errors > 0 ? colors.error('err ' + errors) : '',
|
|
69
|
-
eta !== '?' ? colors.muted('eta ' + formatTime(eta)) : '',
|
|
70
|
-
].filter(Boolean).join(' ');
|
|
71
|
-
|
|
72
|
-
process.stdout.write('\r ' + bar + ' ' + stats + ' ');
|
|
73
|
-
_progressActive = true;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function progressDone() {
|
|
77
|
-
if (_progressActive) {
|
|
78
|
-
process.stdout.write('\n');
|
|
79
|
-
_progressActive = false;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function buildBar(pct) {
|
|
84
|
-
const width = 24;
|
|
85
|
-
const filled = Math.round(pct / 100 * width);
|
|
86
|
-
const empty = width - filled;
|
|
87
|
-
return colors.primary('█'.repeat(filled)) + colors.muted('░'.repeat(empty));
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function formatTime(seconds) {
|
|
91
|
-
if (seconds < 60) return seconds + 's';
|
|
92
|
-
const m = Math.floor(seconds / 60);
|
|
93
|
-
const s = seconds % 60;
|
|
94
|
-
return m + 'm ' + s + 's';
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export function table(rows) {
|
|
98
|
-
if (!rows.length) { info('No results'); return; }
|
|
99
|
-
const cols = Object.keys(rows[0]);
|
|
100
|
-
const widths = cols.map(c => Math.max(c.length, ...rows.map(r => String(r[c] ?? '').length)));
|
|
101
|
-
const header = cols.map((c, i) => colors.muted(c.toUpperCase().padEnd(widths[i]))).join(' ');
|
|
102
|
-
const divider = colors.muted(widths.map(w => '─'.repeat(w)).join('──'));
|
|
103
|
-
console.log('\n' + header);
|
|
104
|
-
console.log(divider);
|
|
105
|
-
for (const row of rows) {
|
|
106
|
-
const line = cols.map((c, i) => {
|
|
107
|
-
const val = String(row[c] ?? '');
|
|
108
|
-
if (c === 'score' || c === 'rating') return colors.primary(val.padEnd(widths[i]));
|
|
109
|
-
if (c === 'name') return colors.white.bold(val.padEnd(widths[i]));
|
|
110
|
-
return colors.muted(val.padEnd(widths[i]));
|
|
111
|
-
}).join(' ');
|
|
112
|
-
console.log(line);
|
|
113
|
-
}
|
|
114
|
-
console.log();
|
|
115
|
-
}
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import boxen from 'boxen';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
|
|
6
|
+
export const colors = {
|
|
7
|
+
primary: chalk.hex('#7C3AED'),
|
|
8
|
+
success: chalk.hex('#10B981'),
|
|
9
|
+
warning: chalk.hex('#F59E0B'),
|
|
10
|
+
error: chalk.hex('#EF4444'),
|
|
11
|
+
muted: chalk.hex('#6B7280'),
|
|
12
|
+
white: chalk.white,
|
|
13
|
+
bold: chalk.bold,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function getVersion() {
|
|
17
|
+
try {
|
|
18
|
+
const pkg = JSON.parse(fs.readFileSync(new URL('./package.json', import.meta.url), 'utf8'));
|
|
19
|
+
return pkg.version;
|
|
20
|
+
} catch { return ''; }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function banner() {
|
|
24
|
+
console.log(
|
|
25
|
+
boxen(
|
|
26
|
+
colors.primary.bold('PromptGraph') + ' ' + colors.muted('v' + getVersion()) + '\n' +
|
|
27
|
+
colors.muted('Semantic skill router for Claude Code'),
|
|
28
|
+
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: '#7C3AED', dimBorder: true }
|
|
29
|
+
)
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function spinner(text) {
|
|
34
|
+
return ora({ text: colors.muted(text), spinner: 'dots', color: 'magenta' });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Full clear including scrollback (console.clear leaves scrollback on Windows)
|
|
38
|
+
export function clearScreen() {
|
|
39
|
+
process.stdout.write('\x1b[2J\x1b[3J\x1b[H');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function success(msg) {
|
|
43
|
+
console.log('\n' + colors.success('✓') + ' ' + msg);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function error(msg) {
|
|
47
|
+
console.log(colors.error('✗') + ' ' + msg);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function info(msg) {
|
|
51
|
+
console.log(colors.muted(' ' + msg));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function section(title) {
|
|
55
|
+
console.log('\n' + colors.primary.bold(title));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let _progressActive = false;
|
|
59
|
+
|
|
60
|
+
export function progress(current, total, { skipped = 0, eta = '?', errors = 0 } = {}) {
|
|
61
|
+
const pct = Math.round(current / total * 100);
|
|
62
|
+
const bar = buildBar(pct);
|
|
63
|
+
|
|
64
|
+
const stats = [
|
|
65
|
+
colors.white.bold(String(pct).padStart(3) + '%'),
|
|
66
|
+
colors.muted(current + '/' + total),
|
|
67
|
+
skipped > 0 ? colors.muted('skip ' + skipped) : '',
|
|
68
|
+
errors > 0 ? colors.error('err ' + errors) : '',
|
|
69
|
+
eta !== '?' ? colors.muted('eta ' + formatTime(eta)) : '',
|
|
70
|
+
].filter(Boolean).join(' ');
|
|
71
|
+
|
|
72
|
+
process.stdout.write('\r ' + bar + ' ' + stats + ' ');
|
|
73
|
+
_progressActive = true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function progressDone() {
|
|
77
|
+
if (_progressActive) {
|
|
78
|
+
process.stdout.write('\n');
|
|
79
|
+
_progressActive = false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function buildBar(pct) {
|
|
84
|
+
const width = 24;
|
|
85
|
+
const filled = Math.round(pct / 100 * width);
|
|
86
|
+
const empty = width - filled;
|
|
87
|
+
return colors.primary('█'.repeat(filled)) + colors.muted('░'.repeat(empty));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function formatTime(seconds) {
|
|
91
|
+
if (seconds < 60) return seconds + 's';
|
|
92
|
+
const m = Math.floor(seconds / 60);
|
|
93
|
+
const s = seconds % 60;
|
|
94
|
+
return m + 'm ' + s + 's';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function table(rows) {
|
|
98
|
+
if (!rows.length) { info('No results'); return; }
|
|
99
|
+
const cols = Object.keys(rows[0]);
|
|
100
|
+
const widths = cols.map(c => Math.max(c.length, ...rows.map(r => String(r[c] ?? '').length)));
|
|
101
|
+
const header = cols.map((c, i) => colors.muted(c.toUpperCase().padEnd(widths[i]))).join(' ');
|
|
102
|
+
const divider = colors.muted(widths.map(w => '─'.repeat(w)).join('──'));
|
|
103
|
+
console.log('\n' + header);
|
|
104
|
+
console.log(divider);
|
|
105
|
+
for (const row of rows) {
|
|
106
|
+
const line = cols.map((c, i) => {
|
|
107
|
+
const val = String(row[c] ?? '');
|
|
108
|
+
if (c === 'score' || c === 'rating') return colors.primary(val.padEnd(widths[i]));
|
|
109
|
+
if (c === 'name') return colors.white.bold(val.padEnd(widths[i]));
|
|
110
|
+
return colors.muted(val.padEnd(widths[i]));
|
|
111
|
+
}).join(' ');
|
|
112
|
+
console.log(line);
|
|
113
|
+
}
|
|
114
|
+
console.log();
|
|
115
|
+
}
|
package/commands/bundle.js
CHANGED
|
@@ -1,150 +1,150 @@
|
|
|
1
|
-
import { colors, banner, success, error, info, section, table } from '../cli.js';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import os from 'os';
|
|
5
|
-
import fs from 'fs';
|
|
6
|
-
import { spawnSync } from 'child_process';
|
|
7
|
-
|
|
8
|
-
export default async function handler(args, bin) {
|
|
9
|
-
if (args[1] === 'update') {
|
|
10
|
-
const { loadConfig: _lcUpd, SKILLS_STORE_DIR: _ssDir } = await import('../config.js');
|
|
11
|
-
const { indexSource } = await import('../indexer.js');
|
|
12
|
-
const cfg = _lcUpd();
|
|
13
|
-
const githubSources = cfg.sources.filter(s => s.source.startsWith('github:'));
|
|
14
|
-
|
|
15
|
-
if (!githubSources.length) { info('No GitHub bundles installed.'); process.exit(0); }
|
|
16
|
-
|
|
17
|
-
const targetId = args[2];
|
|
18
|
-
const toUpdate = targetId
|
|
19
|
-
? githubSources.filter(s => s.source.toLowerCase().includes(targetId.toLowerCase()))
|
|
20
|
-
: githubSources;
|
|
21
|
-
|
|
22
|
-
if (!toUpdate.length) { error(`No installed bundle matching "${targetId}"`); process.exit(1); }
|
|
23
|
-
|
|
24
|
-
let updated = 0, unchanged = 0, failed = 0;
|
|
25
|
-
|
|
26
|
-
for (const src of toUpdate) {
|
|
27
|
-
const repoName = src.source.replace('github:', '');
|
|
28
|
-
const dest = src.dir.replace(/[/\\]skills$|[/\\]commands$|[/\\]prompts$/, '');
|
|
29
|
-
const repoRoot = fs.existsSync(path.join(dest, '.git')) ? dest : src.dir;
|
|
30
|
-
|
|
31
|
-
if (!fs.existsSync(path.join(repoRoot, '.git'))) {
|
|
32
|
-
console.log(chalk.gray(` skip ${repoName} (not a git repo)`));
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
process.stdout.write(` Checking ${chalk.white(repoName)}... `);
|
|
37
|
-
|
|
38
|
-
const before = spawnSync('git', ['-C', repoRoot, 'rev-parse', 'HEAD'], { encoding: 'utf8', timeout: 30000 }).stdout.trim();
|
|
39
|
-
|
|
40
|
-
const fetch = spawnSync('git', ['-C', repoRoot, 'fetch', '--depth=1', 'origin'], { stdio: 'pipe', timeout: 60000 });
|
|
41
|
-
if (fetch.status !== 0) {
|
|
42
|
-
console.log(chalk.red('fetch failed'));
|
|
43
|
-
failed++;
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
const reset = spawnSync('git', ['-C', repoRoot, 'reset', '--hard', 'origin/HEAD'], { stdio: 'pipe', timeout: 30000 });
|
|
47
|
-
if (reset.status !== 0) {
|
|
48
|
-
spawnSync('git', ['-C', repoRoot, 'reset', '--hard', 'origin/main'], { stdio: 'pipe', timeout: 30000 });
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const after = spawnSync('git', ['-C', repoRoot, 'rev-parse', 'HEAD'], { encoding: 'utf8', timeout: 30000 }).stdout.trim();
|
|
52
|
-
|
|
53
|
-
if (before === after) {
|
|
54
|
-
console.log(chalk.gray('already up to date'));
|
|
55
|
-
unchanged++;
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const diff = spawnSync('git', ['-C', repoRoot, 'diff', '--name-only', before, after], { encoding: 'utf8', timeout: 15000 });
|
|
60
|
-
const changedMd = (diff.stdout || '').split('\n').filter(f => f.endsWith('.md')).length;
|
|
61
|
-
console.log(chalk.green(`${changedMd} files changed`) + chalk.gray(` (${before.slice(0,7)} → ${after.slice(0,7)})`));
|
|
62
|
-
|
|
63
|
-
await indexSource(src.dir, src.source);
|
|
64
|
-
updated++;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
console.log();
|
|
68
|
-
if (updated) success(`Updated ${updated} bundle(s)`);
|
|
69
|
-
if (unchanged) info(chalk.gray(`${unchanged} already up to date`));
|
|
70
|
-
if (failed) error(`${failed} failed`);
|
|
71
|
-
process.exit(failed > 0 ? 1 : 0);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (args[1] === 'install') {
|
|
75
|
-
const { installBundle } = await import('../marketplace.js');
|
|
76
|
-
const result = await installBundle(args[2]);
|
|
77
|
-
if (result?.error) { error(result.error); process.exit(1); }
|
|
78
|
-
success(result.type === 'repo_import' ? `Imported from ${result.repo_url}` : `Installed ${result.installed?.length || 0} skills`);
|
|
79
|
-
process.exit(0);
|
|
80
|
-
}
|
|
81
|
-
if (args[1] === 'add-repo') {
|
|
82
|
-
const doPush = args[args.length - 1] === '--push';
|
|
83
|
-
const repoArg = doPush ? args[2] : args[2];
|
|
84
|
-
if (!repoArg || !repoArg.includes('/')) { error('Usage: pg bundle add-repo <owner/repo> [--push]'); process.exit(1); }
|
|
85
|
-
const repo = repoArg.replace('https://github.com/', '').replace('.git', '');
|
|
86
|
-
|
|
87
|
-
const { detectSkillsDirFromAPI: _detectDir } = await import('../github-import.js');
|
|
88
|
-
process.stdout.write(chalk.gray(` Checking ${repo} for skill subdirectory... `));
|
|
89
|
-
const detected = await _detectDir(repo);
|
|
90
|
-
if (!detected) {
|
|
91
|
-
console.log(chalk.red('none found'));
|
|
92
|
-
error(
|
|
93
|
-
`Cannot publish: no skill subdirectory found in ${repo}\n` +
|
|
94
|
-
` Expected: skills/, prompts/, commands/, agents/, or any folder with .md files\n` +
|
|
95
|
-
` Visit: https://github.com/${repo}`
|
|
96
|
-
);
|
|
97
|
-
process.exit(1);
|
|
98
|
-
}
|
|
99
|
-
console.log(chalk.green(`found: ${detected.label}/`));
|
|
100
|
-
const name = repo.split('/')[1].replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
101
|
-
const id = repo.replace('/', '-').toLowerCase();
|
|
102
|
-
const bundle = {
|
|
103
|
-
id, name, repo_url: repo, author: repo.split('/')[0],
|
|
104
|
-
description: `Skills from ${repo}`,
|
|
105
|
-
tags: ['community'],
|
|
106
|
-
stars: 0
|
|
107
|
-
};
|
|
108
|
-
const json = JSON.stringify(bundle, null, 2);
|
|
109
|
-
|
|
110
|
-
if (doPush) {
|
|
111
|
-
const registryDir = path.join(os.tmpdir(), 'pg-push-registry');
|
|
112
|
-
const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: '0' };
|
|
113
|
-
const git = (gArgs, opts = {}) => { const r = spawnSync('git', gArgs, { ...opts, env: gitEnv, stdio: 'pipe', timeout: 60000 }); if (r.status !== 0) { error(r.stderr?.toString() || r.stdout?.toString() || 'git error'); process.exit(1); } return r; };
|
|
114
|
-
if (fs.existsSync(registryDir)) {
|
|
115
|
-
git(['-C', registryDir, 'pull']);
|
|
116
|
-
} else {
|
|
117
|
-
git(['clone', '--depth=1', 'https://github.com/NeiP4n/promptgraph-registry.git', registryDir]);
|
|
118
|
-
}
|
|
119
|
-
const regFile = path.join(registryDir, 'registry.json');
|
|
120
|
-
const reg = JSON.parse(fs.readFileSync(regFile, 'utf8'));
|
|
121
|
-
if (reg.bundles.find(b => b.id === id)) { error(`Bundle "${id}" already exists`); process.exit(1); }
|
|
122
|
-
reg.bundles.push(bundle);
|
|
123
|
-
reg.updated = new Date().toISOString().slice(0, 10);
|
|
124
|
-
fs.writeFileSync(regFile, JSON.stringify(reg, null, 2) + '\n');
|
|
125
|
-
fs.writeFileSync(path.join(registryDir, 'bundles', `${id}.json`), json + '\n');
|
|
126
|
-
git(['-C', registryDir, 'config', 'user.email', 'pg-bot@promptgraph.ai']);
|
|
127
|
-
git(['-C', registryDir, 'config', 'user.name', 'PromptGraph Bot']);
|
|
128
|
-
git(['-C', registryDir, 'add', '-A']);
|
|
129
|
-
git(['-C', registryDir, 'commit', '-m', `bundle: ${name} (${repo})`]);
|
|
130
|
-
git(['-C', registryDir, 'push']);
|
|
131
|
-
success(`Bundle "${id}" pushed to registry`);
|
|
132
|
-
} else {
|
|
133
|
-
const tmp = path.join(os.tmpdir(), `pg-bundle-${id}.json`);
|
|
134
|
-
fs.writeFileSync(tmp, json);
|
|
135
|
-
const { publishBundle } = await import('../marketplace.js');
|
|
136
|
-
const result = await publishBundle(tmp);
|
|
137
|
-
fs.unlinkSync(tmp);
|
|
138
|
-
if (result?.error) { error(result.error); process.exit(1); }
|
|
139
|
-
if (result.gh_not_installed) {
|
|
140
|
-
console.log('\n' + result.instructions);
|
|
141
|
-
console.log(chalk.gray('\nBundle JSON:\n') + chalk.white(json));
|
|
142
|
-
} else {
|
|
143
|
-
success(`Bundle proposed! Submit: ${result.submit_url}`);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
process.exit(0);
|
|
147
|
-
}
|
|
148
|
-
error('Usage: pg bundle install <id> | pg bundle add-repo <owner/repo> [--push]');
|
|
149
|
-
process.exit(1);
|
|
150
|
-
}
|
|
1
|
+
import { colors, banner, success, error, info, section, table } from '../cli.js';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import { spawnSync } from 'child_process';
|
|
7
|
+
|
|
8
|
+
export default async function handler(args, bin) {
|
|
9
|
+
if (args[1] === 'update') {
|
|
10
|
+
const { loadConfig: _lcUpd, SKILLS_STORE_DIR: _ssDir } = await import('../config.js');
|
|
11
|
+
const { indexSource } = await import('../indexer.js');
|
|
12
|
+
const cfg = _lcUpd();
|
|
13
|
+
const githubSources = cfg.sources.filter(s => s.source.startsWith('github:'));
|
|
14
|
+
|
|
15
|
+
if (!githubSources.length) { info('No GitHub bundles installed.'); process.exit(0); }
|
|
16
|
+
|
|
17
|
+
const targetId = args[2];
|
|
18
|
+
const toUpdate = targetId
|
|
19
|
+
? githubSources.filter(s => s.source.toLowerCase().includes(targetId.toLowerCase()))
|
|
20
|
+
: githubSources;
|
|
21
|
+
|
|
22
|
+
if (!toUpdate.length) { error(`No installed bundle matching "${targetId}"`); process.exit(1); }
|
|
23
|
+
|
|
24
|
+
let updated = 0, unchanged = 0, failed = 0;
|
|
25
|
+
|
|
26
|
+
for (const src of toUpdate) {
|
|
27
|
+
const repoName = src.source.replace('github:', '');
|
|
28
|
+
const dest = src.dir.replace(/[/\\]skills$|[/\\]commands$|[/\\]prompts$/, '');
|
|
29
|
+
const repoRoot = fs.existsSync(path.join(dest, '.git')) ? dest : src.dir;
|
|
30
|
+
|
|
31
|
+
if (!fs.existsSync(path.join(repoRoot, '.git'))) {
|
|
32
|
+
console.log(chalk.gray(` skip ${repoName} (not a git repo)`));
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
process.stdout.write(` Checking ${chalk.white(repoName)}... `);
|
|
37
|
+
|
|
38
|
+
const before = spawnSync('git', ['-C', repoRoot, 'rev-parse', 'HEAD'], { encoding: 'utf8', timeout: 30000 }).stdout.trim();
|
|
39
|
+
|
|
40
|
+
const fetch = spawnSync('git', ['-C', repoRoot, 'fetch', '--depth=1', 'origin'], { stdio: 'pipe', timeout: 60000 });
|
|
41
|
+
if (fetch.status !== 0) {
|
|
42
|
+
console.log(chalk.red('fetch failed'));
|
|
43
|
+
failed++;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const reset = spawnSync('git', ['-C', repoRoot, 'reset', '--hard', 'origin/HEAD'], { stdio: 'pipe', timeout: 30000 });
|
|
47
|
+
if (reset.status !== 0) {
|
|
48
|
+
spawnSync('git', ['-C', repoRoot, 'reset', '--hard', 'origin/main'], { stdio: 'pipe', timeout: 30000 });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const after = spawnSync('git', ['-C', repoRoot, 'rev-parse', 'HEAD'], { encoding: 'utf8', timeout: 30000 }).stdout.trim();
|
|
52
|
+
|
|
53
|
+
if (before === after) {
|
|
54
|
+
console.log(chalk.gray('already up to date'));
|
|
55
|
+
unchanged++;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const diff = spawnSync('git', ['-C', repoRoot, 'diff', '--name-only', before, after], { encoding: 'utf8', timeout: 15000 });
|
|
60
|
+
const changedMd = (diff.stdout || '').split('\n').filter(f => f.endsWith('.md')).length;
|
|
61
|
+
console.log(chalk.green(`${changedMd} files changed`) + chalk.gray(` (${before.slice(0,7)} → ${after.slice(0,7)})`));
|
|
62
|
+
|
|
63
|
+
await indexSource(src.dir, src.source);
|
|
64
|
+
updated++;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log();
|
|
68
|
+
if (updated) success(`Updated ${updated} bundle(s)`);
|
|
69
|
+
if (unchanged) info(chalk.gray(`${unchanged} already up to date`));
|
|
70
|
+
if (failed) error(`${failed} failed`);
|
|
71
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (args[1] === 'install') {
|
|
75
|
+
const { installBundle } = await import('../marketplace.js');
|
|
76
|
+
const result = await installBundle(args[2]);
|
|
77
|
+
if (result?.error) { error(result.error); process.exit(1); }
|
|
78
|
+
success(result.type === 'repo_import' ? `Imported from ${result.repo_url}` : `Installed ${result.installed?.length || 0} skills`);
|
|
79
|
+
process.exit(0);
|
|
80
|
+
}
|
|
81
|
+
if (args[1] === 'add-repo') {
|
|
82
|
+
const doPush = args[args.length - 1] === '--push';
|
|
83
|
+
const repoArg = doPush ? args[2] : args[2];
|
|
84
|
+
if (!repoArg || !repoArg.includes('/')) { error('Usage: pg bundle add-repo <owner/repo> [--push]'); process.exit(1); }
|
|
85
|
+
const repo = repoArg.replace('https://github.com/', '').replace('.git', '');
|
|
86
|
+
|
|
87
|
+
const { detectSkillsDirFromAPI: _detectDir } = await import('../github-import.js');
|
|
88
|
+
process.stdout.write(chalk.gray(` Checking ${repo} for skill subdirectory... `));
|
|
89
|
+
const detected = await _detectDir(repo);
|
|
90
|
+
if (!detected) {
|
|
91
|
+
console.log(chalk.red('none found'));
|
|
92
|
+
error(
|
|
93
|
+
`Cannot publish: no skill subdirectory found in ${repo}\n` +
|
|
94
|
+
` Expected: skills/, prompts/, commands/, agents/, or any folder with .md files\n` +
|
|
95
|
+
` Visit: https://github.com/${repo}`
|
|
96
|
+
);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
console.log(chalk.green(`found: ${detected.label}/`));
|
|
100
|
+
const name = repo.split('/')[1].replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
101
|
+
const id = repo.replace('/', '-').toLowerCase();
|
|
102
|
+
const bundle = {
|
|
103
|
+
id, name, repo_url: repo, author: repo.split('/')[0],
|
|
104
|
+
description: `Skills from ${repo}`,
|
|
105
|
+
tags: ['community'],
|
|
106
|
+
stars: 0
|
|
107
|
+
};
|
|
108
|
+
const json = JSON.stringify(bundle, null, 2);
|
|
109
|
+
|
|
110
|
+
if (doPush) {
|
|
111
|
+
const registryDir = path.join(os.tmpdir(), 'pg-push-registry');
|
|
112
|
+
const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: '0' };
|
|
113
|
+
const git = (gArgs, opts = {}) => { const r = spawnSync('git', gArgs, { ...opts, env: gitEnv, stdio: 'pipe', timeout: 60000 }); if (r.status !== 0) { error(r.stderr?.toString() || r.stdout?.toString() || 'git error'); process.exit(1); } return r; };
|
|
114
|
+
if (fs.existsSync(registryDir)) {
|
|
115
|
+
git(['-C', registryDir, 'pull']);
|
|
116
|
+
} else {
|
|
117
|
+
git(['clone', '--depth=1', 'https://github.com/NeiP4n/promptgraph-registry.git', registryDir]);
|
|
118
|
+
}
|
|
119
|
+
const regFile = path.join(registryDir, 'registry.json');
|
|
120
|
+
const reg = JSON.parse(fs.readFileSync(regFile, 'utf8'));
|
|
121
|
+
if (reg.bundles.find(b => b.id === id)) { error(`Bundle "${id}" already exists`); process.exit(1); }
|
|
122
|
+
reg.bundles.push(bundle);
|
|
123
|
+
reg.updated = new Date().toISOString().slice(0, 10);
|
|
124
|
+
fs.writeFileSync(regFile, JSON.stringify(reg, null, 2) + '\n');
|
|
125
|
+
fs.writeFileSync(path.join(registryDir, 'bundles', `${id}.json`), json + '\n');
|
|
126
|
+
git(['-C', registryDir, 'config', 'user.email', 'pg-bot@promptgraph.ai']);
|
|
127
|
+
git(['-C', registryDir, 'config', 'user.name', 'PromptGraph Bot']);
|
|
128
|
+
git(['-C', registryDir, 'add', '-A']);
|
|
129
|
+
git(['-C', registryDir, 'commit', '-m', `bundle: ${name} (${repo})`]);
|
|
130
|
+
git(['-C', registryDir, 'push']);
|
|
131
|
+
success(`Bundle "${id}" pushed to registry`);
|
|
132
|
+
} else {
|
|
133
|
+
const tmp = path.join(os.tmpdir(), `pg-bundle-${id}.json`);
|
|
134
|
+
fs.writeFileSync(tmp, json);
|
|
135
|
+
const { publishBundle } = await import('../marketplace.js');
|
|
136
|
+
const result = await publishBundle(tmp);
|
|
137
|
+
fs.unlinkSync(tmp);
|
|
138
|
+
if (result?.error) { error(result.error); process.exit(1); }
|
|
139
|
+
if (result.gh_not_installed) {
|
|
140
|
+
console.log('\n' + result.instructions);
|
|
141
|
+
console.log(chalk.gray('\nBundle JSON:\n') + chalk.white(json));
|
|
142
|
+
} else {
|
|
143
|
+
success(`Bundle proposed! Submit: ${result.submit_url}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
process.exit(0);
|
|
147
|
+
}
|
|
148
|
+
error('Usage: pg bundle install <id> | pg bundle add-repo <owner/repo> [--push]');
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
package/commands/doctor.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { colors, banner, success, error, info, section, table } from '../cli.js';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
|
|
4
|
-
export default async function handler(args, bin) {
|
|
5
|
-
const { runDoctor } = await import('../doctor.js');
|
|
6
|
-
const spin = (await import('../cli.js')).spinner('Checking database...');
|
|
7
|
-
spin.start();
|
|
8
|
-
const r = runDoctor();
|
|
9
|
-
spin.stop();
|
|
10
|
-
success('Database checked');
|
|
11
|
-
info(`Removed: ${r.orphanChunks} chunks, ${r.orphanRatings} ratings, ${r.orphanFromEdges + r.danglingEdges} edges`);
|
|
12
|
-
if (r.duplicatePaths > 0) info(chalk.yellow(`Warning: ${r.duplicatePaths} duplicate paths`));
|
|
13
|
-
info(chalk.gray(`Now: ${r.totalSkills} skills, ${r.totalChunks} chunks, ${r.totalEdges} edges`));
|
|
14
|
-
process.exit(0);
|
|
15
|
-
}
|
|
1
|
+
import { colors, banner, success, error, info, section, table } from '../cli.js';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
|
|
4
|
+
export default async function handler(args, bin) {
|
|
5
|
+
const { runDoctor } = await import('../doctor.js');
|
|
6
|
+
const spin = (await import('../cli.js')).spinner('Checking database...');
|
|
7
|
+
spin.start();
|
|
8
|
+
const r = runDoctor();
|
|
9
|
+
spin.stop();
|
|
10
|
+
success('Database checked');
|
|
11
|
+
info(`Removed: ${r.orphanChunks} chunks, ${r.orphanRatings} ratings, ${r.orphanFromEdges + r.danglingEdges} edges`);
|
|
12
|
+
if (r.duplicatePaths > 0) info(chalk.yellow(`Warning: ${r.duplicatePaths} duplicate paths`));
|
|
13
|
+
info(chalk.gray(`Now: ${r.totalSkills} skills, ${r.totalChunks} chunks, ${r.totalEdges} edges`));
|
|
14
|
+
process.exit(0);
|
|
15
|
+
}
|
package/commands/import.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { colors, banner, success, error, info, section, table } from '../cli.js';
|
|
2
|
-
|
|
3
|
-
export default async function handler(args, bin) {
|
|
4
|
-
const { importFromGitHub } = await import('../github-import.js');
|
|
5
|
-
await importFromGitHub(args[1]);
|
|
6
|
-
process.exit(0);
|
|
7
|
-
}
|
|
1
|
+
import { colors, banner, success, error, info, section, table } from '../cli.js';
|
|
2
|
+
|
|
3
|
+
export default async function handler(args, bin) {
|
|
4
|
+
const { importFromGitHub } = await import('../github-import.js');
|
|
5
|
+
await importFromGitHub(args[1]);
|
|
6
|
+
process.exit(0);
|
|
7
|
+
}
|