fraim-framework 2.0.83 → 2.0.85

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 CHANGED
@@ -228,11 +228,24 @@ R - Retrospectives: Continuous learning from experience
228
228
  **Why Git Bash on Windows?** All FRAIM scripts use Unix-style paths and Bash commands. Git Bash ensures consistent behavior across platforms.
229
229
 
230
230
  ### **Install & Initialize**
231
+
232
+ **Recommended: Use npx (no installation needed)**
233
+ ```bash
234
+ npx fraim-framework@latest setup --key=<your-fraim-key>
235
+
236
+ # Optional: Create alias for convenience
237
+ echo 'alias fraim="npx fraim-framework"' >> ~/.bashrc
238
+ source ~/.bashrc
239
+ ```
240
+
241
+ **Alternative: Global install**
231
242
  ```bash
232
243
  npm install -g fraim-framework
233
- fraim setup # Complete setup with IDE configuration
244
+ fraim setup --key=<your-fraim-key>
234
245
  ```
235
246
 
247
+ > **šŸ’” Why npx?** Works with any Node version (16+), no conflicts when switching Node versions, always uses correct dependencies, and identical functionality to global install. Perfect for users with nvm, volta, or multiple Node versions.
248
+
236
249
  The setup command supports three modes:
237
250
 
238
251
  **Conversational Mode**: AI workflows only, no platform integration required
@@ -282,7 +295,7 @@ fraim setup --jira # Add Jira integration
282
295
  fraim init-project # Initialize FRAIM in current project
283
296
 
284
297
  # Testing and validation
285
- fraim test-mcp # Test MCP server connections
298
+ fraim doctor --test-mcp # Test MCP server connections
286
299
  fraim doctor # Diagnose configuration issues
287
300
 
288
301
  # Sync and maintenance
@@ -325,7 +338,7 @@ FRAIM uses the official Model Context Protocol (MCP) server for Jira integration
325
338
  **Troubleshooting**:
