fraim-framework 2.0.68 → 2.0.69

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/bin/fraim.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  /**
4
4
  * FRAIM Framework CLI Entry Point
@@ -17,7 +17,7 @@ exports.doctorCommand = new commander_1.Command('doctor')
17
17
  console.log(chalk_1.default.blue('🩺 FRAIM Doctor - Checking health...\n'));
18
18
  // 1. Check .fraim directory
19
19
  if (!fs_1.default.existsSync(fraimDir)) {
20
- console.log(chalk_1.default.red('❌ Missing .fraim/ directory. Run "fraim init".'));
20
+ console.log(chalk_1.default.red('❌ Missing .fraim/ directory. Run "fraim setup" or "fraim init-project".'));
21
21
  issuesFound++;
22
22
  }
23
23
  else {
@@ -13,7 +13,8 @@ const sync_1 = require("./sync");
13
13
  const platform_detection_1 = require("../../utils/platform-detection");
14
14
  const version_utils_1 = require("../../utils/version-utils");
15
15
  const checkGlobalSetup = () => {
16
- const globalConfigPath = path_1.default.join(os_1.default.homedir(), '.fraim', 'config.json');
16
+ const fraimUserDir = process.env.FRAIM_USER_DIR || path_1.default.join(os_1.default.homedir(), '.fraim');
17
+ const globalConfigPath = path_1.default.join(fraimUserDir, 'config.json');
17
18
  if (!fs_1.default.existsSync(globalConfigPath)) {
18
19
  return { exists: false };
19
20
  }
@@ -91,7 +92,7 @@ const runInitProject = async () => {
91
92
  workflowsPath: '.fraim/workflows'
92
93
  }
93
94
  };
94
- console.log(chalk_1.default.blue(` Detected ${detection.provider.toUpperCase()} repository`));
95
+ console.log(chalk_1.default.blue(` Platform: ${detection.provider.toUpperCase()}`));
95
96
  if (detection.provider === 'github') {
96
97
  console.log(chalk_1.default.gray(` Repository: ${detection.repository.owner}/${detection.repository.name}`));
97
98
  console.log(chalk_1.default.gray(` Token: ${hasToken ? '✓ Configured' : '✗ Missing'}`));
@@ -104,28 +105,21 @@ const runInitProject = async () => {
104
105
  }
105
106
  }
106
107
  else {
107
- // No git remote detected - warn user
108
+ // No git remote detected - warn user and fallback to conversational-style config
108
109
  console.log(chalk_1.default.yellow(' ⚠️ No git remote detected'));
109
110
  console.log(chalk_1.default.yellow(' Integrated mode requires a git remote for platform features.'));
110
- console.log(chalk_1.default.gray(' Creating config with placeholder values...'));
111
+ console.log(chalk_1.default.gray(' Falling back to conversational mode configuration...'));
111
112
  const repoName = path_1.default.basename(projectRoot);
112
113
  config = {
113
114
  version: (0, version_utils_1.getFraimVersion)(),
114
115
  project: {
115
116
  name: repoName
116
117
  },
117
- repository: {
118
- provider: 'github',
119
- owner: 'your-username',
120
- name: repoName,
121
- url: `https://github.com/your-username/${repoName}.git`,
122
- defaultBranch: 'main'
123
- },
124
118
  customizations: {
125
119
  workflowsPath: '.fraim/workflows'
126
120
  }
127
121
  };
128
- console.log(chalk_1.default.gray(' Please update .fraim/config.json with your repository details.'));
122
+ console.log(chalk_1.default.gray(' Please update .fraim/config.json if you add a repository later.'));
129
123
  }
130
124
  }
131
125
  fs_1.default.writeFileSync(configPath, JSON.stringify(config, null, 2));
@@ -14,7 +14,7 @@ exports.listCommand = new commander_1.Command('list')
14
14
  const projectRoot = process.cwd();
15
15
  const workflowsDir = path_1.default.join(projectRoot, '.fraim', 'workflows');
16
16
  if (!fs_1.default.existsSync(workflowsDir)) {
17
- console.log(chalk_1.default.yellow('⚠️ No .fraim/workflows directory found. Run "fraim init" and "fraim sync" first.'));
17
+ console.log(chalk_1.default.yellow('⚠️ No .fraim/workflows directory found. Run "fraim setup" or "fraim init-project" first.'));
18
18
  return;
19
19
  }
20
20
  const stubs = fs_1.default.readdirSync(workflowsDir).filter(f => f.endsWith('.md'));
@@ -12,8 +12,9 @@ const path_1 = __importDefault(require("path"));
12
12
  const os_1 = __importDefault(require("os"));
13
13
  const token_validator_1 = require("../setup/token-validator");
14
14
  const auto_mcp_setup_1 = require("../setup/auto-mcp-setup");
15
- const script_sync_utils_1 = require("../../utils/script-sync-utils");
16
15
  const version_utils_1 = require("../../utils/version-utils");
16
+ const platform_detection_1 = require("../../utils/platform-detection");
17
+ const init_project_1 = require("./init-project");
17
18
  const promptForFraimKey = async () => {
18
19
  console.log(chalk_1.default.blue('🔑 FRAIM Key Setup'));
19
20
  console.log('FRAIM requires a valid API key to access workflows and features.\n');
@@ -230,28 +231,6 @@ const saveGlobalConfig = (fraimKey, mode, tokens) => {
230
231
  fs_1.default.writeFileSync(globalConfigPath, JSON.stringify(config, null, 2));
231
232
  console.log(chalk_1.default.green('✅ Global FRAIM configuration saved'));
232
233
  };
233
- const syncGlobalScripts = () => {
234
- console.log(chalk_1.default.blue('🔄 Syncing FRAIM scripts to user directory...'));
235
- // Ensure user directories exist and initialize user config
236
- const { ensureUserFraimDirectories } = require('../../utils/script-sync-utils');
237
- ensureUserFraimDirectories();
238
- console.log(chalk_1.default.green('✅ Initialized user configuration'));
239
- // Find registry path
240
- let registryPath = path_1.default.join(__dirname, '../../../../registry');
241
- if (!fs_1.default.existsSync(registryPath)) {
242
- registryPath = path_1.default.join(__dirname, '../../../registry');
243
- }
244
- if (fs_1.default.existsSync(registryPath)) {
245
- const syncResult = (0, script_sync_utils_1.syncScriptsToUserDirectory)(registryPath);
246
- console.log(chalk_1.default.green(`✅ Synced ${syncResult.synced} scripts to user directory.`));
247
- if (syncResult.ephemeral > 0) {
248
- console.log(chalk_1.default.gray(` ${syncResult.ephemeral} dependent scripts will use ephemeral execution.`));
249
- }
250
- }
251
- else {
252
- console.log(chalk_1.default.yellow('⚠️ Registry not found, skipping script sync.'));
253
- }
254
- };
255
234
  const runSetup = async (options) => {
256
235
  console.log(chalk_1.default.blue('🚀 Welcome to FRAIM! Let\'s get you set up.\n'));
257
236
  // Determine if this is an update (adding platforms to existing setup)
@@ -387,19 +366,26 @@ const runSetup = async (options) => {
387
366
  console.log(chalk_1.default.yellow('⚠️ MCP configuration encountered issues'));
388
367
  console.log(chalk_1.default.gray(' You can configure MCP manually or run setup again later\n'));
389
368
  }
390
- // Sync global scripts
391
- syncGlobalScripts();
369
+ // Auto-run project init if we're in a git repo
370
+ if ((0, platform_detection_1.isGitRepository)()) {
371
+ console.log(chalk_1.default.blue('\n📁 Git repository detected — initializing project...'));
372
+ await (0, init_project_1.runInitProject)();
373
+ }
374
+ else {
375
+ console.log(chalk_1.default.yellow('\nℹ️ Not in a git repository — skipping project initialization.'));
376
+ console.log(chalk_1.default.cyan(' To initialize a project later, cd into a repo and run: fraim init-project'));
377
+ }
392
378
  }
393
379
  // Show summary
394
- console.log(chalk_1.default.green('\n🎯 Configuration complete!'));
380
+ console.log(chalk_1.default.green('\n🎯 Setup complete!'));
395
381
  console.log(chalk_1.default.gray(` Mode: ${mode}`));
396
382
  if (mode === 'integrated') {
397
383
  console.log(chalk_1.default.gray(` Platforms: ${tokens.github ? 'GitHub ✓' : 'GitHub ✗'} ${tokens.ado ? 'ADO ✓' : 'ADO ✗'}`));
398
384
  }
399
- console.log(chalk_1.default.cyan('\n📝 Next steps:'));
400
- console.log(chalk_1.default.cyan(' 1. Go to any project directory'));
385
+ console.log(chalk_1.default.cyan('\n📝 For future projects:'));
386
+ console.log(chalk_1.default.cyan(' 1. cd into any project directory'));
401
387
  console.log(chalk_1.default.cyan(' 2. Run: fraim init-project'));
402
- console.log(chalk_1.default.cyan(' 3. Start using FRAIM workflows'));
388
+ console.log(chalk_1.default.cyan(' 3. Ask your AI agent (Claude Desktop / Cowork, Cursor, etc.) "list fraim workflows"'));
403
389
  if (mode === 'integrated' && (!tokens.github || !tokens.ado)) {
404
390
  console.log(chalk_1.default.gray('\n💡 To add more platforms later:'));
405
391
  if (!tokens.github)
@@ -45,6 +45,7 @@ const child_process_1 = require("child_process");
45
45
  const config_loader_1 = require("../../fraim/config-loader");
46
46
  const version_utils_1 = require("../../utils/version-utils");
47
47
  const script_sync_utils_1 = require("../../utils/script-sync-utils");
48
+ const git_utils_1 = require("../../utils/git-utils");
48
49
  /**
49
50
  * Load API key from user-level config (~/.fraim/config.json)
50
51
  */
