fraim-framework 2.0.74 → 2.0.76

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,6 +1,87 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.generateMCPConfig = exports.generateWindsurfMCPServers = exports.generateVSCodeMCPServers = exports.generateCodexMCPServers = exports.generateKiroMCPServers = exports.generateClaudeMCPServers = exports.generateStandardMCPServers = void 0;
3
+ exports.generateMCPConfig = exports.generateWindsurfMCPServers = exports.generateVSCodeMCPServers = exports.generateCodexMCPServers = exports.generateKiroMCPServers = exports.generateClaudeMCPServers = exports.generateStandardMCPServers = exports.mergeTomlMCPServers = exports.extractTomlMcpServerBlock = void 0;
4
+ const escapeTomlString = (value) => value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
5
+ const findTomlServerBlockRange = (content, server) => {
6
+ const lines = content.split(/\r?\n/);
7
+ const serverSection = `mcp_servers.${server}`;
8
+ const serverHeader = `[${serverSection}]`;
9
+ const start = lines.findIndex(line => line.trim() === serverHeader);
10
+ if (start === -1) {
11
+ return null;
12
+ }
13
+ let end = lines.length;
14
+ for (let i = start + 1; i < lines.length; i++) {
15
+ const sectionMatch = lines[i].trim().match(/^\[([^\]]+)\]$/);
16
+ if (!sectionMatch)
17
+ continue;
18
+ const sectionName = sectionMatch[1];
19
+ if (sectionName === serverSection || sectionName.startsWith(`${serverSection}.`)) {
20
+ continue;
21
+ }
22
+ end = i;
23
+ break;
24
+ }
25
+ return { start, end };
26
+ };
27
+ const extractTomlMcpServerBlock = (content, server) => {
28
+ const range = findTomlServerBlockRange(content, server);
29
+ if (!range)
30
+ return null;
31
+ const lines = content.split(/\r?\n/);
32
+ return lines.slice(range.start, range.end).join('\n').trim();
33
+ };
34
+ exports.extractTomlMcpServerBlock = extractTomlMcpServerBlock;
35
+ const removeTomlMcpServerBlock = (content, server) => {
36
+ const range = findTomlServerBlockRange(content, server);
37
+ if (!range)
38
+ return content;
39
+ const lines = content.split(/\r?\n/);
40
+ const updated = [...lines.slice(0, range.start), ...lines.slice(range.end)].join('\n');
41
+ return updated.replace(/\n{3,}/g, '\n\n').trimEnd();
42
+ };
43
+ const appendTomlBlock = (content, block) => {
44
+ const trimmedContent = content.trimEnd();
45
+ const trimmedBlock = block.trim();
46
+ if (!trimmedBlock)
47
+ return trimmedContent;
48
+ if (!trimmedContent)
49
+ return `${trimmedBlock}\n`;
50
+ return `${trimmedContent}\n\n${trimmedBlock}\n`;
51
+ };
52
+ const mergeTomlMCPServers = (existingContent, generatedContent, servers) => {
53
+ let merged = existingContent || '';
54
+ const addedServers = [];
55
+ const replacedServers = [];
56
+ const skippedServers = [];
57
+ for (const server of servers) {
58
+ const newBlock = (0, exports.extractTomlMcpServerBlock)(generatedContent, server);
59
+ if (!newBlock) {
60
+ skippedServers.push(server);
61
+ continue;
62
+ }
63
+ const existingBlock = (0, exports.extractTomlMcpServerBlock)(merged, server);
64
+ if (existingBlock && existingBlock.trim() === newBlock.trim()) {
65
+ skippedServers.push(server);
66
+ continue;
67
+ }
68
+ if (existingBlock) {
69
+ merged = removeTomlMcpServerBlock(merged, server);
70
+ merged = appendTomlBlock(merged, newBlock);
71
+ replacedServers.push(server);
72
+ continue;
73
+ }
74
+ merged = appendTomlBlock(merged, newBlock);
75
+ addedServers.push(server);
76
+ }
77
+ return {
78
+ content: merged.trimEnd() + '\n',
79
+ addedServers,
80
+ replacedServers,
81
+ skippedServers
82
+ };
83
+ };
84
+ exports.mergeTomlMCPServers = mergeTomlMCPServers;
4
85
  const generateStandardMCPServers = (fraimKey, githubToken) => {
5
86
  const servers = {
6
87
  git: {
@@ -93,17 +174,19 @@ const generateKiroMCPServers = (fraimKey, githubToken) => {
93
174
  };
94
175
  exports.generateKiroMCPServers = generateKiroMCPServers;
95
176
  const generateCodexMCPServers = (fraimKey, githubToken) => {
96
- let config = `
97
- [mcp_servers.git]
98
- command = "npx"
177
+ const escapedFraimKey = escapeTomlString(fraimKey);
178
+ const escapedGithubToken = escapeTomlString(githubToken);
179
+ let config = `
180
+ [mcp_servers.git]
181
+ command = "npx"
99
182
  args = ["-y", "@cyanheads/git-mcp-server"]
100
183
  `;
101
184
  // Only add GitHub server if token is provided
102
185
  if (githubToken) {
103
- config += `
104
- [mcp_servers.github]
105
- url = "https://api.githubcopilot.com/mcp/"
106
- bearer_token_env_var = "GIT_TOKEN"
186
+ config += `
187
+ [mcp_servers.github]
188
+ url = "https://api.githubcopilot.com/mcp/"
189
+ http_headers = { Authorization = "Bearer ${escapedGithubToken}" }
107
190
  `;
108
191
  }
109
192
  config += `
@@ -114,9 +197,9 @@ args = ["-y", "@playwright/mcp"]
114
197
  [mcp_servers.fraim]
115
198
  command = "fraim-mcp"
116
199
 
117
- [mcp_servers.fraim.env]
118
- FRAIM_API_KEY = "${fraimKey}"
119
- FRAIM_REMOTE_URL = "https://fraim.wellnessatwork.me"
200
+ [mcp_servers.fraim.env]
201
+ FRAIM_API_KEY = "${escapedFraimKey}"
202
+ FRAIM_REMOTE_URL = "https://fraim.wellnessatwork.me"
120
203
  `;
121
204
  return config;
122
205
  };
@@ -28,6 +28,7 @@ async function syncFromRemote(options) {
28
28
  success: false,
29
29
  workflowsSynced: 0,
30
30
  scriptsSynced: 0,
31
+ coachingSynced: 0,
31
32
  error: 'FRAIM_API_KEY not set'
32
33
  };
33
34
  }
@@ -48,6 +49,7 @@ async function syncFromRemote(options) {
48
49
  success: false,
49
50
  workflowsSynced: 0,
50
51
  scriptsSynced: 0,
52
+ coachingSynced: 0,
51
53
  error: 'No files received'
52
54
  };
53
55
  }
