fraim-framework 2.0.74 → 2.0.75

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.
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.installCommand = void 0;
7
+ const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const child_process_1 = require("child_process");
10
+ const https_1 = __importDefault(require("https"));
11
+ const http_1 = __importDefault(require("http"));
12
+ /**
13
+ * fraim install - One-command installer with key or token
14
+ * Usage: fraim install --key=<key> | fraim install --token=<token> [--api-url=<url>]
15
+ *
16
+ * Convenience wrapper over fraim setup: installs fraim-framework globally, fetches key from
17
+ * token if needed (no copy-paste), then runs fraim setup --key. fraim setup alone requires
18
+ * the user to already have fraim installed and know their key.
19
+ */
20
+ exports.installCommand = new commander_1.Command('install')
21
+ .description('Install FRAIM globally and configure with your key (use --key or --token from dashboard)')
22
+ .option('--key <key>', 'FRAIM API key (from dashboard)')
23
+ .option('--token <token>', 'Installer token (one-time, from dashboard download link)')
24
+ .option('--api-url <url>', 'API base URL for token fetch', process.env.FRAIM_API_URL || 'https://fraim.wellnessatwork.me')
25
+ .action(async (opts) => {
26
+ let key = opts.key;
27
+ const token = opts.token;
28
+ const apiUrl = (opts.apiUrl || '').replace(/\/$/, '') || 'https://fraim.wellnessatwork.me';
29
+ if (token && !key) {
30
+ console.log(chalk_1.default.blue('Fetching key from FRAIM...'));
31
+ try {
32
+ key = await fetchInstallerKey(token, apiUrl);
33
+ console.log(chalk_1.default.green('Key retrieved.\n'));
34
+ }
35
+ catch (err) {
36
+ console.error(chalk_1.default.red('Failed to fetch key:'), err.message);
37
+ console.error(chalk_1.default.gray('Token may be expired. Get a new link from the FRAIM site.'));
38
+ process.exit(1);
39
+ }
40
+ }
41
+ if (!key) {
42
+ console.error(chalk_1.default.red('Usage: fraim install --key=<your-key>'));
43
+ console.error(chalk_1.default.gray(' or: fraim install --token=<installer-token> [--api-url=<url>]'));
44
+ console.error(chalk_1.default.gray('\nGet your key at https://fraim.wellnessatwork.me (click Get Started)'));
45
+ process.exit(1);
46
+ }
47
+ console.log(chalk_1.default.blue('Installing fraim-framework...'));
48
+ const installResult = (0, child_process_1.spawnSync)('npm', ['install', '-g', 'fraim-framework'], {
49
+ stdio: 'inherit',
50
+ shell: true
51
+ });
52
+ if (installResult.status !== 0)
53
+ process.exit(installResult.status || 1);
54
+ console.log(chalk_1.default.blue('\nConfiguring FRAIM with your key...'));
55
+ const setupResult = (0, child_process_1.spawnSync)('fraim', ['setup', '--key', key], {
56
+ stdio: 'inherit',
57
+ shell: true
58
+ });
59
+ if (setupResult.status !== 0)
60
+ process.exit(setupResult.status || 1);
61
+ console.log(chalk_1.default.green('\n✅ FRAIM is ready. Run "fraim init-project" in your project directory.'));
62
+ });
63
+ function fetchInstallerKey(token, apiUrl) {
64
+ return new Promise((resolve, reject) => {
65
+ const url = new URL(`${apiUrl}/api/installer-key`);
66
+ url.searchParams.set('token', token);
67
+ const lib = url.protocol === 'https:' ? https_1.default : http_1.default;
68
+ const req = lib.get(url.toString(), (res) => {
69
+ let data = '';
70
+ res.on('data', (chunk) => (data += chunk));
71
+ res.on('end', () => {
72
+ try {
73
+ const json = JSON.parse(data);
74
+ if (json.key)
75
+ resolve(json.key);
76
+ else
77
+ reject(new Error(json.error || 'No key in response'));
78
+ }
79
+ catch {
80
+ reject(new Error(`Invalid response: ${data}`));
81
+ }
82
+ });
83
+ });
84
+ req.on('error', reject);
85
+ });
86
+ }
@@ -88,7 +88,7 @@ const runSync = async (options) => {
88
88
  skipUpdates: true
89
89
  });
90
90
  if (result.success) {
91
- console.log(chalk_1.default.green(`✅ Successfully synced ${result.workflowsSynced} workflows and ${result.scriptsSynced} scripts from local server`));
91
+ console.log(chalk_1.default.green(`✅ Successfully synced ${result.workflowsSynced} workflows, ${result.scriptsSynced} scripts, and ${result.coachingSynced} coaching files from local server`));
92
92
  return;
93
93
  }