@@ -66,8 +67,6 @@ const runSync = async (options) => {
66
67
  const projectRoot = process.cwd();
67
68
  const config = (0, config_loader_1.loadFraimConfig)();
68
69
  const fraimDir = path_1.default.join(projectRoot, '.fraim');
69
- const workflowsRelativePath = config.customizations?.workflowsPath || '.fraim/workflows';
70
- const workflowsDir = path_1.default.resolve(projectRoot, workflowsRelativePath);
71
70
  // Check for CLI updates first (but skip during installation to prevent loops)
72
71
  const isPostInstall = process.env.npm_lifecycle_event === 'postinstall' ||
73
72
  process.env.npm_lifecycle_script === 'postinstall';
@@ -77,153 +76,46 @@ const runSync = async (options) => {
77
76
  else if (isPostInstall) {
78
77
  console.log(chalk_1.default.gray('⏭️ Skipping auto-update check during installation to prevent loops.'));
79
78
  }
80
- // Try remote sync first if API key is available (from user config, project config, or env)
81
- const apiKey = loadUserApiKey() || config.apiKey || process.env.FRAIM_API_KEY;
82
- if (apiKey) {
83
- console.log(chalk_1.default.blue('🔄 Syncing FRAIM workflows from remote server...'));
84
- try {
85
- const { syncFromRemote } = await Promise.resolve().then(() => __importStar(require('../../utils/remote-sync.js')));
86
- const result = await syncFromRemote({
87
- remoteUrl: config.remoteUrl, // Will use default if not set
88
- apiKey: apiKey,
89
- projectRoot,
90
- skipUpdates: options.skipUpdates || false
91
- });
92
- if (result.success) {
93
- console.log(chalk_1.default.green(`✅ Successfully synced ${result.workflowsSynced} workflows and ${result.scriptsSynced} scripts from remote`));
94
- // Update version in config.json
95
- const configPath = path_1.default.join(fraimDir, 'config.json');
96
- if (fs_1.default.existsSync(configPath)) {
97
- try {
98
- const currentConfig = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
99
- const newVersion = (0, version_utils_1.getFraimVersion)();
100
- if (currentConfig.version !== newVersion) {
101
- currentConfig.version = newVersion;
102
- fs_1.default.writeFileSync(configPath, JSON.stringify(currentConfig, null, 2));
103
- console.log(chalk_1.default.green(`✅ Updated FRAIM version to ${newVersion} in config.`));
104
- }
105
- }
106
- catch (e) {
107
- console.warn(chalk_1.default.yellow('⚠️ Could not update version in config.json.'));
108
- }
109
- }
110
- return;
111
- }
112
- else {
113
- console.warn(chalk_1.default.yellow(`⚠️ Remote sync failed: ${result.error}, falling back to local sync`));
114
- }
115
- }
116
- catch (error) {
117
- console.warn(chalk_1.default.yellow(`⚠️ Remote sync failed: ${error.message}, falling back to local sync`));
79
+ const { syncFromRemote } = await Promise.resolve().then(() => __importStar(require('../../utils/remote-sync.js')));
80
+ // Path 1: --local flag hit localhost MCP server
81
+ if (options.local) {
82
+ console.log(chalk_1.default.blue('🔄 Syncing FRAIM workflows from local server...'));
83
+ const localPort = process.env.FRAIM_MCP_PORT ? parseInt(process.env.FRAIM_MCP_PORT) : (0, git_utils_1.getPort)();
84
+ const result = await syncFromRemote({
85
+ remoteUrl: `http://localhost:${localPort}`,
86
+ apiKey: 'local-dev',
87
+ projectRoot,
88
+ skipUpdates: true
89
+ });
90
+ if (result.success) {
91
+ console.log(chalk_1.default.green(`✅ Successfully synced ${result.workflowsSynced} workflows and ${result.scriptsSynced} scripts from local server`));
92
+ return;
118
93
  }
119
- }
120
- // Fallback to local sync (for development/testing or when remote is not configured)
121
- console.log(chalk_1.default.blue('🔄 Syncing FRAIM workflows from local package...'));
122
- // Find local stubs directory
123
- let registryPath = path_1.default.join(__dirname, '../../../../registry/stubs');
124
- if (!fs_1.default.existsSync(registryPath)) {
125
- registryPath = path_1.default.join(__dirname, '../../../registry/stubs');
126
- }
127
- if (!fs_1.default.existsSync(registryPath)) {
128
- registryPath = path_1.default.join(projectRoot, 'registry/stubs');
129
- }
130
- if (!fs_1.default.existsSync(registryPath)) {
131
- console.error(chalk_1.default.red('❌ Stub registry not found locally and no remote server configured.'));
132
- console.error(chalk_1.default.yellow('💡 Configure remoteUrl and apiKey in .fraim/config.json to sync from remote server.'));
94
+ console.error(chalk_1.default.red(`❌ Local sync failed: ${result.error}`));
95
+ console.error(chalk_1.default.yellow('💡 Make sure the FRAIM MCP server is running locally (npm run dev).'));
133
96
  process.exit(1);
134
97
  }
135
- if (!fs_1.default.existsSync(workflowsDir)) {
136
- fs_1.default.mkdirSync(workflowsDir, { recursive: true });
137
- }
138
- const registryWorkflowsPath = path_1.default.join(registryPath, 'workflows');
139
- if (!fs_1.default.existsSync(registryWorkflowsPath)) {
140
- console.log(chalk_1.default.yellow('⚠️ No workflow stubs found in local registry.'));
141
- return;
142
- }
143
- // Get all workflow stubs from registry (recursive)
144
- const getFiles = (dir) => {
145
- const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
146
- const files = entries
147
- .filter(e => !e.isDirectory() && e.name.endsWith('.md'))
148
- .map(e => path_1.default.join(dir, e.name));
149
- const folders = entries.filter(e => e.isDirectory());
150
- for (const folder of folders) {
151
- files.push(...getFiles(path_1.default.join(dir, folder.name)));
152
- }
153
- return files;
154
- };
155
- const registryFiles = getFiles(registryWorkflowsPath);
156
- const copiedStubs = [];
157
- for (const file of registryFiles) {
158
- const fileName = path_1.default.basename(file);
159
- const workflowName = fileName.replace('.md', '');
160
- // Calculate relative path from registry/stubs/workflows to preserve structure
161
- const relativePath = path_1.default.relative(registryWorkflowsPath, file);
162
- const relativeDir = path_1.default.dirname(relativePath);
163
- // Ensure target directory exists
164
- const targetDir = path_1.default.join(workflowsDir, relativeDir);
165
- if (!fs_1.default.existsSync(targetDir)) {
166
- fs_1.default.mkdirSync(targetDir, { recursive: true });
167
- }
168
- // Copy stub file directly
169
- const stubContent = fs_1.default.readFileSync(file, 'utf8');
170
- const stubPath = path_1.default.join(targetDir, fileName);
171
- fs_1.default.writeFileSync(stubPath, stubContent);
172
- copiedStubs.push(relativePath);
173
- console.log(chalk_1.default.gray(` + ${workflowName} (${relativeDir === '.' ? 'root' : relativeDir})`));
174
- }
175
- // Cleanup stubs that no longer exist in registry
176
- const getLocalStubs = (dir) => {
177
- if (!fs_1.default.existsSync(dir))
178
- return [];
179
- const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
180
- const files = entries
181
- .filter(e => !e.isDirectory() && e.name.endsWith('.md'))
182
- .map(e => path_1.default.relative(workflowsDir, path_1.default.join(dir, e.name)));
183
- const folders = entries.filter(e => e.isDirectory());
184
- for (const folder of folders) {
185
- files.push(...getLocalStubs(path_1.default.join(dir, folder.name)));
186
- }
187
- return files;
188
- };
189
- const localStubs = getLocalStubs(workflowsDir);
190
- for (const stub of localStubs) {
191
- const normalizedStub = stub.replace(/\\/g, '/');
192
- const normalizedCopied = copiedStubs.map(s => s.replace(/\\/g, '/'));
193
- if (!normalizedCopied.includes(normalizedStub)) {
194
- fs_1.default.unlinkSync(path_1.default.join(workflowsDir, stub));
195
- console.log(chalk_1.default.yellow(` - ${stub} (removed from registry)`));
196
- try {
197
- const dir = path_1.default.dirname(path_1.default.join(workflowsDir, stub));
198
- if (fs_1.default.readdirSync(dir).length === 0) {
199
- fs_1.default.rmdirSync(dir);
200
- }
201
- }
202
- catch (e) { }
203
- }
204
- }
205
- console.log(chalk_1.default.green(`\n✅ Workflow sync complete. Copied ${copiedStubs.length} stubs.`));
206
- // Sync scripts
207
- console.log(chalk_1.default.blue('\n🔄 Syncing FRAIM scripts to user directory...'));
208
- let scriptsRegistryPath = path_1.default.join(__dirname, '../../../../registry');
209
- if (!fs_1.default.existsSync(path_1.default.join(scriptsRegistryPath, 'scripts'))) {
210
- scriptsRegistryPath = path_1.default.join(__dirname, '../../../registry');
211
- }
212
- if (!fs_1.default.existsSync(path_1.default.join(scriptsRegistryPath, 'scripts'))) {
213
- scriptsRegistryPath = path_1.default.join(projectRoot, 'registry');
214
- }
215
- if (!fs_1.default.existsSync(path_1.default.join(scriptsRegistryPath, 'scripts'))) {
216
- console.error(chalk_1.default.red('❌ Scripts registry not found.'));
98
+ // Path 2: Remote sync with API key
99
+ const apiKey = loadUserApiKey() || config.apiKey || process.env.FRAIM_API_KEY;
100
+ 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.'));
217
104
  process.exit(1);
218
105
  }
219
- const syncResult = (0, script_sync_utils_1.syncScriptsToUserDirectory)(scriptsRegistryPath);
220
- const cleanedScriptCount = (0, script_sync_utils_1.cleanupObsoleteUserScripts)(scriptsRegistryPath);
221
- if (syncResult.synced > 0 || cleanedScriptCount > 0) {
222
- console.log(chalk_1.default.green(`✅ Script sync complete. Updated ${syncResult.synced} scripts, removed ${cleanedScriptCount} obsolete scripts.`));
223
- }
224
- else {
225
- console.log(chalk_1.default.green('✅ Scripts are already in sync.'));
106
+ console.log(chalk_1.default.blue('🔄 Syncing FRAIM workflows from remote server...'));
107
+ const result = await syncFromRemote({
108
+ remoteUrl: config.remoteUrl,
109
+ apiKey: apiKey,
110
+ projectRoot,
111
+ skipUpdates: options.skipUpdates || false
112
+ });
113
+ if (!result.success) {
114
+ console.error(chalk_1.default.red(`❌ Remote sync failed: ${result.error}`));
115
+ console.error(chalk_1.default.yellow('💡 Check your API key and network connection.'));
116
+ process.exit(1);
226
117
  }
118
+ console.log(chalk_1.default.green(`✅ Successfully synced ${result.workflowsSynced} workflows and ${result.scriptsSynced} scripts from remote`));
227
119
  // Update version in config.json
228
120
  const configPath = path_1.default.join(fraimDir, 'config.json');
229
121
  if (fs_1.default.existsSync(configPath)) {
@@ -355,4 +247,5 @@ exports.syncCommand = new commander_1.Command('sync')
355
247
  .description('Sync workflow stubs from the framework registry')
356
248
  .option('-f, --force', 'Force sync even if digest matches')
357
249
  .option('--skip-updates', 'Skip checking for CLI updates')
250
+ .option('--local', 'Sync from local development server (port derived from git branch)')
358
251
  .action(exports.runSync);
@@ -5,11 +5,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  const commander_1 = require("commander");
8
- const init_1 = require("./commands/init");
9
8
  const sync_1 = require("./commands/sync");
10
9
  const doctor_1 = require("./commands/doctor");
11
10
  const list_1 = require("./commands/list");
12
- const wizard_1 = require("./commands/wizard");
13
11
  const setup_1 = require("./commands/setup");
14
12
  const init_project_1 = require("./commands/init-project");
15
13
  const test_mcp_1 = require("./commands/test-mcp");
@@ -41,11 +39,9 @@ program
41
39
  .name('fraim')
42
40
  .description('FRAIM Framework CLI - Manage your AI workflows and rules')
43
41
  .version(packageJson.version);
44
- program.addCommand(init_1.initCommand);
45
42
  program.addCommand(sync_1.syncCommand);
46
43
  program.addCommand(doctor_1.doctorCommand);
47
44
  program.addCommand(list_1.listCommand);
48
- program.addCommand(wizard_1.wizardCommand);
49
45
  program.addCommand(setup_1.setupCommand);
50
46
  program.addCommand(init_project_1.initProjectCommand);
51
47
  program.addCommand(test_mcp_1.testMCPCommand);
@@ -73,26 +73,26 @@ const checkMCPConfigurations = () => {
73
73
  // Ignore parsing errors
74
74
  }
75
75
  }
76
- // Check Claude Desktop config
76
+ // Check Claude Desktop / Cowork config
77
77
  const claudeConfigPath = path_1.default.join(os_1.default.homedir(), 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json');
78
78
  if (fs_1.default.existsSync(claudeConfigPath)) {
79
79
  try {
80
80
  const claudeConfig = JSON.parse(fs_1.default.readFileSync(claudeConfigPath, 'utf8'));
81
81
  if (claudeConfig.mcpServers?.fraim) {
82
- sources.push('Claude Desktop');
82
+ sources.push('Claude Desktop / Cowork');
83
83
  }
84
84
  }
85
85
  catch (e) {
86
86
  // Ignore parsing errors
87
87
  }
88
88
  }
89
- // Check macOS Claude config path
89
+ // Check macOS Claude Desktop / Cowork config path
90
90
  const claudeMacPath = path_1.default.join(os_1.default.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
91
91
  if (fs_1.default.existsSync(claudeMacPath)) {
92
92
  try {
93
93
  const claudeConfig = JSON.parse(fs_1.default.readFileSync(claudeMacPath, 'utf8'));
94
94
  if (claudeConfig.mcpServers?.fraim) {
95
- sources.push('Claude Desktop (macOS)');
95
+ sources.push('Claude Desktop / Cowork (macOS)');
96
96
  }
97
97
  }
98
98
  catch (e) {
@@ -21,6 +21,7 @@ const detectClaude = () => {
21
21
  const paths = [
22
22
  '~/.claude.json',
23
23
  '~/.claude',
24
+ '~/Library/Application Support/Cloud',
24
25
  '~/Library/Application Support/Claude',
25
26
  '~/AppData/Roaming/Claude'
26
27
  ];
@@ -55,17 +56,26 @@ const detectWindsurf = () => {
55
56
  };
56
57
  exports.IDE_CONFIGS = [
57
58
  {
58
- name: 'Claude',
59
+ name: 'Claude Code',
59
60
  configPath: '~/.claude.json',
60
61
  configFormat: 'json',
61
62
  configType: 'claude',
62
63
  detectMethod: detectClaude,
63
64
  alternativePaths: [
64
- '~/.claude/settings.json',
65
- '~/Library/Application Support/Claude/settings.json',
66
- '~/AppData/Roaming/Claude/settings.json'
65
+ '~/.claude/settings.json'
67
66
  ],
68
- description: 'Anthropic Claude application'
67
+ description: 'Anthropic Claude Code CLI tool'
68
+ },
69
+ {
70
+ name: 'Claude Desktop / Cowork',
71
+ configPath: '~/AppData/Roaming/Claude/claude_desktop_config.json',
72
+ configFormat: 'json',
73
+ configType: 'claude',
74
+ detectMethod: detectClaude,
75
+ alternativePaths: [
76
+ '~/Library/Application Support/Claude/claude_desktop_config.json'
77
+ ],
78
+ description: 'Anthropic Claude Desktop and Cowork application'
69
79
  },
70
80
  {
71
81
  name: 'Antigravity',
@@ -1,16 +1,11 @@
1
1
  "use strict";
2
- /**
3
- * FRAIM Template Processing System
4
- *
5
- * Handles two types of template processing:
6
- * 1. Config variables: {{config.path}} -> actual config values
7
- * 2. Provider actions: {{action}} -> provider-specific MCP tool calls
8
- */
9
2
  Object.defineProperty(exports, "__esModule", { value: true });
10
3
  exports.TemplateEngine = void 0;
11
4
  exports.getTemplateEngine = getTemplateEngine;
12
5
  const fs_1 = require("fs");
13
6
  const path_1 = require("path");
7
+ const provider_utils_1 = require("../utils/provider-utils");
8
+ const object_utils_1 = require("../utils/object-utils");
14
9
  /**
15
10
  * Multi-Provider Template Engine
16
11
  * Processes workflow templates with provider-specific action mappings
@@ -49,8 +44,9 @@ class TemplateEngine {
49
44
  return this.processProviderActions(workflowContent, repositoryInfo);
50
45
  }
51
46
  processProviderActions(workflowContent, repositoryInfo) {
52
- // Determine provider from repository info
53
- const provider = this.detectProvider(repositoryInfo);
47
+ // Determine provider from repository info using shared utility
48
+ const repoData = repositoryInfo?.repository || repositoryInfo;
49
+ const provider = repoData?.provider || (0, provider_utils_1.detectProvider)(repoData?.url);
54
50
  const providerTemplates = this.providers.get(provider);
55
51
  if (!providerTemplates) {
56
52
  console.warn(`⚠️ No templates found for provider: ${provider}, falling back to GitHub`);
@@ -58,22 +54,6 @@ class TemplateEngine {
58
54
  }
59
55
  return this.processWithProvider(workflowContent, provider, repositoryInfo);
60
56
  }
61
- detectProvider(repositoryInfo) {
62
- if (!repositoryInfo)
63
- return 'github';
64
- // Get the repository data (could be direct or nested)
65
- const repoData = repositoryInfo.repository || repositoryInfo;
66
- // Detect provider from URL or explicit provider field
67
- if (repoData.provider) {
68
- return repoData.provider;
69
- }
70
- if (repoData.url) {
71
- if (repoData.url.includes('dev.azure.com') || repoData.url.includes('visualstudio.com')) {
72
- return 'ado';
73
- }
74
- }
75
- return 'github'; // Default
76
- }
77
57
  processWithProvider(workflowContent, provider, repositoryInfo) {
78
58
  const providerTemplates = this.providers.get(provider);
79
59
  if (!providerTemplates) {
@@ -102,22 +82,14 @@ class TemplateEngine {
102
82
  // Check if repositoryInfo has a 'repository' property (full config)
103
83
  // or if it IS the repository data directly
104
84
  let repoData = repositoryInfo.repository || repositoryInfo;
105
- const value = this.getNestedValue(repoData, repoPath);
85
+ const value = (0, object_utils_1.getNestedValue)(repoData, repoPath);
106
86
  return value !== undefined ? String(value) : match;
107
87
  }
108
88
  // Handle direct variables (for backward compatibility)
109
- const value = this.getNestedValue(repositoryInfo, trimmedPath);
89
+ const value = (0, object_utils_1.getNestedValue)(repositoryInfo, trimmedPath);
110
90
  return value !== undefined ? String(value) : match;
111
91
  });
112
92
  }
113
- /**
114
- * Get nested value from config object using dot notation
115
- */
116
- getNestedValue(obj, path) {
117
- return path.split('.').reduce((current, key) => {
118
- return current && current[key] !== undefined ? current[key] : undefined;
119
- }, obj);
120
- }
121
93
  /**
122
94
  * Get available providers
123
95
  */