@hiveforge/hivemind-mcp 2.3.0 → 2.5.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/README.md +169 -27
- package/dist/cli.js +938 -77
- package/dist/cli.js.map +1 -1
- package/dist/config/schema.d.ts +125 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +79 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/graph/builder.d.ts +7 -3
- package/dist/graph/builder.d.ts.map +1 -1
- package/dist/graph/builder.js +51 -21
- package/dist/graph/builder.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp/tool-generator.d.ts +3 -1
- package/dist/mcp/tool-generator.d.ts.map +1 -1
- package/dist/mcp/tool-generator.js +41 -5
- package/dist/mcp/tool-generator.js.map +1 -1
- package/dist/search/engine.d.ts +9 -1
- package/dist/search/engine.d.ts.map +1 -1
- package/dist/search/engine.js +16 -4
- package/dist/search/engine.js.map +1 -1
- package/dist/templates/builtin/people-management.d.ts +18 -0
- package/dist/templates/builtin/people-management.d.ts.map +1 -0
- package/dist/templates/builtin/people-management.js +533 -0
- package/dist/templates/builtin/people-management.js.map +1 -0
- package/dist/templates/builtin/research.d.ts +18 -0
- package/dist/templates/builtin/research.d.ts.map +1 -0
- package/dist/templates/builtin/research.js +359 -0
- package/dist/templates/builtin/research.js.map +1 -0
- package/dist/templates/builtin/worldbuilding.d.ts.map +1 -1
- package/dist/templates/builtin/worldbuilding.js +148 -0
- package/dist/templates/builtin/worldbuilding.js.map +1 -1
- package/dist/templates/community/architecture.d.ts +19 -0
- package/dist/templates/community/architecture.d.ts.map +1 -0
- package/dist/templates/community/architecture.js +296 -0
- package/dist/templates/community/architecture.js.map +1 -0
- package/dist/templates/community/index.d.ts +37 -0
- package/dist/templates/community/index.d.ts.map +1 -0
- package/dist/templates/community/index.js +45 -0
- package/dist/templates/community/index.js.map +1 -0
- package/dist/templates/community/ux-research.d.ts +21 -0
- package/dist/templates/community/ux-research.d.ts.map +1 -0
- package/dist/templates/community/ux-research.js +347 -0
- package/dist/templates/community/ux-research.js.map +1 -0
- package/dist/templates/loader.d.ts +65 -8
- package/dist/templates/loader.d.ts.map +1 -1
- package/dist/templates/loader.js +139 -12
- package/dist/templates/loader.js.map +1 -1
- package/dist/templates/registry.d.ts +31 -1
- package/dist/templates/registry.d.ts.map +1 -1
- package/dist/templates/registry.js +64 -0
- package/dist/templates/registry.js.map +1 -1
- package/dist/templates/types.d.ts +57 -0
- package/dist/templates/types.d.ts.map +1 -1
- package/dist/templates/validator.d.ts +166 -0
- package/dist/templates/validator.d.ts.map +1 -1
- package/dist/templates/validator.js +67 -2
- package/dist/templates/validator.js.map +1 -1
- package/dist/templates/version.d.ts +63 -0
- package/dist/templates/version.d.ts.map +1 -0
- package/dist/templates/version.js +119 -0
- package/dist/templates/version.js.map +1 -0
- package/dist/types/index.d.ts +12 -12
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,29 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
3
|
-
import { resolve, join, basename } from 'path';
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
3
|
+
import { resolve, join, basename, dirname } from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
4
5
|
import * as readline from 'readline/promises';
|
|
5
6
|
import { stdin as input, stdout as output } from 'process';
|
|
6
7
|
import { FolderMapper } from './templates/folder-mapper.js';
|
|
7
8
|
import { templateRegistry } from './templates/registry.js';
|
|
8
9
|
import { worldbuildingTemplate } from './templates/builtin/worldbuilding.js';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
10
|
+
import { researchTemplate } from './templates/builtin/research.js';
|
|
11
|
+
import { peopleManagementTemplate } from './templates/builtin/people-management.js';
|
|
12
|
+
import { loadTemplateFile } from './templates/loader.js';
|
|
13
|
+
import { TemplateValidationError, TemplateDefinitionSchema } from './templates/validator.js';
|
|
14
|
+
import { communityTemplates, getCommunityTemplate, listCommunityTemplateIds } from './templates/community/index.js';
|
|
15
|
+
import { checkTemplateCompatibility, getHivemindVersion } from './templates/version.js';
|
|
16
|
+
/**
|
|
17
|
+
* All available templates from registry (built-in + community)
|
|
18
|
+
*/
|
|
19
|
+
const AVAILABLE_TEMPLATES = {
|
|
20
|
+
// Built-in templates
|
|
21
|
+
'worldbuilding': worldbuildingTemplate,
|
|
22
|
+
'research': researchTemplate,
|
|
23
|
+
'people-management': peopleManagementTemplate,
|
|
24
|
+
// Community templates are added dynamically
|
|
24
25
|
};
|
|
26
|
+
// Add community templates to available templates
|
|
27
|
+
for (const template of communityTemplates) {
|
|
28
|
+
AVAILABLE_TEMPLATES[template.id] = template;
|
|
29
|
+
}
|
|
25
30
|
async function init() {
|
|
26
|
-
console.log('
|
|
31
|
+
console.log('\nHivemind Setup Wizard');
|
|
32
|
+
console.log('=====================\n');
|
|
27
33
|
const rl = readline.createInterface({ input, output });
|
|
28
34
|
try {
|
|
29
35
|
// Check if config already exists
|
|
@@ -36,7 +42,8 @@ async function init() {
|
|
|
36
42
|
return;
|
|
37
43
|
}
|
|
38
44
|
}
|
|
39
|
-
//
|
|
45
|
+
// Step 1: Vault path
|
|
46
|
+
console.log('Step 1/4: Vault Path\n');
|
|
40
47
|
const vaultPath = await rl.question('Enter your Obsidian vault path: ');
|
|
41
48
|
if (!vaultPath.trim()) {
|
|
42
49
|
console.error('❌ Vault path is required');
|
|
@@ -53,28 +60,56 @@ async function init() {
|
|
|
53
60
|
return;
|
|
54
61
|
}
|
|
55
62
|
}
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
// Step 2: Template selection
|
|
64
|
+
console.log('\nStep 2/4: Template Selection\n');
|
|
65
|
+
console.log('Which template best fits your use case?');
|
|
66
|
+
console.log(' 1. worldbuilding - Fiction, games, RPGs (characters, locations, events)');
|
|
67
|
+
console.log(' 2. research - Academic, knowledge (papers, citations, concepts)');
|
|
68
|
+
console.log(' 3. people-management - Teams, HR (people, goals, teams, 1:1s)');
|
|
69
|
+
console.log(' 4. software-architecture - Engineers (systems, ADRs, components)');
|
|
70
|
+
console.log(' 5. ux-research - UX, product (interviews, insights, personas)');
|
|
71
|
+
const templateChoice = await rl.question('\nEnter choice (1-5) [default: 1]: ');
|
|
72
|
+
const templates = ['worldbuilding', 'research', 'people-management', 'software-architecture', 'ux-research'];
|
|
73
|
+
const selectedTemplate = templates[parseInt(templateChoice) - 1] || 'worldbuilding';
|
|
74
|
+
// Step 3: Vector search
|
|
75
|
+
console.log('\nStep 3/4: Features\n');
|
|
76
|
+
const enableVector = await rl.question('Enable vector search? (requires embedding setup) (y/N): ');
|
|
77
|
+
// Step 4: Write config
|
|
59
78
|
const config = {
|
|
60
|
-
...DEFAULT_CONFIG,
|
|
61
79
|
vault: {
|
|
62
|
-
|
|
63
|
-
|
|
80
|
+
path: resolvedPath,
|
|
81
|
+
watchForChanges: true,
|
|
82
|
+
debounceMs: 100,
|
|
83
|
+
},
|
|
84
|
+
server: {
|
|
85
|
+
transport: 'stdio',
|
|
86
|
+
},
|
|
87
|
+
template: {
|
|
88
|
+
activeTemplate: selectedTemplate,
|
|
64
89
|
},
|
|
65
90
|
indexing: {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
91
|
+
strategy: 'incremental',
|
|
92
|
+
batchSize: 100,
|
|
93
|
+
enableVectorSearch: enableVector.toLowerCase() === 'y',
|
|
94
|
+
enableFullTextSearch: true,
|
|
95
|
+
},
|
|
69
96
|
};
|
|
70
|
-
// Write config
|
|
71
97
|
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
72
|
-
console.log(`\n✅
|
|
73
|
-
console.log(
|
|
74
|
-
|
|
75
|
-
console.log('
|
|
76
|
-
|
|
98
|
+
console.log(`\n✅ Created config.json`);
|
|
99
|
+
console.log(` Template: ${selectedTemplate}`);
|
|
100
|
+
// Step 5: MCP client setup
|
|
101
|
+
console.log('\nStep 4/4: MCP Client Setup\n');
|
|
102
|
+
const setupMcpNow = await rl.question('Set up MCP client config now? (Y/n): ');
|
|
77
103
|
rl.close();
|
|
104
|
+
if (setupMcpNow.toLowerCase() !== 'n') {
|
|
105
|
+
await setupMcp();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Final next steps
|
|
109
|
+
console.log('\n📋 Next steps:');
|
|
110
|
+
console.log(' 1. Run: npx @hiveforge/hivemind-mcp setup-mcp');
|
|
111
|
+
console.log(' 2. Add frontmatter to your vault files');
|
|
112
|
+
console.log(' 3. Start querying via your AI assistant\n');
|
|
78
113
|
}
|
|
79
114
|
catch (error) {
|
|
80
115
|
console.error('Error during setup:', error);
|
|
@@ -207,8 +242,15 @@ function getVaultPath() {
|
|
|
207
242
|
return resolve(config.vault.path);
|
|
208
243
|
}
|
|
209
244
|
}
|
|
210
|
-
catch {
|
|
211
|
-
|
|
245
|
+
catch (err) {
|
|
246
|
+
if (err instanceof SyntaxError) {
|
|
247
|
+
console.error('❌ config.json contains invalid JSON:');
|
|
248
|
+
console.error(` ${err.message}`);
|
|
249
|
+
console.error('\n Run: npx @hiveforge/hivemind-mcp validate');
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
// Re-throw unexpected errors
|
|
253
|
+
throw err;
|
|
212
254
|
}
|
|
213
255
|
}
|
|
214
256
|
// Fall back to current directory
|
|
@@ -283,64 +325,851 @@ function generateId(name, entityType) {
|
|
|
283
325
|
return `${entityType}-${slug}`;
|
|
284
326
|
}
|
|
285
327
|
async function validate() {
|
|
328
|
+
console.log('\nHivemind Configuration Validator');
|
|
329
|
+
console.log('================================\n');
|
|
330
|
+
// Show config detection order
|
|
331
|
+
console.log('📍 Config detection order:');
|
|
332
|
+
console.log(' 1. CLI --vault flag (highest priority)');
|
|
333
|
+
console.log(' 2. HIVEMIND_CONFIG_PATH environment variable');
|
|
334
|
+
console.log(' 3. ./config.json');
|
|
335
|
+
console.log(' 4. ./hivemind.config.json');
|
|
336
|
+
console.log(' 5. HIVEMIND_VAULT_PATH environment variable');
|
|
337
|
+
console.log('');
|
|
286
338
|
const configPath = resolve(process.cwd(), 'config.json');
|
|
287
|
-
console.log('🔍 Validating Hivemind configuration...\n');
|
|
288
339
|
// Check config exists
|
|
289
340
|
if (!existsSync(configPath)) {
|
|
290
341
|
console.error('❌ config.json not found');
|
|
291
342
|
console.log(' Run "npx @hiveforge/hivemind-mcp init" to create one');
|
|
292
343
|
process.exit(1);
|
|
293
344
|
}
|
|
345
|
+
console.log(`📂 Found config at: ${configPath}\n`);
|
|
294
346
|
// Load and validate config
|
|
347
|
+
let config;
|
|
348
|
+
try {
|
|
349
|
+
config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
350
|
+
}
|
|
351
|
+
catch (err) {
|
|
352
|
+
if (err instanceof SyntaxError) {
|
|
353
|
+
console.error('❌ config.json contains invalid JSON:');
|
|
354
|
+
console.error(` ${err.message}`);
|
|
355
|
+
console.error('\n Tip: Use a JSON validator like jsonlint.com to check syntax.');
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
console.error('❌ Error reading config.json:', err);
|
|
359
|
+
}
|
|
360
|
+
process.exit(1);
|
|
361
|
+
}
|
|
362
|
+
console.log('✅ Valid JSON syntax');
|
|
363
|
+
// Import and use schema validation
|
|
364
|
+
const { validateConfig: schemaValidate, formatValidationErrors } = await import('./config/schema.js');
|
|
365
|
+
const validation = schemaValidate(config);
|
|
366
|
+
if (!validation.success) {
|
|
367
|
+
console.error('❌ Schema validation failed:');
|
|
368
|
+
console.error(formatValidationErrors(validation.errors));
|
|
369
|
+
process.exit(1);
|
|
370
|
+
}
|
|
371
|
+
console.log('✅ Schema validation passed');
|
|
372
|
+
// Check vault path
|
|
373
|
+
if (!config.vault || typeof config.vault !== 'object' || !('path' in config.vault)) {
|
|
374
|
+
console.error('❌ vault.path is missing in config');
|
|
375
|
+
process.exit(1);
|
|
376
|
+
}
|
|
377
|
+
const vaultPath = resolve(config.vault.path);
|
|
378
|
+
if (!existsSync(vaultPath)) {
|
|
379
|
+
console.error(`❌ Vault path does not exist: ${vaultPath}`);
|
|
380
|
+
console.log('\n Tip: Update the path in config.json or create the directory.');
|
|
381
|
+
process.exit(1);
|
|
382
|
+
}
|
|
383
|
+
console.log(`✅ Vault path exists: ${vaultPath}`);
|
|
384
|
+
// Check for .md files
|
|
385
|
+
const { readdirSync } = await import('fs');
|
|
386
|
+
let mdCount = 0;
|
|
387
|
+
function countMarkdown(dir) {
|
|
388
|
+
try {
|
|
389
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
390
|
+
for (const entry of entries) {
|
|
391
|
+
const fullPath = join(dir, entry.name);
|
|
392
|
+
if (entry.isDirectory() && !entry.name.startsWith('.')) {
|
|
393
|
+
countMarkdown(fullPath);
|
|
394
|
+
}
|
|
395
|
+
else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
396
|
+
mdCount++;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
401
|
+
// Skip directories we can't read
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
countMarkdown(vaultPath);
|
|
405
|
+
console.log(`✅ Found ${mdCount} markdown file(s)`);
|
|
406
|
+
// Show active template if configured
|
|
407
|
+
if (config.template && typeof config.template === 'object' && 'activeTemplate' in config.template) {
|
|
408
|
+
console.log(`✅ Active template: ${config.template.activeTemplate}`);
|
|
409
|
+
}
|
|
410
|
+
// Check if built
|
|
411
|
+
const distPath = resolve(process.cwd(), 'dist', 'index.js');
|
|
412
|
+
if (!existsSync(distPath)) {
|
|
413
|
+
console.warn('⚠️ dist/index.js not found - run "npm run build"');
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
console.log('✅ Server built (dist/index.js exists)');
|
|
417
|
+
}
|
|
418
|
+
console.log('\n✅ Configuration is valid!');
|
|
419
|
+
console.log('\nTo start the server: npx @hiveforge/hivemind-mcp start');
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Get the MCP config file path for different clients
|
|
423
|
+
*/
|
|
424
|
+
function getMcpConfigPath(client) {
|
|
425
|
+
const platform = process.platform;
|
|
426
|
+
switch (client) {
|
|
427
|
+
case '1': // Claude Desktop
|
|
428
|
+
if (platform === 'win32') {
|
|
429
|
+
return join(process.env.APPDATA || '', 'Claude', 'claude_desktop_config.json');
|
|
430
|
+
}
|
|
431
|
+
else if (platform === 'darwin') {
|
|
432
|
+
return join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
return join(homedir(), '.config', 'claude', 'claude_desktop_config.json');
|
|
436
|
+
}
|
|
437
|
+
case '2': // GitHub Copilot
|
|
438
|
+
return join(homedir(), '.copilot', 'mcp-config.json');
|
|
439
|
+
default:
|
|
440
|
+
return '(varies by client)';
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Generate MCP config for different clients
|
|
445
|
+
*/
|
|
446
|
+
function generateMcpConfig(client, vaultPath) {
|
|
447
|
+
const baseConfig = {
|
|
448
|
+
command: 'npx',
|
|
449
|
+
args: vaultPath
|
|
450
|
+
? ['-y', '@hiveforge/hivemind-mcp', '--vault', vaultPath]
|
|
451
|
+
: ['-y', '@hiveforge/hivemind-mcp', 'start'],
|
|
452
|
+
};
|
|
453
|
+
if (client === '2') {
|
|
454
|
+
// GitHub Copilot format
|
|
455
|
+
return {
|
|
456
|
+
mcpServers: {
|
|
457
|
+
hivemind: {
|
|
458
|
+
type: 'local',
|
|
459
|
+
...baseConfig,
|
|
460
|
+
tools: ['*'],
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
// Claude Desktop and generic format
|
|
466
|
+
return {
|
|
467
|
+
mcpServers: {
|
|
468
|
+
hivemind: baseConfig,
|
|
469
|
+
},
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Write MCP config to file, merging with existing config
|
|
474
|
+
*/
|
|
475
|
+
function writeMcpConfig(configPath, newConfig) {
|
|
476
|
+
let existingConfig = { mcpServers: {} };
|
|
477
|
+
if (existsSync(configPath)) {
|
|
478
|
+
try {
|
|
479
|
+
existingConfig = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
480
|
+
}
|
|
481
|
+
catch {
|
|
482
|
+
// Start fresh if existing config is invalid
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
// Merge hivemind into existing config
|
|
486
|
+
existingConfig.mcpServers = existingConfig.mcpServers || {};
|
|
487
|
+
existingConfig.mcpServers.hivemind = newConfig.mcpServers.hivemind;
|
|
488
|
+
// Ensure directory exists
|
|
489
|
+
const dir = dirname(configPath);
|
|
490
|
+
if (!existsSync(dir)) {
|
|
491
|
+
mkdirSync(dir, { recursive: true });
|
|
492
|
+
}
|
|
493
|
+
writeFileSync(configPath, JSON.stringify(existingConfig, null, 2));
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Setup MCP client configuration
|
|
497
|
+
*/
|
|
498
|
+
async function setupMcp() {
|
|
499
|
+
const rl = readline.createInterface({ input, output });
|
|
500
|
+
console.log('\nHivemind MCP Client Setup');
|
|
501
|
+
console.log('=========================\n');
|
|
502
|
+
try {
|
|
503
|
+
// 1. Select MCP client
|
|
504
|
+
console.log('Which MCP client are you using?');
|
|
505
|
+
console.log(' 1. Claude Desktop');
|
|
506
|
+
console.log(' 2. GitHub Copilot');
|
|
507
|
+
console.log(' 3. Other (show generic config)');
|
|
508
|
+
const clientChoice = await rl.question('\nEnter choice (1-3): ');
|
|
509
|
+
// 2. Get vault path
|
|
510
|
+
const currentVault = getVaultPath();
|
|
511
|
+
console.log(`\nCurrent configured vault: ${currentVault || '(none)'}`);
|
|
512
|
+
const vaultInput = await rl.question('Vault path (press Enter to use current, or enter new path): ');
|
|
513
|
+
const resolvedVault = vaultInput.trim()
|
|
514
|
+
? resolve(vaultInput.trim())
|
|
515
|
+
: currentVault || undefined;
|
|
516
|
+
// 3. Generate config based on client
|
|
517
|
+
const config = generateMcpConfig(clientChoice, resolvedVault);
|
|
518
|
+
// 4. Show config file location
|
|
519
|
+
const configPath = getMcpConfigPath(clientChoice);
|
|
520
|
+
console.log(`\n📁 Config file location:`);
|
|
521
|
+
console.log(` ${configPath}\n`);
|
|
522
|
+
console.log('📋 Add this to your MCP config:\n');
|
|
523
|
+
console.log(JSON.stringify(config, null, 2));
|
|
524
|
+
// 5. Offer to write config (only for known clients)
|
|
525
|
+
if (clientChoice === '1' || clientChoice === '2') {
|
|
526
|
+
const writeChoice = await rl.question('\nWrite to config file? (y/N): ');
|
|
527
|
+
if (writeChoice.toLowerCase() === 'y') {
|
|
528
|
+
writeMcpConfig(configPath, config);
|
|
529
|
+
console.log(`\n✅ Config written to ${configPath}`);
|
|
530
|
+
console.log(' Restart your MCP client to apply changes.');
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
console.log('\n📝 Next steps:');
|
|
534
|
+
console.log(' 1. If you didn\'t write the config, copy the JSON above to your MCP client config');
|
|
535
|
+
console.log(' 2. Restart your MCP client (Claude Desktop, VS Code, etc.)');
|
|
536
|
+
console.log(' 3. Start using Hivemind tools in your AI conversations\n');
|
|
537
|
+
}
|
|
538
|
+
finally {
|
|
539
|
+
rl.close();
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Validate a template file
|
|
544
|
+
*/
|
|
545
|
+
async function validateTemplate() {
|
|
546
|
+
const filePath = process.argv[3] || './template.json';
|
|
547
|
+
console.log(`🔍 Validating template file: ${filePath}\n`);
|
|
548
|
+
if (!existsSync(filePath)) {
|
|
549
|
+
console.error(`❌ Template file not found: ${filePath}`);
|
|
550
|
+
process.exit(1);
|
|
551
|
+
}
|
|
552
|
+
try {
|
|
553
|
+
const template = loadTemplateFile(filePath);
|
|
554
|
+
console.log('✓ Template is valid!\n');
|
|
555
|
+
console.log(`ID: ${template.id}`);
|
|
556
|
+
console.log(`Name: ${template.name}`);
|
|
557
|
+
console.log(`Version: ${template.version}`);
|
|
558
|
+
if (template.description) {
|
|
559
|
+
console.log(`Description: ${template.description}`);
|
|
560
|
+
}
|
|
561
|
+
console.log(`\nEntity Types (${template.entityTypes.length}):`);
|
|
562
|
+
for (const et of template.entityTypes) {
|
|
563
|
+
const requiredFields = et.fields.filter(f => f.required).length;
|
|
564
|
+
console.log(` - ${et.name} (${et.fields.length} fields, ${requiredFields} required)`);
|
|
565
|
+
}
|
|
566
|
+
if (template.relationshipTypes?.length) {
|
|
567
|
+
console.log(`\nRelationship Types (${template.relationshipTypes.length}):`);
|
|
568
|
+
for (const rt of template.relationshipTypes) {
|
|
569
|
+
const sourceStr = Array.isArray(rt.sourceTypes) ? rt.sourceTypes.join(', ') : rt.sourceTypes;
|
|
570
|
+
const targetStr = Array.isArray(rt.targetTypes) ? rt.targetTypes.join(', ') : rt.targetTypes;
|
|
571
|
+
console.log(` - ${rt.id}: ${sourceStr} → ${targetStr}${rt.bidirectional ? ' (bidirectional)' : ''}`);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
console.log('\n✅ Template validation complete!');
|
|
575
|
+
}
|
|
576
|
+
catch (err) {
|
|
577
|
+
if (err instanceof TemplateValidationError) {
|
|
578
|
+
console.error(err.toUserMessage());
|
|
579
|
+
}
|
|
580
|
+
else {
|
|
581
|
+
console.error(`❌ Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
582
|
+
}
|
|
583
|
+
process.exit(1);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Interactive template creation wizard
|
|
588
|
+
*/
|
|
589
|
+
async function createTemplate() {
|
|
590
|
+
console.log('🧠 Hivemind Template Creator\n');
|
|
591
|
+
console.log('='.repeat(40) + '\n');
|
|
592
|
+
const rl = readline.createInterface({ input, output });
|
|
295
593
|
try {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
594
|
+
// 1. Template metadata
|
|
595
|
+
const id = await promptRequired(rl, 'Template ID (lowercase with hyphens)');
|
|
596
|
+
if (!/^[a-z][a-z0-9-]*$/.test(id)) {
|
|
597
|
+
console.error('❌ Template ID must be lowercase alphanumeric with hyphens');
|
|
598
|
+
rl.close();
|
|
301
599
|
process.exit(1);
|
|
302
600
|
}
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
601
|
+
const name = await promptRequired(rl, 'Template name');
|
|
602
|
+
const description = await rl.question('Description (optional): ');
|
|
603
|
+
const version = (await rl.question('Version (default 1.0.0): ')).trim() || '1.0.0';
|
|
604
|
+
if (!/^\d+\.\d+\.\d+$/.test(version)) {
|
|
605
|
+
console.error('❌ Version must follow semantic versioning (e.g., 1.0.0)');
|
|
606
|
+
rl.close();
|
|
306
607
|
process.exit(1);
|
|
307
608
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
609
|
+
// 2. Entity types
|
|
610
|
+
console.log('\n=== Entity Types ===\n');
|
|
611
|
+
const entityTypes = [];
|
|
612
|
+
while (true) {
|
|
613
|
+
const entityName = (await rl.question("Entity name (lowercase, or 'done'): ")).trim().toLowerCase();
|
|
614
|
+
if (entityName === 'done' || entityName === '') {
|
|
615
|
+
if (entityTypes.length === 0) {
|
|
616
|
+
console.log('⚠️ Template must have at least one entity type.');
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
break;
|
|
620
|
+
}
|
|
621
|
+
if (!/^[a-z][a-z0-9_]*$/.test(entityName)) {
|
|
622
|
+
console.log('❌ Entity name must be lowercase alphanumeric with underscores');
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
const displayName = await promptRequired(rl, 'Display name');
|
|
626
|
+
const pluralName = await promptRequired(rl, 'Plural name');
|
|
627
|
+
// Fields
|
|
628
|
+
const fields = [];
|
|
629
|
+
console.log('');
|
|
630
|
+
while (true) {
|
|
631
|
+
const fieldName = (await rl.question(" Field name (or 'done'): ")).trim();
|
|
632
|
+
if (fieldName === 'done' || fieldName === '') {
|
|
633
|
+
break;
|
|
634
|
+
}
|
|
635
|
+
if (!/^[a-z][a-zA-Z0-9_]*$/.test(fieldName)) {
|
|
636
|
+
console.log(' ❌ Field name must be camelCase or snake_case');
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
const fieldTypeInput = (await rl.question(' Field type [string/number/boolean/enum/array/date/record]: ')).trim().toLowerCase();
|
|
640
|
+
const validTypes = ['string', 'number', 'boolean', 'enum', 'array', 'date', 'record'];
|
|
641
|
+
if (!validTypes.includes(fieldTypeInput)) {
|
|
642
|
+
console.log(' ❌ Invalid field type');
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
645
|
+
const field = {
|
|
646
|
+
name: fieldName,
|
|
647
|
+
type: fieldTypeInput,
|
|
648
|
+
};
|
|
649
|
+
// Handle enum values
|
|
650
|
+
if (fieldTypeInput === 'enum') {
|
|
651
|
+
const enumValuesStr = await promptRequired(rl, ' Enum values (comma-separated)');
|
|
652
|
+
field.enumValues = enumValuesStr.split(',').map(v => v.trim()).filter(v => v);
|
|
653
|
+
if (field.enumValues.length === 0) {
|
|
654
|
+
console.log(' ❌ Enum must have at least one value');
|
|
655
|
+
continue;
|
|
319
656
|
}
|
|
320
|
-
|
|
321
|
-
|
|
657
|
+
}
|
|
658
|
+
// Handle array item type
|
|
659
|
+
if (fieldTypeInput === 'array') {
|
|
660
|
+
const itemTypeInput = (await rl.question(' Array item type [string/number/boolean]: ')).trim().toLowerCase();
|
|
661
|
+
const validItemTypes = ['string', 'number', 'boolean'];
|
|
662
|
+
if (validItemTypes.includes(itemTypeInput)) {
|
|
663
|
+
field.arrayItemType = itemTypeInput;
|
|
664
|
+
}
|
|
665
|
+
else {
|
|
666
|
+
field.arrayItemType = 'string';
|
|
322
667
|
}
|
|
323
668
|
}
|
|
669
|
+
// Required?
|
|
670
|
+
const requiredInput = (await rl.question(' Required? (y/n): ')).trim().toLowerCase();
|
|
671
|
+
if (requiredInput === 'y' || requiredInput === 'yes') {
|
|
672
|
+
field.required = true;
|
|
673
|
+
}
|
|
674
|
+
fields.push(field);
|
|
675
|
+
console.log(` ✓ Added field: ${fieldName}\n`);
|
|
324
676
|
}
|
|
325
|
-
|
|
326
|
-
|
|
677
|
+
entityTypes.push({
|
|
678
|
+
name: entityName,
|
|
679
|
+
displayName,
|
|
680
|
+
pluralName,
|
|
681
|
+
fields,
|
|
682
|
+
});
|
|
683
|
+
console.log(`✓ Added entity type: ${entityName}\n`);
|
|
684
|
+
}
|
|
685
|
+
// 3. Relationship types (optional)
|
|
686
|
+
console.log('\n=== Relationship Types (optional) ===\n');
|
|
687
|
+
const addRelationships = (await rl.question('Add relationship types? (y/n): ')).trim().toLowerCase();
|
|
688
|
+
const relationshipTypes = [];
|
|
689
|
+
if (addRelationships === 'y' || addRelationships === 'yes') {
|
|
690
|
+
while (true) {
|
|
691
|
+
const relId = (await rl.question("\nRelationship ID (snake_case, or 'done'): ")).trim().toLowerCase();
|
|
692
|
+
if (relId === 'done' || relId === '') {
|
|
693
|
+
break;
|
|
694
|
+
}
|
|
695
|
+
if (!/^[a-z][a-z0-9_]*$/.test(relId)) {
|
|
696
|
+
console.log('❌ Relationship ID must be snake_case');
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
const relDisplayName = await promptRequired(rl, 'Display name');
|
|
700
|
+
const sourceTypesInput = (await rl.question(`Source types (comma-separated, or 'any'): `)).trim();
|
|
701
|
+
const sourceTypes = sourceTypesInput.toLowerCase() === 'any'
|
|
702
|
+
? 'any'
|
|
703
|
+
: sourceTypesInput.split(',').map(t => t.trim()).filter(t => t);
|
|
704
|
+
const targetTypesInput = (await rl.question(`Target types (comma-separated, or 'any'): `)).trim();
|
|
705
|
+
const targetTypes = targetTypesInput.toLowerCase() === 'any'
|
|
706
|
+
? 'any'
|
|
707
|
+
: targetTypesInput.split(',').map(t => t.trim()).filter(t => t);
|
|
708
|
+
const bidirectionalInput = (await rl.question('Bidirectional? (y/n): ')).trim().toLowerCase();
|
|
709
|
+
const bidirectional = bidirectionalInput === 'y' || bidirectionalInput === 'yes';
|
|
710
|
+
let reverseId;
|
|
711
|
+
if (bidirectional) {
|
|
712
|
+
reverseId = await promptRequired(rl, 'Reverse ID (e.g., for "parent_of" the reverse is "child_of")');
|
|
713
|
+
}
|
|
714
|
+
relationshipTypes.push({
|
|
715
|
+
id: relId,
|
|
716
|
+
displayName: relDisplayName,
|
|
717
|
+
sourceTypes,
|
|
718
|
+
targetTypes,
|
|
719
|
+
bidirectional,
|
|
720
|
+
reverseId,
|
|
721
|
+
});
|
|
722
|
+
console.log(`✓ Added relationship type: ${relId}`);
|
|
327
723
|
}
|
|
328
724
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
725
|
+
// 4. Build and validate template
|
|
726
|
+
const template = {
|
|
727
|
+
id,
|
|
728
|
+
name,
|
|
729
|
+
version,
|
|
730
|
+
entityTypes,
|
|
731
|
+
};
|
|
732
|
+
if (description) {
|
|
733
|
+
template.description = description;
|
|
734
|
+
}
|
|
735
|
+
if (relationshipTypes && relationshipTypes.length > 0) {
|
|
736
|
+
template.relationshipTypes = relationshipTypes;
|
|
335
737
|
}
|
|
336
|
-
|
|
337
|
-
|
|
738
|
+
// Validate before writing
|
|
739
|
+
try {
|
|
740
|
+
const { TemplateDefinitionSchema } = await import('./templates/validator.js');
|
|
741
|
+
TemplateDefinitionSchema.parse(template);
|
|
742
|
+
}
|
|
743
|
+
catch (err) {
|
|
744
|
+
console.error('\n❌ Template validation failed:');
|
|
745
|
+
if (err instanceof TemplateValidationError) {
|
|
746
|
+
console.error(err.toUserMessage());
|
|
747
|
+
}
|
|
748
|
+
else {
|
|
749
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
750
|
+
}
|
|
751
|
+
rl.close();
|
|
752
|
+
process.exit(1);
|
|
338
753
|
}
|
|
339
|
-
|
|
340
|
-
|
|
754
|
+
// Write template file
|
|
755
|
+
const outputPath = resolve(process.cwd(), 'template.json');
|
|
756
|
+
writeFileSync(outputPath, JSON.stringify(template, null, 2));
|
|
757
|
+
console.log('\n' + '='.repeat(40));
|
|
758
|
+
console.log(`\n✓ Template created: ${outputPath}\n`);
|
|
759
|
+
console.log('Next steps:');
|
|
760
|
+
console.log(' 1. Review and edit template.json as needed');
|
|
761
|
+
console.log(' 2. Validate: npx @hiveforge/hivemind-mcp validate-template');
|
|
762
|
+
console.log(' 3. Add to config.json or use standalone template.json');
|
|
763
|
+
console.log(' 4. Consider contributing: https://github.com/hiveforge-sh/hivemind/blob/master/CONTRIBUTING.md');
|
|
764
|
+
console.log('');
|
|
765
|
+
rl.close();
|
|
341
766
|
}
|
|
342
767
|
catch (error) {
|
|
343
|
-
console.error('
|
|
768
|
+
console.error('Error during template creation:', error);
|
|
769
|
+
rl.close();
|
|
770
|
+
process.exit(1);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Helper: prompt for required input
|
|
775
|
+
*/
|
|
776
|
+
async function promptRequired(rl, prompt) {
|
|
777
|
+
while (true) {
|
|
778
|
+
const value = (await rl.question(`${prompt}: `)).trim();
|
|
779
|
+
if (value)
|
|
780
|
+
return value;
|
|
781
|
+
console.log(' ⚠️ This field is required.');
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Add a template from registry or URL
|
|
786
|
+
*/
|
|
787
|
+
async function addTemplate() {
|
|
788
|
+
const templateArg = process.argv[3];
|
|
789
|
+
console.log('🧠 Hivemind - Add Template\n');
|
|
790
|
+
// Show available templates if no argument
|
|
791
|
+
if (!templateArg) {
|
|
792
|
+
console.log('Available templates:\n');
|
|
793
|
+
console.log('Built-in:');
|
|
794
|
+
console.log(' - worldbuilding Characters, locations, events, factions, lore');
|
|
795
|
+
console.log(' - research Papers, citations, concepts, notes');
|
|
796
|
+
console.log(' - people-management People, goals, teams, 1:1 meetings');
|
|
797
|
+
console.log('\nCommunity:');
|
|
798
|
+
const communityIds = listCommunityTemplateIds();
|
|
799
|
+
if (communityIds.length === 0) {
|
|
800
|
+
console.log(' (no community templates available)');
|
|
801
|
+
}
|
|
802
|
+
else {
|
|
803
|
+
for (const id of communityIds) {
|
|
804
|
+
const template = getCommunityTemplate(id);
|
|
805
|
+
console.log(` - ${id.padEnd(20)} ${template?.description || template?.name || ''}`);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
console.log('\nUsage:');
|
|
809
|
+
console.log(' npx @hiveforge/hivemind-mcp add-template <name> - Add from registry');
|
|
810
|
+
console.log(' npx @hiveforge/hivemind-mcp add-template <url> - Add from URL');
|
|
811
|
+
console.log(' npx @hiveforge/hivemind-mcp add-template <file.json> - Add from local file');
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
let template;
|
|
815
|
+
// Check if it's a URL
|
|
816
|
+
if (templateArg.startsWith('http://') || templateArg.startsWith('https://')) {
|
|
817
|
+
console.log(`📥 Fetching template from: ${templateArg}\n`);
|
|
818
|
+
try {
|
|
819
|
+
const response = await fetch(templateArg);
|
|
820
|
+
if (!response.ok) {
|
|
821
|
+
console.error(`❌ Failed to fetch template: ${response.status} ${response.statusText}`);
|
|
822
|
+
process.exit(1);
|
|
823
|
+
}
|
|
824
|
+
const templateData = await response.json();
|
|
825
|
+
const result = TemplateDefinitionSchema.safeParse(templateData);
|
|
826
|
+
if (!result.success) {
|
|
827
|
+
throw new TemplateValidationError('Invalid template from URL', result.error.issues);
|
|
828
|
+
}
|
|
829
|
+
template = result.data;
|
|
830
|
+
}
|
|
831
|
+
catch (err) {
|
|
832
|
+
if (err instanceof TemplateValidationError) {
|
|
833
|
+
console.error(err.toUserMessage());
|
|
834
|
+
}
|
|
835
|
+
else {
|
|
836
|
+
console.error(`❌ Error fetching template: ${err instanceof Error ? err.message : String(err)}`);
|
|
837
|
+
}
|
|
838
|
+
process.exit(1);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
// Check if it's a local file
|
|
842
|
+
else if (templateArg.endsWith('.json') || existsSync(templateArg)) {
|
|
843
|
+
console.log(`📂 Loading template from: ${templateArg}\n`);
|
|
844
|
+
try {
|
|
845
|
+
template = loadTemplateFile(templateArg);
|
|
846
|
+
}
|
|
847
|
+
catch (err) {
|
|
848
|
+
if (err instanceof TemplateValidationError) {
|
|
849
|
+
console.error(err.toUserMessage());
|
|
850
|
+
}
|
|
851
|
+
else {
|
|
852
|
+
console.error(`❌ Error loading template: ${err instanceof Error ? err.message : String(err)}`);
|
|
853
|
+
}
|
|
854
|
+
process.exit(1);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
// Otherwise, look up in registry
|
|
858
|
+
else {
|
|
859
|
+
const registeredTemplate = AVAILABLE_TEMPLATES[templateArg];
|
|
860
|
+
if (!registeredTemplate) {
|
|
861
|
+
console.error(`❌ Template not found: ${templateArg}`);
|
|
862
|
+
console.log('\nAvailable templates:');
|
|
863
|
+
for (const id of Object.keys(AVAILABLE_TEMPLATES)) {
|
|
864
|
+
console.log(` - ${id}`);
|
|
865
|
+
}
|
|
866
|
+
process.exit(1);
|
|
867
|
+
}
|
|
868
|
+
template = registeredTemplate;
|
|
869
|
+
console.log(`📦 Using template from registry: ${templateArg}\n`);
|
|
870
|
+
}
|
|
871
|
+
// Show template info
|
|
872
|
+
console.log(`Template: ${template.name}`);
|
|
873
|
+
console.log(`ID: ${template.id}`);
|
|
874
|
+
console.log(`Version: ${template.version}`);
|
|
875
|
+
if (template.description) {
|
|
876
|
+
console.log(`Description: ${template.description}`);
|
|
877
|
+
}
|
|
878
|
+
console.log(`\nEntity Types (${template.entityTypes.length}):`);
|
|
879
|
+
for (const et of template.entityTypes) {
|
|
880
|
+
console.log(` - ${et.displayName} (${et.name})`);
|
|
881
|
+
}
|
|
882
|
+
if (template.relationshipTypes?.length) {
|
|
883
|
+
console.log(`\nRelationship Types (${template.relationshipTypes.length}):`);
|
|
884
|
+
for (const rt of template.relationshipTypes) {
|
|
885
|
+
console.log(` - ${rt.displayName} (${rt.id})`);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
// Ask how to save
|
|
889
|
+
const rl = readline.createInterface({ input, output });
|
|
890
|
+
try {
|
|
891
|
+
console.log('\nHow do you want to add this template?\n');
|
|
892
|
+
console.log(' 1. Save as standalone template.json (recommended for custom templates)');
|
|
893
|
+
console.log(' 2. Add to config.json (inline definition)');
|
|
894
|
+
console.log(' 3. Just set as active template (for built-in/community templates)\n');
|
|
895
|
+
const choice = await rl.question('Enter choice (1/2/3): ');
|
|
896
|
+
switch (choice.trim()) {
|
|
897
|
+
case '1': {
|
|
898
|
+
// Save as standalone template.json
|
|
899
|
+
const outputPath = resolve(process.cwd(), 'template.json');
|
|
900
|
+
if (existsSync(outputPath)) {
|
|
901
|
+
const overwrite = await rl.question('template.json already exists. Overwrite? (y/N): ');
|
|
902
|
+
if (overwrite.toLowerCase() !== 'y') {
|
|
903
|
+
console.log('Cancelled.');
|
|
904
|
+
rl.close();
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
writeFileSync(outputPath, JSON.stringify(template, null, 2));
|
|
909
|
+
console.log(`\n✅ Template saved to: ${outputPath}`);
|
|
910
|
+
console.log('\nThe template will be automatically loaded when Hivemind starts.');
|
|
911
|
+
break;
|
|
912
|
+
}
|
|
913
|
+
case '2': {
|
|
914
|
+
// Add to config.json
|
|
915
|
+
const configPath = resolve(process.cwd(), 'config.json');
|
|
916
|
+
let config = {};
|
|
917
|
+
if (existsSync(configPath)) {
|
|
918
|
+
try {
|
|
919
|
+
config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
920
|
+
}
|
|
921
|
+
catch {
|
|
922
|
+
// Start fresh if config is invalid
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
// Ensure template section exists
|
|
926
|
+
if (!config.template) {
|
|
927
|
+
config.template = { activeTemplate: template.id, templates: [] };
|
|
928
|
+
}
|
|
929
|
+
if (!config.template.templates) {
|
|
930
|
+
config.template.templates = [];
|
|
931
|
+
}
|
|
932
|
+
// Check if template already exists
|
|
933
|
+
const existingIndex = config.template.templates.findIndex((t) => t.id === template.id);
|
|
934
|
+
if (existingIndex >= 0) {
|
|
935
|
+
const overwrite = await rl.question(`Template "${template.id}" already exists in config. Overwrite? (y/N): `);
|
|
936
|
+
if (overwrite.toLowerCase() !== 'y') {
|
|
937
|
+
console.log('Cancelled.');
|
|
938
|
+
rl.close();
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
config.template.templates[existingIndex] = template;
|
|
942
|
+
}
|
|
943
|
+
else {
|
|
944
|
+
config.template.templates.push(template);
|
|
945
|
+
}
|
|
946
|
+
// Set as active template
|
|
947
|
+
config.template.activeTemplate = template.id;
|
|
948
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
949
|
+
console.log(`\n✅ Template added to: ${configPath}`);
|
|
950
|
+
console.log(` Active template set to: ${template.id}`);
|
|
951
|
+
break;
|
|
952
|
+
}
|
|
953
|
+
case '3': {
|
|
954
|
+
// Just update activeTemplate in config.json
|
|
955
|
+
const configPath = resolve(process.cwd(), 'config.json');
|
|
956
|
+
let config = {};
|
|
957
|
+
if (existsSync(configPath)) {
|
|
958
|
+
try {
|
|
959
|
+
config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
960
|
+
}
|
|
961
|
+
catch {
|
|
962
|
+
// Start fresh if config is invalid
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
// Ensure template section exists
|
|
966
|
+
if (!config.template) {
|
|
967
|
+
config.template = { activeTemplate: template.id };
|
|
968
|
+
}
|
|
969
|
+
else {
|
|
970
|
+
config.template.activeTemplate = template.id;
|
|
971
|
+
}
|
|
972
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
973
|
+
console.log(`\n✅ Active template set to: ${template.id}`);
|
|
974
|
+
console.log(` Updated: ${configPath}`);
|
|
975
|
+
break;
|
|
976
|
+
}
|
|
977
|
+
default:
|
|
978
|
+
console.log('Cancelled.');
|
|
979
|
+
}
|
|
980
|
+
console.log('\nNext steps:');
|
|
981
|
+
console.log(' 1. Start Hivemind: npx @hiveforge/hivemind-mcp start');
|
|
982
|
+
console.log(' 2. Create notes with the new entity types');
|
|
983
|
+
console.log(` 3. Use MCP tools like query_${template.entityTypes[0]?.name || 'entity'}`);
|
|
984
|
+
}
|
|
985
|
+
finally {
|
|
986
|
+
rl.close();
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* List available templates
|
|
991
|
+
*/
|
|
992
|
+
async function listTemplates() {
|
|
993
|
+
console.log('🧠 Hivemind - Available Templates\n');
|
|
994
|
+
console.log('Built-in Templates:');
|
|
995
|
+
console.log('─'.repeat(60));
|
|
996
|
+
const builtinIds = ['worldbuilding', 'research', 'people-management'];
|
|
997
|
+
for (const id of builtinIds) {
|
|
998
|
+
const template = AVAILABLE_TEMPLATES[id];
|
|
999
|
+
console.log(`\n ${template.name} (${template.id})`);
|
|
1000
|
+
if (template.description) {
|
|
1001
|
+
console.log(` ${template.description}`);
|
|
1002
|
+
}
|
|
1003
|
+
console.log(` Entity types: ${template.entityTypes.map(e => e.name).join(', ')}`);
|
|
1004
|
+
if (template.category) {
|
|
1005
|
+
console.log(` Category: ${template.category}`);
|
|
1006
|
+
}
|
|
1007
|
+
if (template.tags?.length) {
|
|
1008
|
+
console.log(` Tags: ${template.tags.join(', ')}`);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
console.log('\n\nCommunity Templates:');
|
|
1012
|
+
console.log('─'.repeat(60));
|
|
1013
|
+
const communityIds = listCommunityTemplateIds();
|
|
1014
|
+
if (communityIds.length === 0) {
|
|
1015
|
+
console.log('\n (no community templates available)');
|
|
1016
|
+
}
|
|
1017
|
+
else {
|
|
1018
|
+
for (const id of communityIds) {
|
|
1019
|
+
const template = getCommunityTemplate(id);
|
|
1020
|
+
if (template) {
|
|
1021
|
+
console.log(`\n ${template.name} (${template.id})`);
|
|
1022
|
+
if (template.description) {
|
|
1023
|
+
console.log(` ${template.description}`);
|
|
1024
|
+
}
|
|
1025
|
+
console.log(` Entity types: ${template.entityTypes.map(e => e.name).join(', ')}`);
|
|
1026
|
+
if (template.category) {
|
|
1027
|
+
console.log(` Category: ${template.category}`);
|
|
1028
|
+
}
|
|
1029
|
+
if (template.tags?.length) {
|
|
1030
|
+
console.log(` Tags: ${template.tags.join(', ')}`);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
console.log('\n\nTo add a template:');
|
|
1036
|
+
console.log(' npx @hiveforge/hivemind-mcp add-template <template-id>');
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* Generate a JSON catalog of all available templates
|
|
1040
|
+
*/
|
|
1041
|
+
async function generateCatalog() {
|
|
1042
|
+
const outputArg = process.argv[3];
|
|
1043
|
+
const outputPath = outputArg || './template-catalog.json';
|
|
1044
|
+
console.log('🧠 Hivemind - Generating Template Catalog\n');
|
|
1045
|
+
const catalog = {
|
|
1046
|
+
version: '1.0.0',
|
|
1047
|
+
generatedAt: new Date().toISOString(),
|
|
1048
|
+
templates: [],
|
|
1049
|
+
};
|
|
1050
|
+
// Add built-in templates
|
|
1051
|
+
const builtinIds = ['worldbuilding', 'research', 'people-management'];
|
|
1052
|
+
for (const id of builtinIds) {
|
|
1053
|
+
const template = AVAILABLE_TEMPLATES[id];
|
|
1054
|
+
catalog.templates.push(templateToCatalogEntry(template, 'builtin'));
|
|
1055
|
+
}
|
|
1056
|
+
// Add community templates
|
|
1057
|
+
for (const template of communityTemplates) {
|
|
1058
|
+
catalog.templates.push(templateToCatalogEntry(template, 'community'));
|
|
1059
|
+
}
|
|
1060
|
+
// Write catalog
|
|
1061
|
+
writeFileSync(outputPath, JSON.stringify(catalog, null, 2));
|
|
1062
|
+
console.log(`✅ Template catalog generated: ${outputPath}`);
|
|
1063
|
+
console.log(` Total templates: ${catalog.templates.length}`);
|
|
1064
|
+
console.log(` Built-in: ${builtinIds.length}`);
|
|
1065
|
+
console.log(` Community: ${communityTemplates.length}`);
|
|
1066
|
+
// Show summary
|
|
1067
|
+
console.log('\nTemplates included:');
|
|
1068
|
+
for (const entry of catalog.templates) {
|
|
1069
|
+
console.log(` - ${entry.name} (${entry.id}) [${entry.source}]`);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Convert a template to a catalog entry
|
|
1074
|
+
*/
|
|
1075
|
+
function templateToCatalogEntry(template, source) {
|
|
1076
|
+
return {
|
|
1077
|
+
id: template.id,
|
|
1078
|
+
name: template.name,
|
|
1079
|
+
version: template.version,
|
|
1080
|
+
description: template.description,
|
|
1081
|
+
category: template.category,
|
|
1082
|
+
tags: template.tags,
|
|
1083
|
+
author: template.author ? {
|
|
1084
|
+
name: template.author.name,
|
|
1085
|
+
url: template.author.url,
|
|
1086
|
+
} : undefined,
|
|
1087
|
+
repository: template.repository,
|
|
1088
|
+
sampleVault: template.sampleVault,
|
|
1089
|
+
license: template.license,
|
|
1090
|
+
minHivemindVersion: template.minHivemindVersion,
|
|
1091
|
+
source,
|
|
1092
|
+
entityTypes: template.entityTypes.map(et => ({
|
|
1093
|
+
name: et.name,
|
|
1094
|
+
displayName: et.displayName,
|
|
1095
|
+
pluralName: et.pluralName,
|
|
1096
|
+
fieldCount: et.fields.length,
|
|
1097
|
+
})),
|
|
1098
|
+
relationshipCount: template.relationshipTypes?.length || 0,
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Check template compatibility with current Hivemind version
|
|
1103
|
+
*/
|
|
1104
|
+
async function checkCompatibility() {
|
|
1105
|
+
const templateArg = process.argv[3];
|
|
1106
|
+
console.log('🧠 Hivemind - Template Compatibility Check\n');
|
|
1107
|
+
const hivemindVersion = getHivemindVersion();
|
|
1108
|
+
console.log(`Hivemind version: ${hivemindVersion}\n`);
|
|
1109
|
+
// If no argument, check all registered templates
|
|
1110
|
+
if (!templateArg) {
|
|
1111
|
+
console.log('Checking all templates:\n');
|
|
1112
|
+
let allCompatible = true;
|
|
1113
|
+
for (const [id, template] of Object.entries(AVAILABLE_TEMPLATES)) {
|
|
1114
|
+
const result = checkTemplateCompatibility(template.minHivemindVersion, template.version);
|
|
1115
|
+
const icon = result.compatible ? '✓' : '✗';
|
|
1116
|
+
console.log(` ${icon} ${id} (v${template.version})`);
|
|
1117
|
+
if (!result.compatible) {
|
|
1118
|
+
console.log(` ${result.message}`);
|
|
1119
|
+
allCompatible = false;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
console.log('');
|
|
1123
|
+
if (allCompatible) {
|
|
1124
|
+
console.log('✅ All templates are compatible');
|
|
1125
|
+
}
|
|
1126
|
+
else {
|
|
1127
|
+
console.log('⚠️ Some templates have compatibility issues');
|
|
1128
|
+
}
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
// Check specific template by name or file
|
|
1132
|
+
let template;
|
|
1133
|
+
// Check if it's a registered template
|
|
1134
|
+
if (AVAILABLE_TEMPLATES[templateArg]) {
|
|
1135
|
+
template = AVAILABLE_TEMPLATES[templateArg];
|
|
1136
|
+
}
|
|
1137
|
+
// Check if it's a file
|
|
1138
|
+
else if (existsSync(templateArg)) {
|
|
1139
|
+
try {
|
|
1140
|
+
template = loadTemplateFile(templateArg);
|
|
1141
|
+
}
|
|
1142
|
+
catch (err) {
|
|
1143
|
+
if (err instanceof TemplateValidationError) {
|
|
1144
|
+
console.error(err.toUserMessage());
|
|
1145
|
+
}
|
|
1146
|
+
else {
|
|
1147
|
+
console.error(`❌ Error loading template: ${err instanceof Error ? err.message : String(err)}`);
|
|
1148
|
+
}
|
|
1149
|
+
process.exit(1);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
else {
|
|
1153
|
+
console.error(`❌ Template not found: ${templateArg}`);
|
|
1154
|
+
console.log('\nUse "list-templates" to see available templates, or provide a file path.');
|
|
1155
|
+
process.exit(1);
|
|
1156
|
+
}
|
|
1157
|
+
// Check compatibility
|
|
1158
|
+
const result = checkTemplateCompatibility(template.minHivemindVersion, template.version);
|
|
1159
|
+
console.log(`Template: ${template.name} (${template.id})`);
|
|
1160
|
+
console.log(`Template version: ${template.version}`);
|
|
1161
|
+
if (template.minHivemindVersion) {
|
|
1162
|
+
console.log(`Requires Hivemind: >= ${template.minHivemindVersion}`);
|
|
1163
|
+
}
|
|
1164
|
+
else {
|
|
1165
|
+
console.log('Requires Hivemind: (not specified)');
|
|
1166
|
+
}
|
|
1167
|
+
console.log('');
|
|
1168
|
+
if (result.compatible) {
|
|
1169
|
+
console.log(`✅ ${result.message}`);
|
|
1170
|
+
}
|
|
1171
|
+
else {
|
|
1172
|
+
console.log(`❌ ${result.message}`);
|
|
344
1173
|
process.exit(1);
|
|
345
1174
|
}
|
|
346
1175
|
}
|
|
@@ -361,18 +1190,50 @@ else {
|
|
|
361
1190
|
case 'validate':
|
|
362
1191
|
validate();
|
|
363
1192
|
break;
|
|
1193
|
+
case 'setup-mcp':
|
|
1194
|
+
setupMcp();
|
|
1195
|
+
break;
|
|
364
1196
|
case 'fix':
|
|
365
1197
|
fix();
|
|
366
1198
|
break;
|
|
1199
|
+
case 'create-template':
|
|
1200
|
+
createTemplate();
|
|
1201
|
+
break;
|
|
1202
|
+
case 'validate-template':
|
|
1203
|
+
validateTemplate();
|
|
1204
|
+
break;
|
|
1205
|
+
case 'add-template':
|
|
1206
|
+
addTemplate();
|
|
1207
|
+
break;
|
|
1208
|
+
case 'list-templates':
|
|
1209
|
+
listTemplates();
|
|
1210
|
+
break;
|
|
1211
|
+
case 'generate-catalog':
|
|
1212
|
+
generateCatalog();
|
|
1213
|
+
break;
|
|
1214
|
+
case 'check-compatibility':
|
|
1215
|
+
checkCompatibility();
|
|
1216
|
+
break;
|
|
367
1217
|
default:
|
|
368
1218
|
console.log('Hivemind MCP Server\n');
|
|
369
1219
|
console.log('Usage:');
|
|
370
|
-
console.log(' npx @hiveforge/hivemind-mcp init
|
|
371
|
-
console.log(' npx @hiveforge/hivemind-mcp validate
|
|
372
|
-
console.log(' npx @hiveforge/hivemind-mcp start
|
|
373
|
-
console.log(' npx @hiveforge/hivemind-mcp
|
|
374
|
-
console.log(' npx @hiveforge/hivemind-mcp
|
|
375
|
-
console.log('
|
|
1220
|
+
console.log(' npx @hiveforge/hivemind-mcp init - Interactive setup wizard');
|
|
1221
|
+
console.log(' npx @hiveforge/hivemind-mcp validate - Validate configuration');
|
|
1222
|
+
console.log(' npx @hiveforge/hivemind-mcp start - Start the MCP server');
|
|
1223
|
+
console.log(' npx @hiveforge/hivemind-mcp setup-mcp - Generate MCP client config');
|
|
1224
|
+
console.log(' npx @hiveforge/hivemind-mcp fix - Add frontmatter to skipped files');
|
|
1225
|
+
console.log('');
|
|
1226
|
+
console.log('Template commands:');
|
|
1227
|
+
console.log(' npx @hiveforge/hivemind-mcp list-templates - List available templates');
|
|
1228
|
+
console.log(' npx @hiveforge/hivemind-mcp add-template <name|url> - Add a template');
|
|
1229
|
+
console.log(' npx @hiveforge/hivemind-mcp create-template - Create a new template interactively');
|
|
1230
|
+
console.log(' npx @hiveforge/hivemind-mcp validate-template [file] - Validate a template file');
|
|
1231
|
+
console.log(' npx @hiveforge/hivemind-mcp generate-catalog [file] - Generate template catalog JSON');
|
|
1232
|
+
console.log(' npx @hiveforge/hivemind-mcp check-compatibility [name] - Check template compatibility');
|
|
1233
|
+
console.log('');
|
|
1234
|
+
console.log('Advanced:');
|
|
1235
|
+
console.log(' npx @hiveforge/hivemind-mcp --vault <path> - Start with specified vault path');
|
|
1236
|
+
console.log(' npx @hiveforge/hivemind-mcp --vault . - Start with current directory as vault');
|
|
376
1237
|
process.exit(command ? 1 : 0);
|
|
377
1238
|
}
|
|
378
1239
|
}
|