326
339
  ```bash
327
340
  # Test Jira MCP connection
328
- fraim test-mcp
341
+ fraim doctor --test-mcp
329
342
 
330
343
  # Reconfigure Jira integration
331
344
  fraim setup --jira
package/bin/fraim-mcp.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  /**
4
4
  * FRAIM MCP Server - Smart Entry Point
@@ -418,7 +418,7 @@ const runAddIDE = async (options) => {
418
418
  console.log(chalk_1.default.blue('\nšŸ”„ Next steps:'));
419
419
  console.log(chalk_1.default.cyan(' 1. Restart your configured IDEs'));
420
420
  console.log(chalk_1.default.cyan(' 2. Ask your AI agent: "list fraim workflows"'));
421
- console.log(chalk_1.default.blue('\nšŸ’” Use "fraim test-mcp" to verify the configuration.'));
421
+ console.log(chalk_1.default.blue('\nšŸ’” Use "fraim doctor --test-mcp" to verify the configuration.'));
422
422
  }
423
423
  };
424
424
  exports.runAddIDE = runAddIDE;
@@ -159,7 +159,7 @@ const runInitProject = async () => {
159
159
  fs_1.default.writeFileSync(configPath, JSON.stringify(config, null, 2));
160
160
  console.log(chalk_1.default.green('Created .fraim/config.json'));
161
161
  }
162
- ['workflows'].forEach((dir) => {
162
+ ['workflows', 'ai-employee/jobs', 'ai-employee/skills', 'ai-manager/jobs', 'personalized-employee'].forEach((dir) => {
163
163
  const dirPath = path_1.default.join(fraimDir, dir);
164
164
  if (!fs_1.default.existsSync(dirPath)) {
165
165
  fs_1.default.mkdirSync(dirPath, { recursive: true });
@@ -16,7 +16,8 @@ exports.listOverridableCommand = new commander_1.Command('list-overridable')
16
16
  const projectRoot = process.cwd();
17
17
  const fraimDir = path_1.default.join(projectRoot, '.fraim');
18
18
  const configPath = path_1.default.join(fraimDir, 'config.json');
19
- const overridesDir = path_1.default.join(fraimDir, 'overrides');
19
+ const personalizedDir = path_1.default.join(fraimDir, 'personalized-employee');
20
+ const legacyOverridesDir = path_1.default.join(fraimDir, 'overrides');
20
21
  // Validate .fraim directory exists
21
22
  if (!fs_1.default.existsSync(fraimDir)) {
22
23
  console.log(chalk_1.default.red('āŒ .fraim/ directory not found. Run "fraim setup" or "fraim init-project" first.'));
@@ -42,20 +43,23 @@ exports.listOverridableCommand = new commander_1.Command('list-overridable')
42
43
  console.log(chalk_1.default.blue('šŸ“‹ Overridable FRAIM Registry Paths:\n'));
43
44
  // Get list of existing overrides
44
45
  const existingOverrides = new Set();
45
- if (fs_1.default.existsSync(overridesDir)) {
46
- const scanDir = (dir, base = '') => {
47
- const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
48
- for (const entry of entries) {
49
- const relativePath = path_1.default.join(base, entry.name);
50
- if (entry.isDirectory()) {
51
- scanDir(path_1.default.join(dir, entry.name), relativePath);
52
- }
53
- else {
54
- existingOverrides.add(relativePath.replace(/\\/g, '/'));
55
- }
46
+ const scanDir = (dir, base = '') => {
47
+ const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
48
+ for (const entry of entries) {
49
+ const relativePath = path_1.default.join(base, entry.name);
50
+ if (entry.isDirectory()) {
51
+ scanDir(path_1.default.join(dir, entry.name), relativePath);
56
52
  }
57
- };
58
- scanDir(overridesDir);
53
+ else {
54
+ existingOverrides.add(relativePath.replace(/\\/g, '/'));
55
+ }
56
+ }
57
+ };
58
+ if (fs_1.default.existsSync(personalizedDir)) {
59
+ scanDir(personalizedDir);
60
+ }
61
+ if (fs_1.default.existsSync(legacyOverridesDir)) {
62
+ scanDir(legacyOverridesDir);
59
63
  }
60
64
  // Handle --rules flag
61
65
  if (options.rules) {
@@ -197,6 +201,6 @@ exports.listOverridableCommand = new commander_1.Command('list-overridable')
197
201
  console.log(chalk_1.default.gray(' • Use "fraim override <path> --copy" to copy current content'));
198
202
  console.log(chalk_1.default.gray(' • Use --job-category <category> to see category-specific items'));
199
203
  console.log(chalk_1.default.gray(' • Use --rules to see all overridable rules'));
200
- console.log(chalk_1.default.gray(' • Overrides are stored in .fraim/overrides/'));
204
+ console.log(chalk_1.default.gray(' • Overrides are stored in .fraim/personalized-employee/'));
201
205
  }
202
206
  });
@@ -39,8 +39,8 @@ exports.overrideCommand = new commander_1.Command('override')
39
39
  console.log(chalk_1.default.red('āŒ Must specify either --inherit or --copy.'));
40
40
  process.exit(1);
41
41
  }
42
- // Create overrides directory structure
43
- const overridePath = path_1.default.join(fraimDir, 'overrides', registryPath);
42
+ // Create personalized override directory structure
43
+ const overridePath = path_1.default.join(fraimDir, 'personalized-employee', registryPath);
44
44
  const overrideDir = path_1.default.dirname(overridePath);
45
45
  if (!fs_1.default.existsSync(overrideDir)) {
46
46
  fs_1.default.mkdirSync(overrideDir, { recursive: true });
@@ -124,6 +124,13 @@ exports.overrideCommand = new commander_1.Command('override')
124
124
  toolName = 'get_fraim_workflow';
125
125
  toolArgs = { workflow: workflowName };
126
126
  }
127
+ else if (registryPath.startsWith('jobs/')) {
128
+ // e.g., "jobs/product-building/feature-specification.md" -> "feature-specification"
129
+ const parts = registryPath.split('/');
130
+ const jobName = parts[parts.length - 1].replace('.md', '');
131
+ toolName = 'get_fraim_job';
132
+ toolArgs = { job: jobName };
133
+ }
127
134
  else {
128
135
  toolName = 'get_fraim_file';
129
136
  toolArgs = { path: registryPath };
@@ -240,16 +240,20 @@ const runSetup = async (options) => {
240
240
  console.log(chalk_1.default.blue('šŸ“ Updating existing FRAIM configuration...\n'));
241
241
  try {
242
242
  const existingConfig = JSON.parse(fs_1.default.readFileSync(globalConfigPath, 'utf8'));
243
- fraimKey = existingConfig.apiKey;
243
+ // Allow updating FRAIM key even without provider changes
244
+ fraimKey = options.key || existingConfig.apiKey;
244
245
  // Now we can parse options with the FRAIM key
245
246
  const parsed = await parseLegacyOptions(options, fraimKey);
246
247
  requestedProviders = parsed.requestedProviders;
247
248
  providedTokens = parsed.providedTokens;
248
249
  providedConfigs = parsed.providedConfigs;
249
- // Only proceed with update if providers were requested
250
- if (requestedProviders.length === 0) {
251
- console.log(chalk_1.default.yellow('āš ļø No providers specified for update.'));
252
- console.log(chalk_1.default.gray('Use --github, --gitlab, --jira, etc. to add providers.'));
250
+ // Check if this is just a key update or a provider update
251
+ const isKeyUpdate = options.key && options.key !== existingConfig.apiKey;
252
+ const isProviderUpdate = requestedProviders.length > 0;
253
+ // Only proceed if there's something to update
254
+ if (!isKeyUpdate && !isProviderUpdate) {
255
+ console.log(chalk_1.default.yellow('āš ļø No changes specified.'));
256
+ console.log(chalk_1.default.gray('Use --key to update FRAIM key, or --github, --gitlab, etc. to add providers.'));
253
257
  return;
254
258
  }
255
259
  mode = existingConfig.mode || 'integrated';
@@ -377,8 +381,9 @@ const runSetup = async (options) => {
377
381
  // Save global configuration
378
382
  console.log(chalk_1.default.blue('šŸ’¾ Saving global configuration...'));
379
383
  saveGlobalConfig(fraimKey, mode, tokens, configs);
380
- // Configure MCP servers (only on initial setup)
384
+ // Configure MCP servers
381
385
  if (!isUpdate) {
386
+ // Initial setup - configure all detected IDEs
382
387
  console.log(chalk_1.default.blue('\nšŸ”Œ Configuring MCP servers...'));
383
388
  // Convert to legacy format for MCP config generator
384
389
  const mcpTokens = {};
@@ -401,7 +406,39 @@ const runSetup = async (options) => {
401
406
  console.log(chalk_1.default.yellow('āš ļø MCP configuration encountered issues'));
402
407
  console.log(chalk_1.default.gray(' You can configure MCP manually or run setup again later\n'));
403
408
  }
404
- // Auto-run project init if we're in a git repo
409
+ }
410
+ else {
411
+ // Update existing setup - refresh all IDE MCP configs with new keys
412
+ console.log(chalk_1.default.blue('\nšŸ”„ Updating IDE MCP configurations...'));
413
+ try {
414
+ const { detectInstalledIDEs } = await Promise.resolve().then(() => __importStar(require('../setup/ide-detector')));
415
+ const installedIDEs = detectInstalledIDEs();
416
+ if (installedIDEs.length === 0) {
417
+ console.log(chalk_1.default.gray(' No IDE configurations found to update'));
418
+ }
419
+ else {
420
+ // Convert to legacy format for MCP config generator
421
+ const mcpTokens = {};
422
+ Object.entries(tokens).forEach(([id, token]) => {
423
+ mcpTokens[id] = token;
424
+ });
425
+ // Build providerConfigs map from configs
426
+ const providerConfigsMap = {};
427
+ Object.entries(configs).forEach(([providerId, config]) => {
428
+ providerConfigsMap[providerId] = config;
429
+ });
430
+ const ideNames = installedIDEs.map(ide => ide.name);
431
+ await (0, auto_mcp_setup_1.autoConfigureMCP)(fraimKey, mcpTokens, ideNames, providerConfigsMap);
432
+ console.log(chalk_1.default.green(`āœ… Updated MCP configs for: ${ideNames.join(', ')}`));
433
+ }
434
+ }
435
+ catch (e) {
436
+ console.log(chalk_1.default.yellow('āš ļø Failed to update IDE MCP configurations'));
437
+ console.log(chalk_1.default.gray(' You can update them manually with: fraim add-ide <ide-name>\n'));
438
+ }
439
+ }
440
+ // Auto-run project init if we're in a git repo (only on initial setup)
441
+ if (!isUpdate) {
405
442
  if ((0, platform_detection_1.isGitRepository)()) {
406
443
  console.log(chalk_1.default.blue('\nšŸ“ Git repository detected — initializing project...'));
407
444
  await (0, init_project_1.runInitProject)();
@@ -63,6 +63,24 @@ function loadUserApiKey() {
63
63
  return undefined;
64
64
  }
65
65
  }
66
+ function updateVersionInConfig(fraimDir) {
67
+ const configPath = path_1.default.join(fraimDir, 'config.json');
68
+ if (!fs_1.default.existsSync(configPath)) {
69
+ return;
70
+ }
71
+ try {
72
+ const currentConfig = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
73
+ const newVersion = (0, version_utils_1.getFraimVersion)();
74
+ if (currentConfig.version !== newVersion) {
75
+ currentConfig.version = newVersion;
76
+ fs_1.default.writeFileSync(configPath, JSON.stringify(currentConfig, null, 2));
77
+ console.log(chalk_1.default.green(`āœ… Updated FRAIM version to ${newVersion} in config.`));
78
+ }
79
+ }
80
+ catch (e) {
81
+ console.warn(chalk_1.default.yellow('āš ļø Could not update version in config.json.'));
82
+ }
83
+ }
66
84
  const runSync = async (options) => {
67
85
  const projectRoot = process.cwd();
68
86
  const config = (0, config_loader_1.loadFraimConfig)();
@@ -88,7 +106,8 @@ const runSync = async (options) => {
88
106
  skipUpdates: true
89
107
  });
90
108
  if (result.success) {
91
- console.log(chalk_1.default.green(`āœ… Successfully synced ${result.workflowsSynced} workflows, ${result.scriptsSynced} scripts, ${result.coachingSynced} coaching files, and ${result.docsSynced} docs from local server`));
109
+ console.log(chalk_1.default.green(`āœ… Successfully synced ${result.workflowsSynced} workflows, ${result.employeeJobsSynced} ai-employee jobs, ${result.managerJobsSynced} ai-manager jobs, ${result.skillsSynced} skills, ${result.rulesSynced} rules, ${result.scriptsSynced} scripts, and ${result.docsSynced} docs from local server`));
110
+ updateVersionInConfig(fraimDir);
92
111
  return;
93
112
  }
94
113
  console.error(chalk_1.default.red(`āŒ Local sync failed: ${result.error}`));
@@ -96,12 +115,18 @@ const runSync = async (options) => {
96
115
  process.exit(1);
97
116
  }
98
117
  // Path 2: Remote sync with API key
99
- const apiKey = loadUserApiKey() || config.apiKey || process.env.FRAIM_API_KEY;
118
+ let apiKey = loadUserApiKey() || config.apiKey || process.env.FRAIM_API_KEY;
100
119
  if (!apiKey) {
101
- console.error(chalk_1.default.red('āŒ No API key configured. Cannot sync.'));
102
- console.error(chalk_1.default.yellow('šŸ’” Set FRAIM_API_KEY in your environment, or add apiKey to ~/.fraim/config.json or .fraim/config.json'));
103
- console.error(chalk_1.default.yellow('šŸ’” Or use --local to sync from a locally running FRAIM server.'));
104
- process.exit(1);
120
+ if (process.env.TEST_MODE === 'true') {
121
+ console.log(chalk_1.default.yellow('āš ļø TEST_MODE: No API key configured. Using test placeholder key.'));
122
+ apiKey = 'test-mode-key';
123
+ }
124
+ else {
125
+ console.error(chalk_1.default.red('āŒ No API key configured. Cannot sync.'));
126
+ console.error(chalk_1.default.yellow('šŸ’” Set FRAIM_API_KEY in your environment, or add apiKey to ~/.fraim/config.json or .fraim/config.json'));
127
+ console.error(chalk_1.default.yellow('šŸ’” Or use --local to sync from a locally running FRAIM server.'));
128
+ process.exit(1);
129
+ }
105
130
  }
106
131
  console.log(chalk_1.default.blue('šŸ”„ Syncing FRAIM workflows from remote server...'));
107
132
  const result = await syncFromRemote({
@@ -115,27 +140,13 @@ const runSync = async (options) => {
115
140
  console.error(chalk_1.default.yellow('šŸ’” Check your API key and network connection.'));
116
141
  if (process.env.TEST_MODE === 'true') {
117
142
  console.log(chalk_1.default.yellow('āš ļø TEST_MODE: Continuing without remote sync (server may be unavailable).'));
143
+ updateVersionInConfig(fraimDir);
118
144
  return;
119
145
  }
120
146
  process.exit(1);
121
147
  }
122
- console.log(chalk_1.default.green(`āœ… Successfully synced ${result.workflowsSynced} workflows, ${result.scriptsSynced} scripts, ${result.coachingSynced} coaching files, and ${result.docsSynced} docs from remote`));
123
- // Update version in config.json
124
- const configPath = path_1.default.join(fraimDir, 'config.json');
125
- if (fs_1.default.existsSync(configPath)) {
126
- try {
127
- const currentConfig = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
128
- const newVersion = (0, version_utils_1.getFraimVersion)();
129
- if (currentConfig.version !== newVersion) {
130
- currentConfig.version = newVersion;
131
- fs_1.default.writeFileSync(configPath, JSON.stringify(currentConfig, null, 2));
132
- console.log(chalk_1.default.green(`āœ… Updated FRAIM version to ${newVersion} in config.`));
133
- }
134
- }
135
- catch (e) {
136
- console.warn(chalk_1.default.yellow('āš ļø Could not update version in config.json.'));
137
- }
138
- }
148
+ console.log(chalk_1.default.green(`āœ… Successfully synced ${result.workflowsSynced} workflows, ${result.employeeJobsSynced} ai-employee jobs, ${result.managerJobsSynced} ai-manager jobs, ${result.skillsSynced} skills, ${result.rulesSynced} rules, ${result.scriptsSynced} scripts, and ${result.docsSynced} docs from remote`));
149
+ updateVersionInConfig(fraimDir);
139
150
  };
140
151
  exports.runSync = runSync;
141
152
  async function checkAndUpdateCLI() {
@@ -166,8 +166,9 @@ function checkOverrideSyntaxValid() {
166
166
  category: 'workflows',
167
167
  critical: false,
168
168
  run: async () => {
169
- const overridesDir = path_1.default.join(process.cwd(), '.fraim', 'overrides');
170
- if (!fs_1.default.existsSync(overridesDir)) {
169
+ const personalizedDir = path_1.default.join(process.cwd(), '.fraim', 'personalized-employee');
170
+ const legacyOverridesDir = path_1.default.join(process.cwd(), '.fraim', 'overrides');
171
+ if (!fs_1.default.existsSync(personalizedDir) && !fs_1.default.existsSync(legacyOverridesDir)) {
171
172
  return {
172
173
  status: 'passed',
173
174
  message: 'No overrides (not required)',
@@ -192,10 +193,17 @@ function checkOverrideSyntaxValid() {
192
193
  }
193
194
  }
194
195
  };
195
- scanDir(overridesDir);
196
+ if (fs_1.default.existsSync(personalizedDir)) {
197
+ scanDir(personalizedDir);
198
+ }
199
+ if (fs_1.default.existsSync(legacyOverridesDir)) {
200
+ scanDir(legacyOverridesDir);
201
+ }
196
202
  // Validate each override
197
203
  for (const override of overrides) {
198
- const overridePath = path_1.default.join(overridesDir, override);
204
+ const primaryPath = path_1.default.join(personalizedDir, override);
205
+ const legacyPath = path_1.default.join(legacyOverridesDir, override);
206
+ const overridePath = fs_1.default.existsSync(primaryPath) ? primaryPath : legacyPath;
199
207
  const content = fs_1.default.readFileSync(overridePath, 'utf-8');
200
208
  const parseResult = parser.parse(content);
201
209
  if (parseResult.hasImports) {
@@ -43,7 +43,6 @@ const doctor_1 = require("./commands/doctor");
43
43
  const list_1 = require("./commands/list");
44
44
  const setup_1 = require("./commands/setup");
45
45
  const init_project_1 = require("./commands/init-project");
46
- const test_mcp_1 = require("./commands/test-mcp");
47
46
  const add_ide_1 = require("./commands/add-ide");
48
47
  const add_provider_1 = require("./commands/add-provider");
49
48
  const override_1 = require("./commands/override");
@@ -80,7 +79,6 @@ program.addCommand(doctor_1.doctorCommand);
80
79
  program.addCommand(list_1.listCommand);
81
80
  program.addCommand(setup_1.setupCommand);
82
81
  program.addCommand(init_project_1.initProjectCommand);
83
- program.addCommand(test_mcp_1.testMCPCommand);
84
82
  program.addCommand(add_ide_1.addIDECommand);
85
83
  program.addCommand(add_provider_1.addProviderCommand);
86
84
  program.addCommand(override_1.overrideCommand);
@@ -38,6 +38,8 @@ exports.BASE_MCP_SERVERS = [
38
38
  buildServer: (fraimKey) => ({
39
39
  command: 'fraim-mcp',
40
40
  env: {
41
+ // Include API key for IDE configs (Codex, VSCode, etc.)
42
+ // The stdio-server will use this if set, otherwise falls back to ~/.fraim/config.json
41
43
  FRAIM_API_KEY: fraimKey,
42
44
  FRAIM_REMOTE_URL: 'https://fraim.wellnessatwork.me'
43
45
  }
@@ -203,12 +203,20 @@ const configureIDEMCP = async (ide, fraimKey, tokenInput, providerConfigs) => {
203
203
  // Merge MCP servers intelligently
204
204
  const mergedMCPServers = { ...existingMCPServers };
205
205
  const addedServers = [];
206
+ const updatedServers = [];
206
207
  const skippedServers = [];
208
+ // Servers that should always be updated (not skipped)
209
+ const alwaysUpdateServers = new Set(['fraim', 'github', 'gitlab', 'jira', 'ado', 'linear']);
207
210
  for (const [serverName, serverConfig] of Object.entries(newMCPServers)) {
208
211
  if (!existingMCPServers[serverName]) {
209
212
  mergedMCPServers[serverName] = serverConfig;
210
213
  addedServers.push(serverName);
211
214
  }
215
+ else if (alwaysUpdateServers.has(serverName)) {
216
+ // Always update these servers to ensure keys/tokens are current
217
+ mergedMCPServers[serverName] = serverConfig;
218
+ updatedServers.push(serverName);
219
+ }
212
220
  else {
213
221
  skippedServers.push(serverName);
214
222
  }
@@ -223,8 +231,11 @@ const configureIDEMCP = async (ide, fraimKey, tokenInput, providerConfigs) => {
223
231
  addedServers.forEach(server => {
224
232
  console.log(chalk_1.default.green(` āœ… Added ${server} MCP server`));
225
233
  });
234
+ updatedServers.forEach(server => {
235
+ console.log(chalk_1.default.blue(` šŸ”„ Updated ${server} MCP server`));
236
+ });
226
237
  skippedServers.forEach(server => {
227
- console.log(chalk_1.default.gray(` ā­ļø Skipped ${server} (already exists)`));
238
+ console.log(chalk_1.default.gray(` ā­ļø Skipped ${server} (already exists)`));
228
239
  });
229
240
  }
230
241
  console.log(chalk_1.default.green(`āœ… Updated ${configPath}`));
@@ -27,8 +27,11 @@ async function syncFromRemote(options) {
27
27
  return {
28
28
  success: false,
29
29
  workflowsSynced: 0,
30
+ employeeJobsSynced: 0,
31
+ managerJobsSynced: 0,
32
+ skillsSynced: 0,
33
+ rulesSynced: 0,
30
34
  scriptsSynced: 0,
31
- coachingSynced: 0,
32
35
  docsSynced: 0,
33
36
  error: 'FRAIM_API_KEY not set'
34
37
  };
@@ -49,8 +52,11 @@ async function syncFromRemote(options) {
49
52
  return {
50
53
  success: false,
51
54
  workflowsSynced: 0,
55
+ employeeJobsSynced: 0,
56
+ managerJobsSynced: 0,
57
+ skillsSynced: 0,
58
+ rulesSynced: 0,
52
59
  scriptsSynced: 0,
53
- coachingSynced: 0,
54
60
  docsSynced: 0,
55
61
  error: 'No files received'
56
62
  };
@@ -73,6 +79,72 @@ async function syncFromRemote(options) {
73
79
  (0, fs_1.writeFileSync)(filePath, file.content, 'utf8');
74
80
  console.log(chalk_1.default.gray(` + ${file.path}`));
75
81
  }
82
+ // Sync job stubs to role-specific folders under .fraim
83
+ const allJobFiles = files.filter(f => f.type === 'job');
84
+ const managerJobFiles = allJobFiles.filter(f => f.path.startsWith('ai-manager/'));
85
+ const jobFiles = allJobFiles.filter(f => !f.path.startsWith('ai-manager/'));
86
+ const employeeJobsDir = (0, path_1.join)(options.projectRoot, '.fraim', 'ai-employee', 'jobs');
87
+ if (!(0, fs_1.existsSync)(employeeJobsDir)) {
88
+ (0, fs_1.mkdirSync)(employeeJobsDir, { recursive: true });
89
+ }
90
+ cleanDirectory(employeeJobsDir);
91
+ for (const file of jobFiles) {
92
+ const filePath = (0, path_1.join)(employeeJobsDir, file.path);
93
+ const fileDir = (0, path_1.dirname)(filePath);
94
+ if (!(0, fs_1.existsSync)(fileDir)) {
95
+ (0, fs_1.mkdirSync)(fileDir, { recursive: true });
96
+ }
97
+ (0, fs_1.writeFileSync)(filePath, file.content, 'utf8');
98
+ console.log(chalk_1.default.gray(` + ai-employee/jobs/${file.path}`));
99
+ }
100
+ // Sync ai-manager job stubs to .fraim/ai-manager/jobs/
101
+ const managerJobsDir = (0, path_1.join)(options.projectRoot, '.fraim', 'ai-manager', 'jobs');
102
+ if (!(0, fs_1.existsSync)(managerJobsDir)) {
103
+ (0, fs_1.mkdirSync)(managerJobsDir, { recursive: true });
104
+ }
105
+ cleanDirectory(managerJobsDir);
106
+ for (const file of managerJobFiles) {
107
+ const managerRelativePath = file.path.replace(/^ai-manager\//, '');
108
+ const filePath = (0, path_1.join)(managerJobsDir, managerRelativePath);
109
+ const fileDir = (0, path_1.dirname)(filePath);
110
+ if (!(0, fs_1.existsSync)(fileDir)) {
111
+ (0, fs_1.mkdirSync)(fileDir, { recursive: true });
112
+ }
113
+ (0, fs_1.writeFileSync)(filePath, file.content, 'utf8');
114
+ console.log(chalk_1.default.gray(` + ai-manager/jobs/${managerRelativePath}`));
115
+ }
116
+ // Sync full skill files to .fraim/ai-employee/skills/
117
+ const skillFiles = files.filter(f => f.type === 'skill');
118
+ const skillsDir = (0, path_1.join)(options.projectRoot, '.fraim', 'ai-employee', 'skills');
119
+ if (!(0, fs_1.existsSync)(skillsDir)) {
120
+ (0, fs_1.mkdirSync)(skillsDir, { recursive: true });
121
+ }
122
+ cleanDirectory(skillsDir);
123
+ for (const file of skillFiles) {
124
+ const filePath = (0, path_1.join)(skillsDir, file.path);
125
+ const fileDir = (0, path_1.dirname)(filePath);
126
+ if (!(0, fs_1.existsSync)(fileDir)) {
127
+ (0, fs_1.mkdirSync)(fileDir, { recursive: true });
128
+ }
129
+ (0, fs_1.writeFileSync)(filePath, file.content, 'utf8');
130
+ console.log(chalk_1.default.gray(` + ai-employee/skills/${file.path}`));
131
+ }
132
+ // Sync full rule files to .fraim/ai-employee/rules/
133
+ const ruleFiles = files.filter(f => f.type === 'rule');
134
+ const rulesDir = (0, path_1.join)(options.projectRoot, '.fraim', 'ai-employee', 'rules');
135
+ if (!(0, fs_1.existsSync)(rulesDir)) {
136
+ (0, fs_1.mkdirSync)(rulesDir, { recursive: true });
137
+ }
138
+ cleanDirectory(rulesDir);
139
+ for (const file of ruleFiles) {
140
+ const filePath = (0, path_1.join)(rulesDir, file.path);
141
+ const fileDir = (0, path_1.dirname)(filePath);
142
+ if (!(0, fs_1.existsSync)(fileDir)) {
143
+ (0, fs_1.mkdirSync)(fileDir, { recursive: true });
144
+ }
145
+ (0, fs_1.writeFileSync)(filePath, file.content, 'utf8');
146
+ console.log(chalk_1.default.gray(` + ai-employee/rules/${file.path}`));
147
+ }
76
148
  // Sync scripts to user directory
77
149
  const scriptFiles = files.filter(f => f.type === 'script');
78
150
  const userDir = (0, script_sync_utils_1.getUserFraimDir)();
@@ -92,22 +164,6 @@ async function syncFromRemote(options) {
92
164
  (0, fs_1.writeFileSync)(filePath, file.content, 'utf8');
93
165
  console.log(chalk_1.default.gray(` + ${file.path}`));
94
166
  }
95
- // Sync coaching files to .fraim/coaching-moments/
96
- const coachingFiles = files.filter(f => f.type === 'coaching');
97
- const coachingDir = (0, path_1.join)(options.projectRoot, '.fraim', 'coaching-moments');
98
- if (!(0, fs_1.existsSync)(coachingDir)) {
99
- (0, fs_1.mkdirSync)(coachingDir, { recursive: true });
100
- }
101
- cleanDirectory(coachingDir);
102
- for (const file of coachingFiles) {
103
- const filePath = (0, path_1.join)(coachingDir, file.path);
104
- const fileDir = (0, path_1.dirname)(filePath);
105
- if (!(0, fs_1.existsSync)(fileDir)) {
106
- (0, fs_1.mkdirSync)(fileDir, { recursive: true });
107
- }
108
- (0, fs_1.writeFileSync)(filePath, file.content, 'utf8');
109
- console.log(chalk_1.default.gray(` + coaching-moments/${file.path}`));
110
- }
111
167
  // Sync docs to .fraim/docs/
112
168
  const docsFiles = files.filter(f => f.type === 'docs');
113
169
  const docsDir = (0, path_1.join)(options.projectRoot, '.fraim', 'docs');
@@ -124,12 +180,14 @@ async function syncFromRemote(options) {
124
180
  (0, fs_1.writeFileSync)(filePath, file.content, 'utf8');
125
181
  console.log(chalk_1.default.gray(` + docs/${file.path}`));
126
182
  }
127
- console.log(chalk_1.default.green(`\nāœ… Synced ${workflowFiles.length} workflows, ${scriptFiles.length} scripts, ${coachingFiles.length} coaching files, and ${docsFiles.length} docs from remote`));
128
183
  return {
129
184
  success: true,
130
185
  workflowsSynced: workflowFiles.length,
186
+ employeeJobsSynced: jobFiles.length,
187
+ managerJobsSynced: managerJobFiles.length,
188
+ skillsSynced: skillFiles.length,
189
+ rulesSynced: ruleFiles.length,
131
190
  scriptsSynced: scriptFiles.length,
132
- coachingSynced: coachingFiles.length,
133
191
  docsSynced: docsFiles.length
134
192
  };
135
193
  }
@@ -138,8 +196,11 @@ async function syncFromRemote(options) {
138
196
  return {
139
197
  success: false,
140
198
  workflowsSynced: 0,
199
+ employeeJobsSynced: 0,
200
+ managerJobsSynced: 0,
201
+ skillsSynced: 0,
202
+ rulesSynced: 0,
141
203
  scriptsSynced: 0,
142
- coachingSynced: 0,
143
204
  docsSynced: 0,
144
205
  error: error.message
145
206
  };