@@ -88,11 +90,28 @@ async function syncFromRemote(options) {
88
90
  (0, fs_1.writeFileSync)(filePath, file.content, 'utf8');
89
91
  console.log(chalk_1.default.gray(` + ${file.path}`));
90
92
  }
91
- console.log(chalk_1.default.green(`\n✅ Synced ${workflowFiles.length} workflows and ${scriptFiles.length} scripts from remote`));
93
+ // Sync coaching files to .fraim/coaching-moments/
94
+ const coachingFiles = files.filter(f => f.type === 'coaching');
95
+ const coachingDir = (0, path_1.join)(options.projectRoot, '.fraim', 'coaching-moments');
96
+ if (!(0, fs_1.existsSync)(coachingDir)) {
97
+ (0, fs_1.mkdirSync)(coachingDir, { recursive: true });
98
+ }
99
+ cleanDirectory(coachingDir);
100
+ for (const file of coachingFiles) {
101
+ const filePath = (0, path_1.join)(coachingDir, file.path);
102
+ const fileDir = (0, path_1.dirname)(filePath);
103
+ if (!(0, fs_1.existsSync)(fileDir)) {
104
+ (0, fs_1.mkdirSync)(fileDir, { recursive: true });
105
+ }
106
+ (0, fs_1.writeFileSync)(filePath, file.content, 'utf8');
107
+ console.log(chalk_1.default.gray(` + coaching-moments/${file.path}`));
108
+ }
109
+ console.log(chalk_1.default.green(`\n✅ Synced ${workflowFiles.length} workflows, ${scriptFiles.length} scripts, and ${coachingFiles.length} coaching files from remote`));
92
110
  return {
93
111
  success: true,
94
112
  workflowsSynced: workflowFiles.length,
95
- scriptsSynced: scriptFiles.length
113
+ scriptsSynced: scriptFiles.length,
114
+ coachingSynced: coachingFiles.length
96
115
  };
97
116
  }
98
117
  catch (error) {
@@ -101,6 +120,7 @@ async function syncFromRemote(options) {
101
120
  success: false,
102
121
  workflowsSynced: 0,
103
122
  scriptsSynced: 0,
123
+ coachingSynced: 0,
104
124
  error: error.message
105
125
  };
106
126
  }
