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.
@@ -1,110 +1,110 @@
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 boxen from 'boxen';
7
-
8
- export default async function handler(args, bin) {
9
- const { loadConfig: _lc } = await import('../config.js');
10
- const { getDb } = await import('../db.js');
11
- const { fetchText } = await import('../marketplace.js');
12
- const purple = chalk.hex('#7C3AED');
13
- const cfg = _lc();
14
- const db = getDb();
15
-
16
- const sourceCounts = new Map();
17
- for (const row of db.prepare('SELECT source, COUNT(*) as n FROM skills GROUP BY source').all()) {
18
- sourceCounts.set(row.source, row.n);
19
- }
20
- const totalSkills = db.prepare('SELECT COUNT(*) as n FROM skills').get().n;
21
- const totalBundles = cfg.sources.filter(s => s.source.startsWith('github:')).length;
22
-
23
- let marketSkills = 0, marketBundles = 0;
24
- try {
25
- const REGISTRY_URL = 'https://raw.githubusercontent.com/NeiP4n/promptgraph-registry/main/registry.json';
26
- const reg = JSON.parse(await fetchText(REGISTRY_URL));
27
- marketSkills = reg.skills?.length || 0;
28
- marketBundles = reg.bundles?.length || 0;
29
- } catch {}
30
-
31
- console.log();
32
- console.log(' ' + purple.bold('◆ PromptGraph Status'));
33
- console.log(' ' + chalk.gray('─'.repeat(56)));
34
- console.log();
35
-
36
- const skillsLine = chalk.bold.white(`${totalSkills} skills`) +
37
- (marketSkills ? chalk.gray(` / ${marketSkills} in registry`) : '');
38
- const bundlesLine = chalk.bold.white(`${totalBundles} repos`) +
39
- (marketBundles ? chalk.gray(` / ${marketBundles} in marketplace`) : '');
40
- console.log(' ' + skillsLine + chalk.gray(' · ') + bundlesLine);
41
- console.log();
42
-
43
- const githubSources = cfg.sources.filter(s => s.source.startsWith('github:'));
44
- const localSources = cfg.sources.filter(s => !s.source.startsWith('github:'));
45
-
46
- if (localSources.length) {
47
- console.log(' ' + purple('📁 Local'));
48
- for (const s of localSources) {
49
- const n = sourceCounts.get(s.source) || 0;
50
- const exists = fs.existsSync(s.dir);
51
- const label = exists ? chalk.white(s.source) : chalk.gray(s.source + ' (missing)');
52
- console.log(' ' + label + chalk.gray(` ${n} skills · ${s.dir}`));
53
- }
54
- console.log();
55
- }
56
-
57
- if (githubSources.length) {
58
- console.log(' ' + purple('🌐 GitHub repos'));
59
- for (const s of githubSources) {
60
- const n = sourceCounts.get(s.source) || 0;
61
- const repoName = s.source.replace('github:', '');
62
- const exists = fs.existsSync(s.dir);
63
- const label = exists ? chalk.white(repoName) : chalk.gray(repoName + ' (not cloned)');
64
- console.log(' ' + label + chalk.gray(` ${n} skills`));
65
- console.log(' ' + chalk.dim(s.dir));
66
- }
67
- console.log();
68
- }
69
-
70
- const marketplaceDir = path.join(os.homedir(), '.claude', 'skills-store', 'marketplace');
71
- const installedBundles = fs.existsSync(marketplaceDir)
72
- ? fs.readdirSync(marketplaceDir, { withFileTypes: true }).filter(d => d.isDirectory()).map(d => d.name)
73
- : [];
74
-
75
- if (installedBundles.length) {
76
- let registryBundles = [];
77
- try {
78
- const REGISTRY_URL = 'https://raw.githubusercontent.com/NeiP4n/promptgraph-registry/main/registry.json';
79
- const text = await fetchText(REGISTRY_URL);
80
- registryBundles = JSON.parse(text).bundles || [];
81
- } catch {}
82
-
83
- console.log(' ' + purple('📦 Installed marketplace bundles'));
84
- for (const b of installedBundles) {
85
- const bundle = registryBundles.find(rb => rb.id === b);
86
- const name = bundle ? chalk.white.bold(bundle.name || b) : chalk.white(b);
87
- const cat = bundle?.category ? chalk.dim(` [${bundle.category}]`) : '';
88
- const n = sourceCounts.get('marketplace') || 0;
89
- console.log(` ${name}${cat} ${chalk.gray(n + ' skills')}`);
90
- }
91
- console.log();
92
- }
93
-
94
- const emptyRepos = githubSources.filter(s => (sourceCounts.get(s.source) || 0) === 0 && fs.existsSync(s.dir));
95
- if (emptyRepos.length) {
96
- console.log(' ' + chalk.yellow(`⚠ ${emptyRepos.length} repo(s) not indexed`) + chalk.gray(' → run: ') + chalk.cyan(`${bin} reindex`));
97
- console.log();
98
- }
99
-
100
- console.log(
101
- boxen(
102
- chalk.dim('full reindex ') + chalk.cyan(`${bin} reindex`) + '\n' +
103
- chalk.dim('install bundle ') + chalk.cyan(`${bin} bundle install <id>`) + '\n' +
104
- chalk.dim('browse market ') + chalk.cyan(`${bin} marketplace bundles`),
105
- { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderStyle: 'round', borderColor: '#4B5563', dimBorder: true }
106
- )
107
- );
108
- console.log();
109
- process.exit(0);
110
- }
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 boxen from 'boxen';
7
+
8
+ export default async function handler(args, bin) {
9
+ const { loadConfig: _lc } = await import('../config.js');
10
+ const { getDb } = await import('../db.js');
11
+ const { fetchText } = await import('../marketplace.js');
12
+ const purple = chalk.hex('#7C3AED');
13
+ const cfg = _lc();
14
+ const db = getDb();
15
+
16
+ const sourceCounts = new Map();
17
+ for (const row of db.prepare('SELECT source, COUNT(*) as n FROM skills GROUP BY source').all()) {
18
+ sourceCounts.set(row.source, row.n);
19
+ }
20
+ const totalSkills = db.prepare('SELECT COUNT(*) as n FROM skills').get().n;
21
+ const totalBundles = cfg.sources.filter(s => s.source.startsWith('github:')).length;
22
+
23
+ let marketSkills = 0, marketBundles = 0;
24
+ try {
25
+ const REGISTRY_URL = 'https://raw.githubusercontent.com/NeiP4n/promptgraph-registry/main/registry.json';
26
+ const reg = JSON.parse(await fetchText(REGISTRY_URL));
27
+ marketSkills = reg.skills?.length || 0;
28
+ marketBundles = reg.bundles?.length || 0;
29
+ } catch {}
30
+
31
+ console.log();
32
+ console.log(' ' + purple.bold('◆ PromptGraph Status'));
33
+ console.log(' ' + chalk.gray('─'.repeat(56)));
34
+ console.log();
35
+
36
+ const skillsLine = chalk.bold.white(`${totalSkills} skills`) +
37
+ (marketSkills ? chalk.gray(` / ${marketSkills} in registry`) : '');
38
+ const bundlesLine = chalk.bold.white(`${totalBundles} repos`) +
39
+ (marketBundles ? chalk.gray(` / ${marketBundles} in marketplace`) : '');
40
+ console.log(' ' + skillsLine + chalk.gray(' · ') + bundlesLine);
41
+ console.log();
42
+
43
+ const githubSources = cfg.sources.filter(s => s.source.startsWith('github:'));
44
+ const localSources = cfg.sources.filter(s => !s.source.startsWith('github:'));
45
+
46
+ if (localSources.length) {
47
+ console.log(' ' + purple('📁 Local'));
48
+ for (const s of localSources) {
49
+ const n = sourceCounts.get(s.source) || 0;
50
+ const exists = fs.existsSync(s.dir);
51
+ const label = exists ? chalk.white(s.source) : chalk.gray(s.source + ' (missing)');
52
+ console.log(' ' + label + chalk.gray(` ${n} skills · ${s.dir}`));
53
+ }
54
+ console.log();
55
+ }
56
+
57
+ if (githubSources.length) {
58
+ console.log(' ' + purple('🌐 GitHub repos'));
59
+ for (const s of githubSources) {
60
+ const n = sourceCounts.get(s.source) || 0;
61
+ const repoName = s.source.replace('github:', '');
62
+ const exists = fs.existsSync(s.dir);
63
+ const label = exists ? chalk.white(repoName) : chalk.gray(repoName + ' (not cloned)');
64
+ console.log(' ' + label + chalk.gray(` ${n} skills`));
65
+ console.log(' ' + chalk.dim(s.dir));
66
+ }
67
+ console.log();
68
+ }
69
+
70
+ const marketplaceDir = path.join(os.homedir(), '.claude', 'skills-store', 'marketplace');
71
+ const installedBundles = fs.existsSync(marketplaceDir)
72
+ ? fs.readdirSync(marketplaceDir, { withFileTypes: true }).filter(d => d.isDirectory()).map(d => d.name)
73
+ : [];
74
+
75
+ if (installedBundles.length) {
76
+ let registryBundles = [];
77
+ try {
78
+ const REGISTRY_URL = 'https://raw.githubusercontent.com/NeiP4n/promptgraph-registry/main/registry.json';
79
+ const text = await fetchText(REGISTRY_URL);
80
+ registryBundles = JSON.parse(text).bundles || [];
81
+ } catch {}
82
+
83
+ console.log(' ' + purple('📦 Installed marketplace bundles'));
84
+ for (const b of installedBundles) {
85
+ const bundle = registryBundles.find(rb => rb.id === b);
86
+ const name = bundle ? chalk.white.bold(bundle.name || b) : chalk.white(b);
87
+ const cat = bundle?.category ? chalk.dim(` [${bundle.category}]`) : '';
88
+ const n = sourceCounts.get('marketplace') || 0;
89
+ console.log(` ${name}${cat} ${chalk.gray(n + ' skills')}`);
90
+ }
91
+ console.log();
92
+ }
93
+
94
+ const emptyRepos = githubSources.filter(s => (sourceCounts.get(s.source) || 0) === 0 && fs.existsSync(s.dir));
95
+ if (emptyRepos.length) {
96
+ console.log(' ' + chalk.yellow(`⚠ ${emptyRepos.length} repo(s) not indexed`) + chalk.gray(' → run: ') + chalk.cyan(`${bin} reindex`));
97
+ console.log();
98
+ }
99
+
100
+ console.log(
101
+ boxen(
102
+ chalk.dim('full reindex ') + chalk.cyan(`${bin} reindex`) + '\n' +
103
+ chalk.dim('install bundle ') + chalk.cyan(`${bin} bundle install <id>`) + '\n' +
104
+ chalk.dim('browse market ') + chalk.cyan(`${bin} marketplace bundles`),
105
+ { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderStyle: 'round', borderColor: '#4B5563', dimBorder: true }
106
+ )
107
+ );
108
+ console.log();
109
+ process.exit(0);
110
+ }
package/commands/train.js CHANGED
@@ -1,18 +1,18 @@
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 { train: trainModel } = await import('../src/filter/train.js');
6
- const spin = (await import('../cli.js')).spinner('Training classifier...');
7
- spin.start();
8
- try {
9
- const model = await trainModel();
10
- spin.stop();
11
- success(`Classifier trained (${model.counts.good} good, ${model.counts.bad} bad examples)`);
12
- } catch (e) {
13
- spin.stop();
14
- error(`Training failed: ${e.message}`);
15
- process.exit(1);
16
- }
17
- process.exit(0);
18
- }
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 { train: trainModel } = await import('../src/filter/train.js');
6
+ const spin = (await import('../cli.js')).spinner('Training classifier...');
7
+ spin.start();
8
+ try {
9
+ const model = await trainModel();
10
+ spin.stop();
11
+ success(`Classifier trained (${model.counts.good} good, ${model.counts.bad} bad examples)`);
12
+ } catch (e) {
13
+ spin.stop();
14
+ error(`Training failed: ${e.message}`);
15
+ process.exit(1);
16
+ }
17
+ process.exit(0);
18
+ }
@@ -1,49 +1,49 @@
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 { spawnSync } = await import('child_process');
6
- const { createRequire } = await import('module');
7
- const https = (await import('https')).default;
8
- const req = createRequire(import.meta.url);
9
- const currentVersion = req('../package.json').version;
10
-
11
- const spin = (await import('../cli.js')).spinner('Checking latest version...');
12
- spin.start();
13
- let latest = null;
14
- try {
15
- latest = await new Promise((res, rej) => {
16
- const r = https.get('https://registry.npmjs.org/promptgraph-mcp/latest',
17
- { headers: { Accept: 'application/json' }, timeout: 8000, family: 4 },
18
- (resp) => {
19
- let d = ''; resp.setEncoding('utf8');
20
- resp.on('data', c => d += c);
21
- resp.on('end', () => { try { res(JSON.parse(d).version); } catch { rej(new Error('bad response')); } });
22
- }
23
- );
24
- r.on('error', rej);
25
- r.on('timeout', () => { r.destroy(new Error('timeout')); });
26
- });
27
- } catch {}
28
- spin.stop();
29
-
30
- if (!latest) { error('Could not reach npm registry. Check your network.'); process.exit(1); }
31
- if (latest === currentVersion) {
32
- success(`Already on latest version ${chalk.white.bold('v' + currentVersion)}`);
33
- process.exit(0);
34
- }
35
-
36
- info(`Current: ${chalk.gray('v' + currentVersion)} → Latest: ${chalk.white.bold('v' + latest)}`);
37
- const updateSpin = (await import('../cli.js')).spinner(`Installing promptgraph-mcp@latest (v${latest})...`);
38
- updateSpin.start();
39
- const result = spawnSync('npm', ['install', '-g', 'promptgraph-mcp@latest'], { encoding: 'utf8', stdio: 'pipe', shell: true });
40
- updateSpin.stop();
41
-
42
- if (result.status !== 0) {
43
- error('Update failed:');
44
- console.log(chalk.gray(result.stderr || result.stdout));
45
- process.exit(1);
46
- }
47
- success(`Updated to ${chalk.white.bold('v' + latest)}`);
48
- process.exit(0);
49
- }
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 { spawnSync } = await import('child_process');
6
+ const { createRequire } = await import('module');
7
+ const https = (await import('https')).default;
8
+ const req = createRequire(import.meta.url);
9
+ const currentVersion = req('../package.json').version;
10
+
11
+ const spin = (await import('../cli.js')).spinner('Checking latest version...');
12
+ spin.start();
13
+ let latest = null;
14
+ try {
15
+ latest = await new Promise((res, rej) => {
16
+ const r = https.get('https://registry.npmjs.org/promptgraph-mcp/latest',
17
+ { headers: { Accept: 'application/json' }, timeout: 8000, family: 4 },
18
+ (resp) => {
19
+ let d = ''; resp.setEncoding('utf8');
20
+ resp.on('data', c => d += c);
21
+ resp.on('end', () => { try { res(JSON.parse(d).version); } catch { rej(new Error('bad response')); } });
22
+ }
23
+ );
24
+ r.on('error', rej);
25
+ r.on('timeout', () => { r.destroy(new Error('timeout')); });
26
+ });
27
+ } catch {}
28
+ spin.stop();
29
+
30
+ if (!latest) { error('Could not reach npm registry. Check your network.'); process.exit(1); }
31
+ if (latest === currentVersion) {
32
+ success(`Already on latest version ${chalk.white.bold('v' + currentVersion)}`);
33
+ process.exit(0);
34
+ }
35
+
36
+ info(`Current: ${chalk.gray('v' + currentVersion)} → Latest: ${chalk.white.bold('v' + latest)}`);
37
+ const updateSpin = (await import('../cli.js')).spinner(`Installing promptgraph-mcp@latest (v${latest})...`);
38
+ updateSpin.start();
39
+ const result = spawnSync('npm', ['install', '-g', 'promptgraph-mcp@latest'], { encoding: 'utf8', stdio: 'pipe', shell: true });
40
+ updateSpin.stop();
41
+
42
+ if (result.status !== 0) {
43
+ error('Update failed:');
44
+ console.log(chalk.gray(result.stderr || result.stdout));
45
+ process.exit(1);
46
+ }
47
+ success(`Updated to ${chalk.white.bold('v' + latest)}`);
48
+ process.exit(0);
49
+ }
@@ -1,63 +1,63 @@
1
- import { colors, banner, success, error, info, section, table } from '../cli.js';
2
- import chalk from 'chalk';
3
- import fs from 'fs';
4
-
5
- export default async function handler(args, bin) {
6
- const { validateSkill } = await import('../validator.js');
7
- const file = args[1];
8
- if (!file) { error('Usage: ' + bin + ' validate <skill.md>'); process.exit(1); }
9
-
10
- const raw = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : null;
11
-
12
- if (raw) {
13
- const { filterWithClassifier, isSkillFile: _isSkill } = await import('../parser.js');
14
- const { hardFilter } = await import('../src/filter/hard-filter.js');
15
- const { loadModel } = await import('../src/filter/train.js');
16
- const { embed } = await import('../embedder.js');
17
- const { classify } = await import('../src/filter/classifier.js');
18
-
19
- const hfResult = hardFilter(file, raw);
20
- const willIndex = _isSkill(file, raw);
21
- const scoreLabel = willIndex ? chalk.green('✓ will be indexed') : chalk.red('✗ will be skipped by indexer');
22
- console.log(chalk.bold('\n Indexing check: ') + scoreLabel);
23
-
24
- const signals = [];
25
- if (!hfResult.pass) {
26
- signals.push(chalk.red(`✗ hard filter: ${hfResult.reason}`));
27
- } else {
28
- signals.push(chalk.green('✓ hard filter passed'));
29
- }
30
-
31
- const centroids = loadModel();
32
- if (centroids) {
33
- try {
34
- const vec = await embed(raw);
35
- const decision = classify(vec, centroids, raw, file);
36
- const pct = (decision.score * 100).toFixed(0);
37
- if (decision.label === 'skill') signals.push(chalk.green(`✓ classifier: skill (${pct}%)`));
38
- else if (decision.label === 'unsure') signals.push(chalk.yellow(`? classifier: unsure (${pct}%)`));
39
- else signals.push(chalk.red(`✗ classifier: reject (${pct}%)`));
40
- } catch {
41
- signals.push(chalk.gray(' classifier: embed failed (skip)'));
42
- }
43
- } else {
44
- signals.push(chalk.gray(' classifier: no model (run `pg train`)'));
45
- }
46
-
47
- if (signals.length) {
48
- signals.forEach(s => console.log(' ' + s));
49
- }
50
- console.log();
51
- }
52
-
53
- const result = validateSkill(file);
54
- result.warnings.forEach(w => console.log(chalk.yellow('⚠') + ' ' + chalk.gray(w)));
55
- if (result.ok) {
56
- success('Skill is valid — ready to publish');
57
- process.exit(0);
58
- } else {
59
- error('Validation failed:');
60
- result.errors.forEach(e => console.log(' ' + chalk.red('•') + ' ' + e));
61
- process.exit(1);
62
- }
63
- }
1
+ import { colors, banner, success, error, info, section, table } from '../cli.js';
2
+ import chalk from 'chalk';
3
+ import fs from 'fs';
4
+
5
+ export default async function handler(args, bin) {
6
+ const { validateSkill } = await import('../validator.js');
7
+ const file = args[1];
8
+ if (!file) { error('Usage: ' + bin + ' validate <skill.md>'); process.exit(1); }
9
+
10
+ const raw = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : null;
11
+
12
+ if (raw) {
13
+ const { filterWithClassifier, isSkillFile: _isSkill } = await import('../parser.js');
14
+ const { hardFilter } = await import('../src/filter/hard-filter.js');
15
+ const { loadModel } = await import('../src/filter/train.js');
16
+ const { embed } = await import('../embedder.js');
17
+ const { classify } = await import('../src/filter/classifier.js');
18
+
19
+ const hfResult = hardFilter(file, raw);
20
+ const willIndex = _isSkill(file, raw);
21
+ const scoreLabel = willIndex ? chalk.green('✓ will be indexed') : chalk.red('✗ will be skipped by indexer');
22
+ console.log(chalk.bold('\n Indexing check: ') + scoreLabel);
23
+
24
+ const signals = [];
25
+ if (!hfResult.pass) {
26
+ signals.push(chalk.red(`✗ hard filter: ${hfResult.reason}`));
27
+ } else {
28
+ signals.push(chalk.green('✓ hard filter passed'));
29
+ }
30
+
31
+ const centroids = loadModel();
32
+ if (centroids) {
33
+ try {
34
+ const vec = await embed(raw);
35
+ const decision = classify(vec, centroids, raw, file);
36
+ const pct = (decision.score * 100).toFixed(0);
37
+ if (decision.label === 'skill') signals.push(chalk.green(`✓ classifier: skill (${pct}%)`));
38
+ else if (decision.label === 'unsure') signals.push(chalk.yellow(`? classifier: unsure (${pct}%)`));
39
+ else signals.push(chalk.red(`✗ classifier: reject (${pct}%)`));
40
+ } catch {
41
+ signals.push(chalk.gray(' classifier: embed failed (skip)'));
42
+ }
43
+ } else {
44
+ signals.push(chalk.gray(' classifier: no model (run `pg train`)'));
45
+ }
46
+
47
+ if (signals.length) {
48
+ signals.forEach(s => console.log(' ' + s));
49
+ }
50
+ console.log();
51
+ }
52
+
53
+ const result = validateSkill(file);
54
+ result.warnings.forEach(w => console.log(chalk.yellow('⚠') + ' ' + chalk.gray(w)));
55
+ if (result.ok) {
56
+ success('Skill is valid — ready to publish');
57
+ process.exit(0);
58
+ } else {
59
+ error('Validation failed:');
60
+ result.errors.forEach(e => console.log(' ' + chalk.red('•') + ' ' + e));
61
+ process.exit(1);
62
+ }
63
+ }
package/config.js CHANGED
@@ -1,72 +1,72 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import os from 'os';
4
- import readline from 'readline';
5
-
6
- const CLAUDE_DIR = path.join(os.homedir(), '.claude');
7
- export const PROMPTGRAPH_DIR = path.join(CLAUDE_DIR, '.promptgraph');
8
- export const SKILLS_STORE_DIR = path.join(CLAUDE_DIR, 'skills-store');
9
- const CONFIG_PATH = path.join(PROMPTGRAPH_DIR, 'config.json');
10
-
11
- export const MAX_DOWNLOAD_SIZE = 50 * 1024 * 1024 // 50 MB per file
12
- export const MAX_FILE_COUNT = 50000 // max files per repo
13
- export const MAX_REPO_SIZE = 500 * 1024 * 1024 // 500 MB per repo
14
- export const RATE_LIMIT_REQUESTS = 30 // requests per window
15
- export const RATE_LIMIT_WINDOW_MS = 60000 // 1 minute window
16
- export const BATCH_SIZE = 100 // batch indexing size
17
-
18
- const DEFAULTS = {
19
- sources: [
20
- { dir: path.join(CLAUDE_DIR, 'skills-store'), source: 'skills-store' },
21
- { dir: path.join(CLAUDE_DIR, 'skills'), source: 'skills' },
22
- { dir: path.join(CLAUDE_DIR, 'commands'), source: 'commands' },
23
- ],
24
- };
25
-
26
-
27
- export function loadConfig() {
28
- if (fs.existsSync(CONFIG_PATH)) {
29
- return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
30
- }
31
- return JSON.parse(JSON.stringify(DEFAULTS));
32
- }
33
-
34
- export function saveConfig(config) {
35
- fs.mkdirSync(path.dirname(CONFIG_PATH), { recursive: true });
36
- fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
37
- }
38
-
39
- export async function promptConfig() {
40
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
41
- const ask = (q) => new Promise(r => rl.question(q, r));
42
-
43
- console.log('\n=== PromptGraph Setup ===\n');
44
- console.log('Default skill directories:');
45
- DEFAULTS.sources.forEach((s, i) => console.log(` ${i + 1}. ${s.dir}`));
46
-
47
- const extra = await ask('\nAdd extra skill directories? (comma-separated paths, or press Enter to skip): ');
48
- rl.close();
49
-
50
- const config = structuredClone(DEFAULTS);
51
-
52
- if (extra.trim()) {
53
- const extraDirs = extra.split(',').map(d => d.trim()).filter(Boolean);
54
- for (const dir of extraDirs) {
55
- const base = path.basename(path.resolve(dir));
56
- const existing = config.sources.filter(s => s.source === `custom:${base}`);
57
- const tag = existing.length === 0 ? `custom:${base}` : `custom:${base}-${existing.length}`;
58
- config.sources.push({ dir, source: tag });
59
- }
60
- }
61
-
62
- saveConfig(config);
63
- console.log(`\nConfig saved to ${CONFIG_PATH}`);
64
- return config;
65
- }
66
-
67
- export function sanitizePath(inputPath) {
68
- if (inputPath.includes('..')) {
69
- throw new Error(`Path traversal blocked: "${inputPath}"`);
70
- }
71
- return path.resolve(inputPath);
72
- }
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import readline from 'readline';
5
+
6
+ const CLAUDE_DIR = path.join(os.homedir(), '.claude');
7
+ export const PROMPTGRAPH_DIR = path.join(CLAUDE_DIR, '.promptgraph');
8
+ export const SKILLS_STORE_DIR = path.join(CLAUDE_DIR, 'skills-store');
9
+ const CONFIG_PATH = path.join(PROMPTGRAPH_DIR, 'config.json');
10
+
11
+ export const MAX_DOWNLOAD_SIZE = 50 * 1024 * 1024 // 50 MB per file
12
+ export const MAX_FILE_COUNT = 50000 // max files per repo
13
+ export const MAX_REPO_SIZE = 500 * 1024 * 1024 // 500 MB per repo
14
+ export const RATE_LIMIT_REQUESTS = 30 // requests per window
15
+ export const RATE_LIMIT_WINDOW_MS = 60000 // 1 minute window
16
+ export const BATCH_SIZE = 100 // batch indexing size
17
+
18
+ const DEFAULTS = {
19
+ sources: [
20
+ { dir: path.join(CLAUDE_DIR, 'skills-store'), source: 'skills-store' },
21
+ { dir: path.join(CLAUDE_DIR, 'skills'), source: 'skills' },
22
+ { dir: path.join(CLAUDE_DIR, 'commands'), source: 'commands' },
23
+ ],
24
+ };
25
+
26
+
27
+ export function loadConfig() {
28
+ if (fs.existsSync(CONFIG_PATH)) {
29
+ return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
30
+ }
31
+ return JSON.parse(JSON.stringify(DEFAULTS));
32
+ }
33
+
34
+ export function saveConfig(config) {
35
+ fs.mkdirSync(path.dirname(CONFIG_PATH), { recursive: true });
36
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
37
+ }
38
+
39
+ export async function promptConfig() {
40
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
41
+ const ask = (q) => new Promise(r => rl.question(q, r));
42
+
43
+ console.log('\n=== PromptGraph Setup ===\n');
44
+ console.log('Default skill directories:');
45
+ DEFAULTS.sources.forEach((s, i) => console.log(` ${i + 1}. ${s.dir}`));
46
+
47
+ const extra = await ask('\nAdd extra skill directories? (comma-separated paths, or press Enter to skip): ');
48
+ rl.close();
49
+
50
+ const config = structuredClone(DEFAULTS);
51
+
52
+ if (extra.trim()) {
53
+ const extraDirs = extra.split(',').map(d => d.trim()).filter(Boolean);
54
+ for (const dir of extraDirs) {
55
+ const base = path.basename(path.resolve(dir));
56
+ const existing = config.sources.filter(s => s.source === `custom:${base}`);
57
+ const tag = existing.length === 0 ? `custom:${base}` : `custom:${base}-${existing.length}`;
58
+ config.sources.push({ dir, source: tag });
59
+ }
60
+ }
61
+
62
+ saveConfig(config);
63
+ console.log(`\nConfig saved to ${CONFIG_PATH}`);
64
+ return config;
65
+ }
66
+
67
+ export function sanitizePath(inputPath) {
68
+ if (inputPath.includes('..')) {
69
+ throw new Error(`Path traversal blocked: "${inputPath}"`);
70
+ }
71
+ return path.resolve(inputPath);
72
+ }