94
94
  console.error(chalk_1.default.red(`❌ Local sync failed: ${result.error}`));
@@ -119,7 +119,7 @@ const runSync = async (options) => {
119
119
  }
120
120
  process.exit(1);
121
121
  }
122
- console.log(chalk_1.default.green(`✅ Successfully synced ${result.workflowsSynced} workflows and ${result.scriptsSynced} scripts from remote`));
122
+ console.log(chalk_1.default.green(`✅ Successfully synced ${result.workflowsSynced} workflows, ${result.scriptsSynced} scripts, and ${result.coachingSynced} coaching files from remote`));
123
123
  // Update version in config.json
124
124
  const configPath = path_1.default.join(fraimDir, 'config.json');
125
125
  if (fs_1.default.existsSync(configPath)) {
@@ -14,6 +14,7 @@ const test_mcp_1 = require("./commands/test-mcp");
14
14
  const add_ide_1 = require("./commands/add-ide");
15
15
  const override_1 = require("./commands/override");
16
16
  const list_overridable_1 = require("./commands/list-overridable");
17
+ const install_1 = require("./commands/install");
17
18
  const fs_1 = __importDefault(require("fs"));
18
19
  const path_1 = __importDefault(require("path"));
19
20
  const program = new commander_1.Command();
@@ -50,4 +51,5 @@ program.addCommand(test_mcp_1.testMCPCommand);
50
51
  program.addCommand(add_ide_1.addIDECommand);
51
52
  program.addCommand(override_1.overrideCommand);
52
53
  program.addCommand(list_overridable_1.listOverridableCommand);
54
+ program.addCommand(install_1.installCommand);
53
55
  program.parse(process.argv);
@@ -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
  }
@@ -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
+ }
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "fraim-framework",
3
- "version": "2.0.74",
3
+ "version": "2.0.75",
4
4
  "description": "FRAIM v2: Framework for Rigor-based AI Management - Transform from solo developer to AI manager orchestrating production-ready code with enterprise-grade discipline",
5
5
  "main": "index.js",
6
6
  "bin": {
7
7
  "fraim": "./index.js",
8
+ "fraim-framework": "./index.js",
8
9
  "fraim-mcp": "./bin/fraim-mcp.js"
9
10
  },
10
11
  "scripts": {
@@ -24,11 +25,12 @@
24
25
  "postinstall": "fraim sync --skip-updates || echo 'FRAIM setup skipped.'",
25
26
  "prepublishOnly": "npm run build",
26
27
  "release": "npm version patch && npm publish",
27
- "test-smoke-ci": "tsx --test tests/test-genericization.ts tests/test-cli.ts tests/test-stub-registry.ts",
28
+ "test-smoke-ci": "tsx --test tests/test-genericization.ts tests/test-cli.ts tests/test-stub-registry.ts tests/test-include-resolver.ts",
28
29
  "test-all-ci": "node scripts/test-with-server.js",
29
- "validate:registry": "tsx scripts/verify-registry-paths.ts && npm run validate:workflows && npm run validate:platform-agnostic",
30
+ "validate:registry": "tsx scripts/verify-registry-paths.ts && npm run validate:workflows && npm run validate:skills && npm run validate:platform-agnostic",
30
31
  "validate:workflows": "tsx scripts/validate-workflows.ts",
31
- "validate:platform-agnostic": "tsx scripts/validate-platform-agnostic.ts"
32
+ "validate:platform-agnostic": "tsx scripts/validate-platform-agnostic.ts",
33
+ "validate:skills": "tsx scripts/validate-skills.ts"
32
34
  },
33
35
  "repository": {
34
36
  "type": "git",
@@ -70,6 +72,7 @@
70
72
  "html-to-docx": "^1.8.0",
71
73
  "markdown-it": "^14.1.0",
72
74
  "markdown-it-highlightjs": "^4.2.0",
75
+ "playwright": "^1.58.2",
73
76
  "pptxgenjs": "^4.0.1",
74
77
  "puppeteer": "^24.36.1",
75
78
  "qrcode": "^1.5.4",
@@ -96,11 +99,11 @@
96
99
  "axios": "^1.7.0",
97
100
  "chalk": "4.1.2",
98
101
  "commander": "^14.0.2",
102
+ "cors": "^2.8.5",
99
103
  "dotenv": "^16.4.7",
100
- "prompts": "^2.4.2",
101
- "tree-kill": "^1.2.2",
102
104
  "express": "^5.2.1",
103
- "cors": "^2.8.5",
104
- "mongodb": "^7.0.0"
105
+ "mongodb": "^7.0.0",
106
+ "prompts": "^2.4.2",
107
+ "tree-kill": "^1.2.2"
105
108
  }
106
109
  }