@@ -46,22 +46,16 @@ function loadFraimConfig() {
46
46
  ...(config.customizations || {})
47
47
  }
48
48
  };
49
- // Add optional fields only if they exist in the config
50
- if (config.persona) {
51
- mergedConfig.persona = config.persona;
52
- }
53
- if (config.marketing) {
54
- mergedConfig.marketing = config.marketing;
55
- }
56
- if (config.database) {
57
- mergedConfig.database = config.database;
58
- }
49
+ // Add optional workflow-driven fields only if they exist in the config
59
50
  if (config.compliance) {
60
51
  mergedConfig.compliance = config.compliance;
61
52
  }
62
53
  if (config.learning) {
63
54
  mergedConfig.learning = config.learning;
64
55
  }
56
+ if (config.competitors && typeof config.competitors === 'object') {
57
+ mergedConfig.competitors = config.competitors;
58
+ }
65
59
  console.log(`📋 Loaded FRAIM config from .fraim/config.json (version ${mergedConfig.version})`);
66
60
  // Warn about deprecated git config
67
61
  if (config.git && !config.repository) {
@@ -4,7 +4,7 @@
4
4
  * TypeScript types for .fraim/config.json
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.MINIMAL_FRAIM_CONFIG = exports.DEFAULT_FRAIM_CONFIG = void 0;
7
+ exports.DEFAULT_FRAIM_CONFIG = void 0;
8
8
  /**
9
9
  * Default configuration values
10
10
  */
@@ -19,22 +19,5 @@ exports.DEFAULT_FRAIM_CONFIG = {
19
19
  name: '',
20
20
  defaultBranch: 'main'
21
21
  },
22
- customizations: {
23
- workflowsPath: '.fraim/workflows'
24
- }
25
- };
26
- /**
27
- * Minimal config template for new projects
28
- */
29
- exports.MINIMAL_FRAIM_CONFIG = {
30
- version: '2.0.47',
31
- project: {
32
- name: 'My Project'
33
- },
34
- repository: {
35
- provider: 'github',
36
- owner: 'your-username',
37
- name: 'your-repo',
38
- defaultBranch: 'main'
39
- }
22
+ customizations: {}
40
23
  };
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ /**
3
+ * Resolve {{include:path}} directives in registry content.
4
+ *
5
+ * Used by: MCP service (get_fraim_file, get_fraim_workflow), AI Mentor (phase instructions),
6
+ * and any other server-side content that needs includes resolved.
7
+ *
8
+ * - Resolves recursively (e.g. skills can include other skills)
9
+ * - MAX_PASSES prevents infinite loops from circular includes
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.MAX_INCLUDE_PASSES = void 0;
13
+ exports.resolveIncludesWithIndex = resolveIncludesWithIndex;
14
+ const fs_1 = require("fs");
15
+ /** Maximum resolution passes to prevent infinite loops from circular includes */
16
+ exports.MAX_INCLUDE_PASSES = 10;
17
+ /**
18
+ * Resolve {{include:path}} directives in content.
19
+ * Looks up referenced files in the fileIndex and inlines their content.
20
+ *
21
+ * @param content - Raw content that may contain {{include:path}} directives
22
+ * @param fileIndex - Map of path -> { fullPath, ... } for registry files
23
+ * @returns Content with all resolvable includes inlined (recursive, up to MAX_PASSES)
24
+ */
25
+ function resolveIncludesWithIndex(content, fileIndex) {
26
+ let result = content;
27
+ let pass = 0;
28
+ while (result.includes('{{include:') && pass < exports.MAX_INCLUDE_PASSES) {
29
+ result = result.replace(/\{\{include:([^}]+)\}\}/g, (match, filePath) => {
30
+ const trimmedPath = filePath.trim();
31
+ const fileEntry = fileIndex.get(trimmedPath);
32
+ if (fileEntry?.fullPath) {
33
+ try {
34
+ return (0, fs_1.readFileSync)(fileEntry.fullPath, 'utf-8');
35
+ }
36
+ catch (error) {
37
+ console.error(`❌ Failed to read included file: ${trimmedPath}`, error);
38
+ return match;
39
+ }
40
+ }
41
+ console.warn(`⚠️ Include file not found in fileIndex: ${trimmedPath}`);
42
+ return match;
43
+ });
44
+ pass++;
45
+ }
46
+ return result;
47
+ }