promptgraph-mcp 2.9.55 → 2.9.57
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/api.js +2 -2
- package/commands/add-dir.js +1 -1
- package/commands/marketplace.js +157 -157
- package/config.js +8 -1
- package/github-import.js +934 -923
- package/indexer.js +2 -2
- package/marketplace.js +889 -889
- package/package.json +1 -1
- package/src/filter/hard-filter.js +13 -3
- package/src/filter/train.js +1 -1
package/api.js
CHANGED
|
@@ -44,7 +44,7 @@ export async function index(sourceDir, sourceName) {
|
|
|
44
44
|
const { skillId, vecToBlob } = await import('./db.js');
|
|
45
45
|
const { chunkText } = await import('./chunker.js');
|
|
46
46
|
|
|
47
|
-
const files = globSync(`${sourceDir}/**/*.md
|
|
47
|
+
const files = globSync(`${sourceDir}/**/*.md`, { dot: true });
|
|
48
48
|
let indexed = 0, skipped = 0, errors = 0;
|
|
49
49
|
const batch = [];
|
|
50
50
|
|
|
@@ -118,7 +118,7 @@ export async function update() {
|
|
|
118
118
|
const seenFiles = new Set();
|
|
119
119
|
const allFiles = [];
|
|
120
120
|
for (const { dir, source } of normalizedSources) {
|
|
121
|
-
const files = globSync(`${dir}/**/*.md
|
|
121
|
+
const files = globSync(`${dir}/**/*.md`, { dot: true });
|
|
122
122
|
for (const f of files) {
|
|
123
123
|
const norm = path.resolve(f);
|
|
124
124
|
if (!seenFiles.has(norm)) { seenFiles.add(norm); allFiles.push({ file: norm, source }); }
|
package/commands/add-dir.js
CHANGED
|
@@ -21,7 +21,7 @@ export default async function handler(args, bin) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
const { globSync } = await import('glob');
|
|
24
|
-
const mdCount = globSync(`${abs}/**/*.md`, { absolute: true }).length;
|
|
24
|
+
const mdCount = globSync(`${abs}/**/*.md`, { absolute: true, dot: true }).length;
|
|
25
25
|
if (mdCount === 0) {
|
|
26
26
|
error(`No .md files found under ${abs}`);
|
|
27
27
|
process.exit(1);
|
package/commands/marketplace.js
CHANGED
|
@@ -1,157 +1,157 @@
|
|
|
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
|
-
|
|
7
|
-
export default async function handler(args, bin) {
|
|
8
|
-
// Subcommand: validate / prune all installed marketplace files
|
|
9
|
-
if (args[0] === 'validate' || args[0] === 'prune' || args[0] === '--validate' || args[0] === '--prune') {
|
|
10
|
-
const { validateAndPruneMarketplace } = await import('../marketplace.js');
|
|
11
|
-
const result = validateAndPruneMarketplace();
|
|
12
|
-
if (result.removed.length > 0) {
|
|
13
|
-
error(`Removed ${result.removed.length} invalid files:`);
|
|
14
|
-
result.removed.forEach(r => console.log(` ${chalk.red('✗')} ${r.file}`));
|
|
15
|
-
}
|
|
16
|
-
if (result.errors.length > 0) {
|
|
17
|
-
error(`${result.errors.length} errors:`);
|
|
18
|
-
result.errors.forEach(e => console.log(` ${chalk.yellow('⚠')} ${e}`));
|
|
19
|
-
}
|
|
20
|
-
success(`${result.valid.length} valid files, ${result.removed.length} removed, ${result.errors.length} errors`);
|
|
21
|
-
process.exit(result.errors.length > 0 ? 1 : 0);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (!process.stdout.isTTY) {
|
|
25
|
-
error('marketplace TUI requires an interactive terminal');
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
|
-
const { browseMarketplace, browseBundles, installSkill, installBundle, installBundleBg, validateAndPruneMarketplace } = await import('../marketplace.js');
|
|
29
|
-
const { loadConfig: _lcMkt } = await import('../config.js');
|
|
30
|
-
const { getDb: _getDbMkt } = await import('../db.js');
|
|
31
|
-
const { spinner: spin2 } = await import('../cli.js');
|
|
32
|
-
const sp = spin2('Fetching marketplace...');
|
|
33
|
-
sp.start();
|
|
34
|
-
try {
|
|
35
|
-
var [skills, bundles] = await Promise.all([browseMarketplace(1000), browseBundles(1000)]);
|
|
36
|
-
} finally {
|
|
37
|
-
sp.stop();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (skills?.error) { error(skills.error); process.exit(1); }
|
|
41
|
-
|
|
42
|
-
const { getCachedCount, setCachedCount, refreshCountsInBackground } = await import('../bundle-counts.js');
|
|
43
|
-
const { SKILLS_STORE_DIR } = await import('../config.js');
|
|
44
|
-
const { globSync } = await import('glob');
|
|
45
|
-
const { SCRIPT_GLOBS } = await import('../github-import.js');
|
|
46
|
-
const githubDir = path.join(SKILLS_STORE_DIR, 'github');
|
|
47
|
-
|
|
48
|
-
// For each bundle: if installed on disk — use real file count + detect scripts; otherwise use cache
|
|
49
|
-
const bundlesWithCounts = (Array.isArray(bundles) ? bundles : []).map(b => {
|
|
50
|
-
if (!b.repo_url) return b;
|
|
51
|
-
const owner = b.repo_url.split('/')[0];
|
|
52
|
-
const repo = b.repo_url.split('/')[1];
|
|
53
|
-
const clonedDir = path.join(githubDir, `${owner}-${repo}`);
|
|
54
|
-
if (fs.existsSync(clonedDir) && fs.readdirSync(clonedDir).length > 0) {
|
|
55
|
-
const realCount = globSync(`${clonedDir}/**/*.md`, { absolute: true }).length;
|
|
56
|
-
const hasScripts = globSync(SCRIPT_GLOBS.map(p => `${clonedDir}/${p}`), { absolute: true }).length > 0;
|
|
57
|
-
setCachedCount(b.repo_url, realCount);
|
|
58
|
-
return { ...b, skillCount: realCount, has_tools: b.has_tools || hasScripts };
|
|
59
|
-
}
|
|
60
|
-
const cached = getCachedCount(b.repo_url);
|
|
61
|
-
const knownCount = cached ?? b.skill_count ?? null;
|
|
62
|
-
return knownCount !== null ? { ...b, skillCount: knownCount, has_tools: b.has_tools } : b;
|
|
63
|
-
});
|
|
64
|
-
refreshCountsInBackground(bundlesWithCounts);
|
|
65
|
-
|
|
66
|
-
const installedSet = new Set();
|
|
67
|
-
try {
|
|
68
|
-
const cfg = _lcMkt();
|
|
69
|
-
const db = _getDbMkt();
|
|
70
|
-
|
|
71
|
-
for (const b of (Array.isArray(bundles) ? bundles : [])) {
|
|
72
|
-
if (b.repo_url) {
|
|
73
|
-
const owner = b.repo_url.split('/')[0];
|
|
74
|
-
const repo = b.repo_url.split('/')[1];
|
|
75
|
-
const clonedDir = path.join(githubDir, `${owner}-${repo}`);
|
|
76
|
-
if (fs.existsSync(clonedDir) && fs.readdirSync(clonedDir).length > 0) installedSet.add(b.id);
|
|
77
|
-
} else if (Array.isArray(b.skills)) {
|
|
78
|
-
const allOnDisk = b.skills.every(sid => {
|
|
79
|
-
const row = db.prepare('SELECT path FROM skills WHERE id = ?').get(sid);
|
|
80
|
-
return row && fs.existsSync(row.path);
|
|
81
|
-
});
|
|
82
|
-
if (b.skills.length > 0 && allOnDisk) installedSet.add(b.id);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
for (const row of db.prepare('SELECT id, path FROM skills WHERE source = ?').all('marketplace')) {
|
|
87
|
-
if (fs.existsSync(row.path)) installedSet.add(row.id);
|
|
88
|
-
}
|
|
89
|
-
} catch {}
|
|
90
|
-
|
|
91
|
-
const { runTUI } = await import('../tui.js');
|
|
92
|
-
const { loadConfig: _lcR, saveConfig: _scR, SKILLS_STORE_DIR: _ssR } = await import('../config.js');
|
|
93
|
-
const { getDb: _getDbR } = await import('../db.js');
|
|
94
|
-
|
|
95
|
-
await runTUI(
|
|
96
|
-
Array.isArray(skills) ? skills : [],
|
|
97
|
-
bundlesWithCounts,
|
|
98
|
-
async (item, onStatus) => {
|
|
99
|
-
if (item.type === 'bundle') {
|
|
100
|
-
const r = await installBundleBg(item.id, async (err, result) => {
|
|
101
|
-
if (err) { onStatus(false, err.message?.slice(0, 60) || 'Install failed'); return; }
|
|
102
|
-
installedSet.add(item.id);
|
|
103
|
-
const { getCachedCount } = await import('../bundle-counts.js');
|
|
104
|
-
const cached = getCachedCount(item.repo_url);
|
|
105
|
-
if (cached !== null) item.skillCount = cached;
|
|
106
|
-
validateAndPruneMarketplace();
|
|
107
|
-
onStatus(true, `Installed ${item.name}`);
|
|
108
|
-
});
|
|
109
|
-
if (r?.error) {
|
|
110
|
-
if (!r.dedup) onStatus(false, r.error.slice(0, 60));
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
onStatus(null, `Queued ${item.name}…`);
|
|
114
|
-
} else {
|
|
115
|
-
const r = await installSkill(item.code || item.id);
|
|
116
|
-
if (r?.error) { onStatus(false, r.error.slice(0, 60)); return; }
|
|
117
|
-
installedSet.add(item.id);
|
|
118
|
-
if (item.code) installedSet.add(item.code);
|
|
119
|
-
validateAndPruneMarketplace();
|
|
120
|
-
onStatus(true, `Installed ${item.name}`);
|
|
121
|
-
}
|
|
122
|
-
},
|
|
123
|
-
installedSet,
|
|
124
|
-
async (item) => {
|
|
125
|
-
const cfg = _lcR();
|
|
126
|
-
const db = _getDbR();
|
|
127
|
-
if (item.type === 'bundle' && item.repo_url) {
|
|
128
|
-
const owner = item.repo_url.split('/')[0];
|
|
129
|
-
const repo = item.repo_url.split('/')[1];
|
|
130
|
-
const clonedName = `${owner}-${repo}`;
|
|
131
|
-
const clonedDir = path.join(_ssR, 'github', clonedName);
|
|
132
|
-
if (fs.existsSync(clonedDir)) fs.rmSync(clonedDir, { recursive: true, force: true });
|
|
133
|
-
const src = `github:${clonedName}`;
|
|
134
|
-
cfg.sources = cfg.sources.filter(s => s.source !== src && !s.dir.startsWith(clonedDir));
|
|
135
|
-
_scR(cfg);
|
|
136
|
-
db.prepare('DELETE FROM skills WHERE source = ?').run(src);
|
|
137
|
-
db.prepare('DELETE FROM chunks WHERE skill_id NOT IN (SELECT id FROM skills)').run();
|
|
138
|
-
} else if (item.type === 'bundle') {
|
|
139
|
-
const mktDir = path.join(_ssR, 'marketplace');
|
|
140
|
-
for (const sid of (item.skills || [])) {
|
|
141
|
-
const row = db.prepare('SELECT path FROM skills WHERE id = ?').get(sid);
|
|
142
|
-
if (row?.path && fs.existsSync(row.path)) fs.unlinkSync(row.path);
|
|
143
|
-
db.prepare('DELETE FROM skills WHERE id = ?').run(sid);
|
|
144
|
-
db.prepare('DELETE FROM chunks WHERE skill_id = ?').run(sid);
|
|
145
|
-
}
|
|
146
|
-
} else {
|
|
147
|
-
const row = db.prepare('SELECT path FROM skills WHERE id = ?').get(item.id);
|
|
148
|
-
if (row?.path && fs.existsSync(row.path)) fs.unlinkSync(row.path);
|
|
149
|
-
db.prepare('DELETE FROM skills WHERE id = ?').run(item.id);
|
|
150
|
-
db.prepare('DELETE FROM chunks WHERE skill_id = ?').run(item.id);
|
|
151
|
-
}
|
|
152
|
-
installedSet.delete(item.id);
|
|
153
|
-
if (item.code) installedSet.delete(item.code);
|
|
154
|
-
}
|
|
155
|
-
);
|
|
156
|
-
process.exit(0);
|
|
157
|
-
}
|
|
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
|
+
|
|
7
|
+
export default async function handler(args, bin) {
|
|
8
|
+
// Subcommand: validate / prune all installed marketplace files
|
|
9
|
+
if (args[0] === 'validate' || args[0] === 'prune' || args[0] === '--validate' || args[0] === '--prune') {
|
|
10
|
+
const { validateAndPruneMarketplace } = await import('../marketplace.js');
|
|
11
|
+
const result = validateAndPruneMarketplace();
|
|
12
|
+
if (result.removed.length > 0) {
|
|
13
|
+
error(`Removed ${result.removed.length} invalid files:`);
|
|
14
|
+
result.removed.forEach(r => console.log(` ${chalk.red('✗')} ${r.file}`));
|
|
15
|
+
}
|
|
16
|
+
if (result.errors.length > 0) {
|
|
17
|
+
error(`${result.errors.length} errors:`);
|
|
18
|
+
result.errors.forEach(e => console.log(` ${chalk.yellow('⚠')} ${e}`));
|
|
19
|
+
}
|
|
20
|
+
success(`${result.valid.length} valid files, ${result.removed.length} removed, ${result.errors.length} errors`);
|
|
21
|
+
process.exit(result.errors.length > 0 ? 1 : 0);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!process.stdout.isTTY) {
|
|
25
|
+
error('marketplace TUI requires an interactive terminal');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
const { browseMarketplace, browseBundles, installSkill, installBundle, installBundleBg, validateAndPruneMarketplace } = await import('../marketplace.js');
|
|
29
|
+
const { loadConfig: _lcMkt } = await import('../config.js');
|
|
30
|
+
const { getDb: _getDbMkt } = await import('../db.js');
|
|
31
|
+
const { spinner: spin2 } = await import('../cli.js');
|
|
32
|
+
const sp = spin2('Fetching marketplace...');
|
|
33
|
+
sp.start();
|
|
34
|
+
try {
|
|
35
|
+
var [skills, bundles] = await Promise.all([browseMarketplace(1000), browseBundles(1000)]);
|
|
36
|
+
} finally {
|
|
37
|
+
sp.stop();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (skills?.error) { error(skills.error); process.exit(1); }
|
|
41
|
+
|
|
42
|
+
const { getCachedCount, setCachedCount, refreshCountsInBackground } = await import('../bundle-counts.js');
|
|
43
|
+
const { SKILLS_STORE_DIR } = await import('../config.js');
|
|
44
|
+
const { globSync } = await import('glob');
|
|
45
|
+
const { SCRIPT_GLOBS } = await import('../github-import.js');
|
|
46
|
+
const githubDir = path.join(SKILLS_STORE_DIR, 'github');
|
|
47
|
+
|
|
48
|
+
// For each bundle: if installed on disk — use real file count + detect scripts; otherwise use cache
|
|
49
|
+
const bundlesWithCounts = (Array.isArray(bundles) ? bundles : []).map(b => {
|
|
50
|
+
if (!b.repo_url) return b;
|
|
51
|
+
const owner = b.repo_url.split('/')[0];
|
|
52
|
+
const repo = b.repo_url.split('/')[1];
|
|
53
|
+
const clonedDir = path.join(githubDir, `${owner}-${repo}`);
|
|
54
|
+
if (fs.existsSync(clonedDir) && fs.readdirSync(clonedDir).length > 0) {
|
|
55
|
+
const realCount = globSync(`${clonedDir}/**/*.md`, { absolute: true, dot: true }).length;
|
|
56
|
+
const hasScripts = globSync(SCRIPT_GLOBS.map(p => `${clonedDir}/${p}`), { absolute: true, dot: true }).length > 0;
|
|
57
|
+
setCachedCount(b.repo_url, realCount);
|
|
58
|
+
return { ...b, skillCount: realCount, has_tools: b.has_tools || hasScripts };
|
|
59
|
+
}
|
|
60
|
+
const cached = getCachedCount(b.repo_url);
|
|
61
|
+
const knownCount = cached ?? b.skill_count ?? null;
|
|
62
|
+
return knownCount !== null ? { ...b, skillCount: knownCount, has_tools: b.has_tools } : b;
|
|
63
|
+
});
|
|
64
|
+
refreshCountsInBackground(bundlesWithCounts);
|
|
65
|
+
|
|
66
|
+
const installedSet = new Set();
|
|
67
|
+
try {
|
|
68
|
+
const cfg = _lcMkt();
|
|
69
|
+
const db = _getDbMkt();
|
|
70
|
+
|
|
71
|
+
for (const b of (Array.isArray(bundles) ? bundles : [])) {
|
|
72
|
+
if (b.repo_url) {
|
|
73
|
+
const owner = b.repo_url.split('/')[0];
|
|
74
|
+
const repo = b.repo_url.split('/')[1];
|
|
75
|
+
const clonedDir = path.join(githubDir, `${owner}-${repo}`);
|
|
76
|
+
if (fs.existsSync(clonedDir) && fs.readdirSync(clonedDir).length > 0) installedSet.add(b.id);
|
|
77
|
+
} else if (Array.isArray(b.skills)) {
|
|
78
|
+
const allOnDisk = b.skills.every(sid => {
|
|
79
|
+
const row = db.prepare('SELECT path FROM skills WHERE id = ?').get(sid);
|
|
80
|
+
return row && fs.existsSync(row.path);
|
|
81
|
+
});
|
|
82
|
+
if (b.skills.length > 0 && allOnDisk) installedSet.add(b.id);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (const row of db.prepare('SELECT id, path FROM skills WHERE source = ?').all('marketplace')) {
|
|
87
|
+
if (fs.existsSync(row.path)) installedSet.add(row.id);
|
|
88
|
+
}
|
|
89
|
+
} catch {}
|
|
90
|
+
|
|
91
|
+
const { runTUI } = await import('../tui.js');
|
|
92
|
+
const { loadConfig: _lcR, saveConfig: _scR, SKILLS_STORE_DIR: _ssR } = await import('../config.js');
|
|
93
|
+
const { getDb: _getDbR } = await import('../db.js');
|
|
94
|
+
|
|
95
|
+
await runTUI(
|
|
96
|
+
Array.isArray(skills) ? skills : [],
|
|
97
|
+
bundlesWithCounts,
|
|
98
|
+
async (item, onStatus) => {
|
|
99
|
+
if (item.type === 'bundle') {
|
|
100
|
+
const r = await installBundleBg(item.id, async (err, result) => {
|
|
101
|
+
if (err) { onStatus(false, err.message?.slice(0, 60) || 'Install failed'); return; }
|
|
102
|
+
installedSet.add(item.id);
|
|
103
|
+
const { getCachedCount } = await import('../bundle-counts.js');
|
|
104
|
+
const cached = getCachedCount(item.repo_url);
|
|
105
|
+
if (cached !== null) item.skillCount = cached;
|
|
106
|
+
validateAndPruneMarketplace();
|
|
107
|
+
onStatus(true, `Installed ${item.name}`);
|
|
108
|
+
});
|
|
109
|
+
if (r?.error) {
|
|
110
|
+
if (!r.dedup) onStatus(false, r.error.slice(0, 60));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
onStatus(null, `Queued ${item.name}…`);
|
|
114
|
+
} else {
|
|
115
|
+
const r = await installSkill(item.code || item.id);
|
|
116
|
+
if (r?.error) { onStatus(false, r.error.slice(0, 60)); return; }
|
|
117
|
+
installedSet.add(item.id);
|
|
118
|
+
if (item.code) installedSet.add(item.code);
|
|
119
|
+
validateAndPruneMarketplace();
|
|
120
|
+
onStatus(true, `Installed ${item.name}`);
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
installedSet,
|
|
124
|
+
async (item) => {
|
|
125
|
+
const cfg = _lcR();
|
|
126
|
+
const db = _getDbR();
|
|
127
|
+
if (item.type === 'bundle' && item.repo_url) {
|
|
128
|
+
const owner = item.repo_url.split('/')[0];
|
|
129
|
+
const repo = item.repo_url.split('/')[1];
|
|
130
|
+
const clonedName = `${owner}-${repo}`;
|
|
131
|
+
const clonedDir = path.join(_ssR, 'github', clonedName);
|
|
132
|
+
if (fs.existsSync(clonedDir)) fs.rmSync(clonedDir, { recursive: true, force: true });
|
|
133
|
+
const src = `github:${clonedName}`;
|
|
134
|
+
cfg.sources = cfg.sources.filter(s => s.source !== src && !s.dir.startsWith(clonedDir));
|
|
135
|
+
_scR(cfg);
|
|
136
|
+
db.prepare('DELETE FROM skills WHERE source = ?').run(src);
|
|
137
|
+
db.prepare('DELETE FROM chunks WHERE skill_id NOT IN (SELECT id FROM skills)').run();
|
|
138
|
+
} else if (item.type === 'bundle') {
|
|
139
|
+
const mktDir = path.join(_ssR, 'marketplace');
|
|
140
|
+
for (const sid of (item.skills || [])) {
|
|
141
|
+
const row = db.prepare('SELECT path FROM skills WHERE id = ?').get(sid);
|
|
142
|
+
if (row?.path && fs.existsSync(row.path)) fs.unlinkSync(row.path);
|
|
143
|
+
db.prepare('DELETE FROM skills WHERE id = ?').run(sid);
|
|
144
|
+
db.prepare('DELETE FROM chunks WHERE skill_id = ?').run(sid);
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
const row = db.prepare('SELECT path FROM skills WHERE id = ?').get(item.id);
|
|
148
|
+
if (row?.path && fs.existsSync(row.path)) fs.unlinkSync(row.path);
|
|
149
|
+
db.prepare('DELETE FROM skills WHERE id = ?').run(item.id);
|
|
150
|
+
db.prepare('DELETE FROM chunks WHERE skill_id = ?').run(item.id);
|
|
151
|
+
}
|
|
152
|
+
installedSet.delete(item.id);
|
|
153
|
+
if (item.code) installedSet.delete(item.code);
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
process.exit(0);
|
|
157
|
+
}
|
package/config.js
CHANGED
|
@@ -57,7 +57,14 @@ function makeDefaults(skillsDir, platform) {
|
|
|
57
57
|
|
|
58
58
|
export function loadConfig() {
|
|
59
59
|
if (fs.existsSync(CONFIG_PATH)) {
|
|
60
|
-
|
|
60
|
+
// Strip a UTF-8/UTF-16 BOM if an editor or PowerShell wrote one — JSON.parse
|
|
61
|
+
// rejects a leading BOM and would otherwise crash every command.
|
|
62
|
+
const raw = fs.readFileSync(CONFIG_PATH, 'utf8').replace(/^/, '');
|
|
63
|
+
try {
|
|
64
|
+
return JSON.parse(raw);
|
|
65
|
+
} catch (e) {
|
|
66
|
+
throw new Error(`Config file is corrupted (${CONFIG_PATH}): ${e.message}. Delete it to regenerate defaults.`);
|
|
67
|
+
}
|
|
61
68
|
}
|
|
62
69
|
return JSON.parse(JSON.stringify(makeDefaults()));
|
|
63
70
|
}
|