ac-framework 1.8.0 → 1.9.1

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.
@@ -29,6 +29,10 @@ console.log(' acfm memory search Search memories');
29
29
  console.log(' acfm memory save Save memory manually');
30
30
  console.log(' acfm memory stats View memory statistics');
31
31
  console.log();
32
+ console.log(' MCP (Model Context Protocol) Servers');
33
+ console.log(' acfm memory install-mcps Install MCP servers for AI assistants');
34
+ console.log(' acfm memory uninstall-mcps Uninstall MCP servers from AI assistants');
35
+ console.log();
32
36
  console.log(' Tip: Add --json to any command for machine-readable output.');
33
37
  console.log();
34
38
 
@@ -44,3 +48,12 @@ if (isWin) {
44
48
  console.log(' 4. Restart your terminal');
45
49
  console.log();
46
50
  }
51
+
52
+ // Auto-detect and install MCPs for supported assistants
53
+ try {
54
+ const { detectAndInstallMCPs } = require('../src/services/mcp-installer');
55
+ detectAndInstallMCPs();
56
+ } catch (error) {
57
+ // Silently fail if MCP installer is not available yet
58
+ // This allows the framework to work without MCP dependencies during early development
59
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ac-framework",
3
- "version": "1.8.0",
3
+ "version": "1.9.1",
4
4
  "description": "Agentic Coding Framework - Multi-assistant configuration system with OpenSpec workflows",
5
5
  "main": "src/index.js",
6
6
  "exports": {
@@ -42,6 +42,7 @@
42
42
  },
43
43
  "homepage": "https://github.com/b4san/AC-framework#readme",
44
44
  "dependencies": {
45
+ "@modelcontextprotocol/sdk": "^1.27.1",
45
46
  "better-sqlite3": "^11.8.1",
46
47
  "chalk": "^5.3.0",
47
48
  "commander": "^12.1.0",
@@ -49,7 +50,8 @@
49
50
  "inquirer": "^12.3.2",
50
51
  "js-yaml": "^4.1.1",
51
52
  "nanospinner": "^1.2.2",
52
- "tar": "^7.5.7"
53
+ "tar": "^7.5.7",
54
+ "zod": "^4.3.6"
53
55
  },
54
56
  "type": "module",
55
57
  "engines": {
@@ -14,6 +14,9 @@
14
14
  import chalk from 'chalk';
15
15
  import gradient from 'gradient-string';
16
16
  import inquirer from 'inquirer';
17
+ import { existsSync } from 'node:fs';
18
+ import { homedir } from 'node:os';
19
+ import { join } from 'node:path';
17
20
  import { DESCRIPTIONS, ASSISTANT_ICONS, BUNDLED } from '../config/constants.js';
18
21
  import { IDE_MD_MAP, AVAILABLE_MD_FILES, MD_DESCRIPTIONS } from '../config/ide-mapping.js';
19
22
  import { formatFolderName, sleep } from '../utils/helpers.js';
@@ -40,6 +43,102 @@ import {
40
43
 
41
44
  const acGradient = gradient(['#6C5CE7', '#00CEC9', '#0984E3']);
42
45
 
46
+ // ── Persistent Memory Setup ───────────────────────────────────────
47
+
48
+ /**
49
+ * First-time persistent memory setup.
50
+ * Only shown when ~/.acfm/memory.db does not yet exist.
51
+ *
52
+ * The memory store is named "NexusVault" — a unique, memorable name
53
+ * for the embedded knowledge base that persists context across sessions.
54
+ */
55
+ async function setupPersistentMemory() {
56
+ const memoryDbPath = join(homedir(), '.acfm', 'memory.db');
57
+ const alreadyExists = existsSync(memoryDbPath);
58
+
59
+ console.log();
60
+ await animatedSeparator(60);
61
+ console.log();
62
+
63
+ const vaultBadge = chalk.hex('#2D3436').bgHex('#6C5CE7').bold(' NexusVault ');
64
+ console.log(` ${vaultBadge} ${chalk.hex('#B2BEC3').bold('Persistent Memory System')}`);
65
+ console.log();
66
+ console.log(
67
+ chalk.hex('#636E72')(
68
+ ' NexusVault is an embedded SQLite knowledge base that lives at\n' +
69
+ ` ${chalk.hex('#DFE6E9')('~/.acfm/memory.db')} on your machine.\n\n` +
70
+ ' When enabled, your AI assistants will automatically:\n' +
71
+ ` ${chalk.hex('#00CEC9')('◆')} Remember architectural decisions, bugfixes & patterns\n` +
72
+ ` ${chalk.hex('#00CEC9')('◆')} Recall relevant context when you start a new task\n` +
73
+ ` ${chalk.hex('#00CEC9')('◆')} Connect to assistants via MCP (Model Context Protocol)\n\n` +
74
+ ' Your data never leaves your machine — fully offline & private.'
75
+ )
76
+ );
77
+ console.log();
78
+
79
+ const { enableMemory } = await inquirer.prompt([{
80
+ type: 'confirm',
81
+ name: 'enableMemory',
82
+ message: chalk.hex('#B2BEC3')('Enable NexusVault persistent memory?'),
83
+ default: true,
84
+ }]);
85
+
86
+ if (!enableMemory) {
87
+ console.log();
88
+ console.log(chalk.hex('#636E72')(' Skipped. You can enable it later with: acfm memory init'));
89
+ console.log();
90
+ return;
91
+ }
92
+
93
+ console.log();
94
+ console.log(chalk.hex('#B2BEC3')(alreadyExists ? ' Reconnecting NexusVault...' : ' Initializing NexusVault...'));
95
+
96
+ // Init the SQLite database (idempotent — skips if already exists)
97
+ const { initDatabase, isDatabaseInitialized } = await import('../memory/database.js');
98
+ if (!isDatabaseInitialized()) {
99
+ initDatabase();
100
+ }
101
+ console.log(
102
+ chalk.hex('#00CEC9')(' ◆ ') +
103
+ chalk.hex('#DFE6E9')(
104
+ alreadyExists
105
+ ? 'NexusVault database found at ~/.acfm/memory.db'
106
+ : 'NexusVault database created at ~/.acfm/memory.db'
107
+ )
108
+ );
109
+
110
+ // Install MCP server into detected assistants
111
+ console.log();
112
+ console.log(chalk.hex('#B2BEC3')(' Connecting NexusVault to your AI assistants via MCP...'));
113
+ console.log();
114
+
115
+ const { detectAndInstallMCPs, ASSISTANTS, isAssistantInstalled } = await import('../services/mcp-installer.js');
116
+ const { installed, success } = detectAndInstallMCPs();
117
+
118
+ if (installed === 0) {
119
+ console.log(chalk.hex('#636E72')(' No AI assistants detected yet.'));
120
+ console.log(chalk.hex('#636E72')(' Run ' + chalk.hex('#DFE6E9')('acfm memory install-mcps') + ' after installing an assistant.'));
121
+ } else {
122
+ for (const assistant of ASSISTANTS) {
123
+ if (isAssistantInstalled(assistant)) {
124
+ console.log(
125
+ chalk.hex('#00CEC9')(' ◆ ') +
126
+ chalk.hex('#DFE6E9').bold(assistant.name) +
127
+ chalk.hex('#636E72')(` · MCP config → ${assistant.configPath}`)
128
+ );
129
+ }
130
+ }
131
+ console.log();
132
+ const successBadge = chalk.hex('#2D3436').bgHex('#00CEC9').bold(` ${success}/${installed} `);
133
+ console.log(` ${successBadge} ${chalk.hex('#B2BEC3')('assistants connected to NexusVault')}`);
134
+ }
135
+
136
+ console.log();
137
+ console.log(chalk.hex('#6C5CE7').bold(' NexusVault is active.'));
138
+ console.log(chalk.hex('#636E72')(' Use acfm memory --help to manage your knowledge base.'));
139
+ console.log();
140
+ }
141
+
43
142
  // ── Helpers ──────────────────────────────────────────────────────
44
143
 
45
144
  function buildChoices(folders) {
@@ -187,7 +286,7 @@ export async function initCommand(options = {}) {
187
286
 
188
287
  // Dynamic step counting: +1 step when downloading from GitHub
189
288
  const stepOffset = useLatest ? 1 : 0;
190
- const totalSteps = 4 + stepOffset;
289
+ const totalSteps = 5 + stepOffset;
191
290
 
192
291
  // Framework source: bundled by default, overridden by --latest
193
292
  let frameworkPath = FRAMEWORK_PATH;
@@ -423,6 +522,7 @@ export async function initCommand(options = {}) {
423
522
  // ── Final result ──────────────────────────────────────────────
424
523
  if (errors.length === 0) {
425
524
  await celebrateSuccess(installed, targetDir);
525
+ await setupPersistentMemory();
426
526
  } else {
427
527
  await showFailureSummary(installed, errors);
428
528
  }
@@ -706,61 +706,122 @@ export function memoryCommand() {
706
706
 
707
707
  // ─── acfm memory session ───────────────────────────────────────────────────
708
708
  const sessionCmd = new Command('session')
709
- .description('Gestión de sesiones de memoria');
709
+ .description('Gestión de sesiones de memoria');
710
710
 
711
711
  sessionCmd
712
- .command('start')
713
- .description('Inicia nueva sesión')
714
- .option('-p, --project <path>', 'Proyecto', process.cwd())
715
- .option('-c, --change <name>', 'Change')
716
- .option('--json', 'Output as JSON')
717
- .action((opts) => {
718
- try {
719
- ensureInitialized();
720
-
721
- const sessionId = startSession(opts.project, opts.change);
722
-
723
- output({ sessionId, project: opts.project, change: opts.change }, opts.json);
724
-
725
- if (!opts.json) {
726
- console.log(chalk.green('✓ Session started'));
727
- console.log(chalk.dim(` ID: ${sessionId}`));
728
- }
729
- } catch (err) {
730
- output({ error: err.message }, opts.json);
731
- if (!opts.json) console.error(chalk.red(`Error: ${err.message}`));
732
- process.exit(1);
712
+ .command('start')
713
+ .description('Inicia nueva sesión')
714
+ .option('-p, --project <path>', 'Proyecto', process.cwd())
715
+ .option('-c, --change <name>', 'Change')
716
+ .option('--json', 'Output as JSON')
717
+ .action((opts) => {
718
+ try {
719
+ ensureInitialized();
720
+
721
+ const sessionId = startSession(opts.project, opts.change);
722
+
723
+ output({ sessionId, project: opts.project, change: opts.change }, opts.json);
724
+
725
+ if (!opts.json) {
726
+ console.log(chalk.green('✓ Session started'));
727
+ console.log(chalk.dim(` ID: ${sessionId}`));
733
728
  }
734
- });
729
+ } catch (err) {
730
+ output({ error: err.message }, opts.json);
731
+ if (!opts.json) console.error(chalk.red(`Error: ${err.message}`));
732
+ process.exit(1);
733
+ }
734
+ });
735
735
 
736
736
  sessionCmd
737
- .command('end <sessionId>')
738
- .description('Finaliza sesión')
739
- .option('-s, --summary <text>', 'Resumen de la sesión')
740
- .option('--json', 'Output as JSON')
741
- .action((sessionId, opts) => {
742
- try {
743
- ensureInitialized();
744
-
745
- endSession(sessionId, opts.summary);
746
-
747
- output({ ended: true, sessionId }, opts.json);
748
-
749
- if (!opts.json) {
750
- console.log(chalk.green('✓ Session ended'));
751
- if (opts.summary) {
752
- console.log(chalk.dim('Summary saved as memory'));
753
- }
737
+ .command('end <sessionId>')
738
+ .description('Finaliza sesión')
739
+ .option('-s, --summary <text>', 'Resumen de la sesión')
740
+ .option('--json', 'Output as JSON')
741
+ .action((sessionId, opts) => {
742
+ try {
743
+ ensureInitialized();
744
+
745
+ endSession(sessionId, opts.summary);
746
+
747
+ output({ ended: true, sessionId }, opts.json);
748
+
749
+ if (!opts.json) {
750
+ console.log(chalk.green('✓ Session ended'));
751
+ if (opts.summary) {
752
+ console.log(chalk.dim('Summary saved as memory'));
754
753
  }
755
- } catch (err) {
756
- output({ error: err.message }, opts.json);
757
- if (!opts.json) console.error(chalk.red(`Error: ${err.message}`));
758
- process.exit(1);
759
754
  }
760
- });
755
+ } catch (err) {
756
+ output({ error: err.message }, opts.json);
757
+ if (!opts.json) console.error(chalk.red(`Error: ${err.message}`));
758
+ process.exit(1);
759
+ }
760
+ });
761
761
 
762
762
  memory.addCommand(sessionCmd);
763
-
763
+
764
+ // ─── acfm memory install-mcps ───────────────────────────────────────────────
765
+ memory
766
+ .command('install-mcps')
767
+ .description('Instala servidores MCP para asistentes de IA detectados')
768
+ .option('--all', 'Instalar para todos (sin requerir detección)', false)
769
+ .option('--json', 'Output as JSON')
770
+ .action(async (opts) => {
771
+ try {
772
+ const { detectAndInstallMCPs, installAllMCPs, ASSISTANTS, isAssistantInstalled } = await import('../services/mcp-installer.js');
773
+
774
+ const result = opts.all ? installAllMCPs() : detectAndInstallMCPs();
775
+
776
+ output({ total: result.total ?? result.installed, success: result.success }, opts.json);
777
+
778
+ if (!opts.json) {
779
+ if (!opts.all) {
780
+ if (result.installed === 0) {
781
+ console.log(chalk.yellow('No AI assistants detected.'));
782
+ console.log(chalk.dim('Use --all to install for all supported assistants.'));
783
+ return;
784
+ }
785
+ for (const assistant of ASSISTANTS) {
786
+ if (isAssistantInstalled(assistant)) {
787
+ console.log(
788
+ chalk.cyan('◆ ') + chalk.bold(assistant.name) +
789
+ chalk.dim(` → ${assistant.configPath}`)
790
+ );
791
+ }
792
+ }
793
+ }
794
+ console.log(chalk.green(`\n✓ MCP servers installed (${result.success}/${result.total ?? result.installed})`));
795
+ }
796
+ } catch (err) {
797
+ output({ error: err.message }, opts.json);
798
+ if (!opts.json) console.error(chalk.red(`Error: ${err.message}`));
799
+ process.exit(1);
800
+ }
801
+ });
802
+
803
+ // ─── acfm memory uninstall-mcps ───────────────────────────────────────────
804
+ memory
805
+ .command('uninstall-mcps')
806
+ .description('Desinstala servidores MCP de asistentes de IA')
807
+ .option('--json', 'Output as JSON')
808
+ .action(async (opts) => {
809
+ try {
810
+ const { uninstallAllMCPs } = await import('../services/mcp-installer.js');
811
+ const result = uninstallAllMCPs();
812
+
813
+ output({ success: result.success }, opts.json);
814
+
815
+ if (!opts.json) {
816
+ console.log(chalk.green(`✓ MCP servers uninstalled (${result.success})`));
817
+ }
818
+ } catch (err) {
819
+ output({ error: err.message }, opts.json);
820
+ if (!opts.json) console.error(chalk.red(`Error: ${err.message}`));
821
+ process.exit(1);
822
+ }
823
+ });
824
+
764
825
  return memory;
765
826
  }
766
827