fraim-framework 2.0.106 → 2.0.108

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.
@@ -633,24 +633,49 @@ const runSetup = async (options) => {
633
633
  console.log(chalk_1.default.cyan(' To initialize a project later, cd into a repo and run: fraim init-project'));
634
634
  }
635
635
  }
636
- // Show summary
637
- console.log(chalk_1.default.green('\nšŸŽÆ Setup complete!'));
638
- console.log(chalk_1.default.gray(` Mode: ${mode}`));
636
+ // Show mode-aware summary with clear value prop and next steps
637
+ console.log(chalk_1.default.green('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
638
+ console.log(chalk_1.default.green(' FRAIM is ready!'));
639
+ console.log(chalk_1.default.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
640
+ console.log(chalk_1.default.white('\n FRAIM is an AI management layer that turns you into'));
641
+ console.log(chalk_1.default.white(' a manager of AI agents. Run multiple agents through'));
642
+ console.log(chalk_1.default.white(' structured jobs, get manager-style coaching, and build'));
643
+ console.log(chalk_1.default.white(' a learning loop where agents improve over time.'));
644
+ console.log(chalk_1.default.gray('\n 60+ jobs across engineering, marketing, fundraising,'));
645
+ console.log(chalk_1.default.gray(' legal, product, hiring, customer development, and more.'));
646
+ // Show which IDEs were configured and how to use FRAIM in each
647
+ const { detectInstalledIDEs: detectIDEs } = await Promise.resolve().then(() => __importStar(require('../setup/ide-detector')));
648
+ const configuredIDEs = detectIDEs();
649
+ if (configuredIDEs.length > 0) {
650
+ const ideNames = configuredIDEs.map(ide => ide.name).join(', ');
651
+ console.log(chalk_1.default.cyan(`\n FRAIM is configured for: `) + chalk_1.default.white(ideNames));
652
+ console.log(chalk_1.default.cyan('\n Get started — open any of those tools and:'));
653
+ }
654
+ else {
655
+ console.log(chalk_1.default.cyan('\n Get started — open your AI tool and:'));
656
+ }
657
+ // Check if Claude is among configured IDEs
658
+ const hasClaude = configuredIDEs.some(ide => ide.name.toLowerCase().includes('claude'));
659
+ if (hasClaude) {
660
+ console.log(chalk_1.default.white(' /fraim ') + chalk_1.default.gray('(Claude Code) Browse all jobs'));
661
+ }
662
+ console.log(chalk_1.default.white(' "What can FRAIM help me with?" ') + chalk_1.default.gray('Works in any AI tool'));
663
+ console.log(chalk_1.default.gray('\n Just tell your AI what you need — FRAIM will find the right job.'));
639
664
  if (mode !== 'conversational') {
665
+ console.log(chalk_1.default.cyan('\n To set up FRAIM in a specific project:'));
666
+ console.log(chalk_1.default.white(' cd your-project && fraim init-project'));
667
+ console.log(chalk_1.default.gray(' This enables project-specific customizations,'));
668
+ console.log(chalk_1.default.gray(' GitHub workflows, and team learning.'));
640
669
  const configuredProviders = await Promise.all(Object.keys(tokens).map(async (id) => await (0, provider_registry_1.getProviderDisplayName)(id)));
641
- console.log(chalk_1.default.gray(` Platforms: ${configuredProviders.join(', ') || 'none'}`));
642
- }
643
- console.log(chalk_1.default.cyan('\nšŸ“ For future projects:'));
644
- console.log(chalk_1.default.cyan(' 1. cd into any project directory'));
645
- console.log(chalk_1.default.cyan(' 2. Run: fraim init-project'));
646
- console.log(chalk_1.default.cyan(' 3. Ask your AI agent: "FRAIM was just installed. Read the FRAIM docs, explain what it can do for me, then run project-onboarding."'));
647
- if (mode === 'integrated') {
670
+ if (configuredProviders.length > 0) {
671
+ console.log(chalk_1.default.gray(`\n Platforms: ${configuredProviders.join(', ')}`));
672
+ }
648
673
  const allProviderIds = await (0, provider_registry_1.getAllProviderIds)();
649
674
  const unconfiguredProviders = allProviderIds.filter(id => !tokens[id]);
650
675
  if (unconfiguredProviders.length > 0) {
651
- console.log(chalk_1.default.gray('\nšŸ’” To add more platforms later:'));
676
+ console.log(chalk_1.default.gray('\n To add more platforms later:'));
652
677
  unconfiguredProviders.forEach(id => {
653
- console.log(chalk_1.default.gray(` fraim setup --${id}`));
678
+ console.log(chalk_1.default.gray(` fraim setup --${id}`));
654
679
  });
655
680
  }
656
681
  }
@@ -22,7 +22,14 @@ const FRAIM_SLASH_COMMAND_CONTENT = `The user wants to run FRAIM. The requested
22
22
 
23
23
  Follow this process:
24
24
 
25
- 1. **If no argument was given** (the line above ends with ": "): call \`list_fraim_jobs()\` to discover available jobs. List each by name and its Intent line. Ask the user which job they want to run, then proceed to step 2.
25
+ 1. **If no argument was given** (the line above ends with ": "):
26
+ Call \`list_fraim_jobs()\` to discover available jobs. Present the results to the user grouped by business function (the server returns jobs organized by category — use those categories as group headings). For each group, list 3-5 of the most impactful jobs with a one-line description.
27
+
28
+ After listing, suggest 2-3 starting points based on what seems most relevant:
29
+ - If in a code repo: suggest jobs from engineering/product-building categories
30
+ - If no repo context: suggest jobs from marketing, fundraising, or business categories
31
+
32
+ Ask the user which job they want to run, then proceed to step 2.
26
33
 
27
34
  2. **Find the match**: from the list returned by \`list_fraim_jobs()\`, find the job whose name matches or closely resembles the argument. If no job matches, search for a matching skill by calling \`get_fraim_file({ path: "skills/<likely-category>/<argument>.md" })\` — try common categories like \`engineering/\`, \`marketing/\`, \`business/\`, \`product-management/\`, \`ai-tools/\`. Confirm the match with the user.
28
35
 
@@ -1,37 +1,4 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
36
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
4
  };
@@ -39,11 +6,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
6
  exports.ensureUserLevelDirectories = ensureUserLevelDirectories;
40
7
  exports.syncUserLevelArtifacts = syncUserLevelArtifacts;
41
8
  /**
42
- * User-Level FRAIM Artifact Sync
9
+ * User-Level FRAIM Setup
10
+ *
11
+ * Ensures the user-level ~/.fraim/ directory has the minimal structure needed
12
+ * for FRAIM to work outside of any project:
43
13
  *
44
- * Syncs jobs, skills, rules, and docs stubs from the remote FRAIM server
45
- * to the user-level ~/.fraim/ directory. This makes FRAIM discoverable
46
- * everywhere without requiring fraim init-project.
14
+ * - config.json — auth, mode, tokens (managed by setup.ts)
15
+ * - scripts/ — locally-executed scripts (synced during any fraim sync)
16
+ * - personalized-employee/ — user-level overrides and learnings
17
+ *
18
+ * Job/skill/rule stubs are NOT synced to user-level. The MCP proxy serves
19
+ * those on demand via list_fraim_jobs() and get_fraim_job(). Stubs only exist
20
+ * at project-level (fraim/) where CLAUDE.md tells agents to scan them on disk.
47
21
  *
48
22
  * Part of: User-Level FRAIM Artifacts with Local Shadow Semantics
49
23
  */
@@ -52,21 +26,17 @@ const path_1 = __importDefault(require("path"));
52
26
  const chalk_1 = __importDefault(require("chalk"));
53
27
  const script_sync_utils_1 = require("../utils/script-sync-utils");
54
28
  /**
55
- * Ensure the user-level FRAIM directory structure exists for content sync.
56
- * Creates directories for synced content and personalized overrides.
29
+ * Ensure the user-level FRAIM directory structure exists.
30
+ * Creates personalized-employee dirs for user-level overrides.
31
+ * Scripts dir is handled by existing script-sync-utils during any fraim sync.
57
32
  */
58
33
  function ensureUserLevelDirectories(userFraimDir) {
59
34
  const baseDir = userFraimDir || (0, script_sync_utils_1.getUserFraimDir)();
60
35
  const dirs = [
61
- path_1.default.join(baseDir, 'ai-employee', 'jobs'),
62
- path_1.default.join(baseDir, 'ai-employee', 'skills'),
63
- path_1.default.join(baseDir, 'ai-employee', 'rules'),
64
- path_1.default.join(baseDir, 'ai-manager', 'jobs'),
65
36
  path_1.default.join(baseDir, 'personalized-employee', 'jobs'),
66
37
  path_1.default.join(baseDir, 'personalized-employee', 'skills'),
67
38
  path_1.default.join(baseDir, 'personalized-employee', 'rules'),
68
39
  path_1.default.join(baseDir, 'personalized-employee', 'learnings'),
69
- path_1.default.join(baseDir, 'docs'),
70
40
  ];
71
41
  for (const dir of dirs) {
72
42
  if (!fs_1.default.existsSync(dir)) {
@@ -75,65 +45,15 @@ function ensureUserLevelDirectories(userFraimDir) {
75
45
  }
76
46
  }
77
47
  /**
78
- * Sync FRAIM artifacts (jobs, skills, rules, docs) from the remote server
79
- * to the user-level ~/.fraim/ directory.
80
- *
81
- * Uses the same remote sync endpoint as project-level sync, but writes
82
- * content directly to the user-level directory structure instead of under
83
- * a project's fraim/ subdirectory.
48
+ * Set up the user-level FRAIM directory.
49
+ * Creates the personalized-employee structure so FRAIM works outside any project.
84
50
  *
85
51
  * @param userFraimDir - Override for the target directory (for testing)
86
52
  */
87
53
  async function syncUserLevelArtifacts(userFraimDir) {
88
54
  const baseDir = userFraimDir || (0, script_sync_utils_1.getUserFraimDir)();
89
- console.log(chalk_1.default.blue('šŸ“¦ Syncing FRAIM content to user-level directory...'));
55
+ console.log(chalk_1.default.blue('šŸ“¦ Setting up user-level FRAIM directory...'));
90
56
  console.log(chalk_1.default.gray(` Target: ${baseDir}`));
91
- // Ensure directory structure exists
92
57
  ensureUserLevelDirectories(baseDir);
93
- // Try to sync from remote. If this fails (no network, no key),
94
- // we still have the directory structure in place.
95
- try {
96
- const { syncFromRemote } = await Promise.resolve().then(() => __importStar(require('../utils/remote-sync')));
97
- const apiKey = loadApiKeyFromConfig(baseDir);
98
- if (!apiKey) {
99
- console.log(chalk_1.default.yellow('āš ļø No API key found. User-level content sync skipped.'));
100
- console.log(chalk_1.default.gray(' Directory structure created. Content will sync on next fraim sync --global.'));
101
- return;
102
- }
103
- const result = await syncFromRemote({
104
- apiKey,
105
- projectRoot: baseDir,
106
- targetIsUserLevel: true,
107
- skipUpdates: true
108
- });
109
- if (result.success) {
110
- console.log(chalk_1.default.green(`āœ… Synced ${result.employeeJobsSynced} ai-employee jobs, ` +
111
- `${result.managerJobsSynced} ai-manager jobs, ` +
112
- `${result.skillsSynced} skills, ${result.rulesSynced} rules, ` +
113
- `${result.scriptsSynced} scripts, and ${result.docsSynced} docs to ~/.fraim/`));
114
- }
115
- else {
116
- console.log(chalk_1.default.yellow(`āš ļø User-level content sync failed: ${result.error}`));
117
- console.log(chalk_1.default.gray(' Directory structure created. Content will sync on next fraim sync --global.'));
118
- }
119
- }
120
- catch (error) {
121
- console.log(chalk_1.default.yellow(`āš ļø User-level content sync failed: ${error.message}`));
122
- console.log(chalk_1.default.gray(' Directory structure created. Content will sync on next fraim sync --global.'));
123
- }
124
- }
125
- /**
126
- * Load API key from the user-level config file.
127
- */
128
- function loadApiKeyFromConfig(userFraimDir) {
129
- const configPath = path_1.default.join(userFraimDir, 'config.json');
130
- if (!fs_1.default.existsSync(configPath))
131
- return undefined;
132
- try {
133
- const config = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
134
- return config.apiKey;
135
- }
136
- catch {
137
- return undefined;
138
- }
58
+ console.log(chalk_1.default.green('āœ… User-level FRAIM directory ready'));
139
59
  }
@@ -135,17 +135,8 @@ async function syncFromRemote(options) {
135
135
  error: 'No files received'
136
136
  };
137
137
  }
138
- // Helper: resolve path within the FRAIM content directory.
139
- // For user-level sync, write directly to projectRoot (which IS ~/.fraim/).
140
- // For project-level sync, use getWorkspaceFraimPath which prepends 'fraim/'.
141
- const resolveFraimPath = (...parts) => {
142
- if (options.targetIsUserLevel) {
143
- return (0, path_1.join)(options.projectRoot, ...parts);
144
- }
145
- return (0, project_fraim_paths_1.getWorkspaceFraimPath)(options.projectRoot, ...parts);
146
- };
147
- const lockTargets = options.targetIsUserLevel ? [] : getSyncedContentLockTargets(options.projectRoot);
148
- if (!options.targetIsUserLevel && shouldLockSyncedContent()) {
138
+ const lockTargets = getSyncedContentLockTargets(options.projectRoot);
139
+ if (shouldLockSyncedContent()) {
149
140
  // If previous sync locked these paths read-only, temporarily unlock before cleanup/write.
150
141
  for (const target of lockTargets) {
151
142
  setFileWriteLockRecursively(target, false);
@@ -155,7 +146,7 @@ async function syncFromRemote(options) {
155
146
  const allJobFiles = files.filter(f => f.type === 'job');
156
147
  const managerJobFiles = allJobFiles.filter(f => f.path.startsWith('ai-manager/'));
157
148
  const jobFiles = allJobFiles.filter(f => !f.path.startsWith('ai-manager/'));
158
- const employeeJobsDir = resolveFraimPath('ai-employee', 'jobs');
149
+ const employeeJobsDir = (0, project_fraim_paths_1.getWorkspaceFraimPath)(options.projectRoot, 'ai-employee', 'jobs');
159
150
  if (!(0, fs_1.existsSync)(employeeJobsDir)) {
160
151
  (0, fs_1.mkdirSync)(employeeJobsDir, { recursive: true });
161
152
  }
@@ -175,7 +166,7 @@ async function syncFromRemote(options) {
175
166
  console.log(chalk_1.default.gray(` + ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)(`ai-employee/jobs/${relPath}`)}`));
176
167
  }
177
168
  // Sync ai-manager job stubs to fraim/ai-manager/jobs/
178
- const managerJobsDir = resolveFraimPath('ai-manager', 'jobs');
169
+ const managerJobsDir = (0, project_fraim_paths_1.getWorkspaceFraimPath)(options.projectRoot, 'ai-manager', 'jobs');
179
170
  if (!(0, fs_1.existsSync)(managerJobsDir)) {
180
171
  (0, fs_1.mkdirSync)(managerJobsDir, { recursive: true });
181
172
  }
@@ -196,7 +187,7 @@ async function syncFromRemote(options) {
196
187
  }
197
188
  // Sync skill STUBS to fraim/ai-employee/skills/
198
189
  const skillFiles = files.filter(f => f.type === 'skill');
199
- const skillsDir = resolveFraimPath('ai-employee', 'skills');
190
+ const skillsDir = (0, project_fraim_paths_1.getWorkspaceFraimPath)(options.projectRoot, 'ai-employee', 'skills');
200
191
  if (!(0, fs_1.existsSync)(skillsDir)) {
201
192
  (0, fs_1.mkdirSync)(skillsDir, { recursive: true });
202
193
  }
@@ -216,7 +207,7 @@ async function syncFromRemote(options) {
216
207
  }
217
208
  // Sync rule STUBS to fraim/ai-employee/rules/
218
209
  const ruleFiles = files.filter(f => f.type === 'rule');
219
- const rulesDir = resolveFraimPath('ai-employee', 'rules');
210
+ const rulesDir = (0, project_fraim_paths_1.getWorkspaceFraimPath)(options.projectRoot, 'ai-employee', 'rules');
220
211
  if (!(0, fs_1.existsSync)(rulesDir)) {
221
212
  (0, fs_1.mkdirSync)(rulesDir, { recursive: true });
222
213
  }
@@ -255,7 +246,7 @@ async function syncFromRemote(options) {
255
246
  }
256
247
  // Sync docs to fraim/docs/
257
248
  const docsFiles = files.filter(f => f.type === 'docs');
258
- const docsDir = resolveFraimPath('docs');
249
+ const docsDir = (0, project_fraim_paths_1.getWorkspaceFraimPath)(options.projectRoot, 'docs');
259
250
  if (!(0, fs_1.existsSync)(docsDir)) {
260
251
  (0, fs_1.mkdirSync)(docsDir, { recursive: true });
261
252
  }
@@ -269,7 +260,7 @@ async function syncFromRemote(options) {
269
260
  (0, fs_1.writeFileSync)(filePath, file.content, 'utf8');
270
261
  console.log(chalk_1.default.gray(` + ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)(`docs/${file.path}`)}`));
271
262
  }
272
- if (!options.targetIsUserLevel && shouldLockSyncedContent()) {
263
+ if (shouldLockSyncedContent()) {
273
264
  for (const target of lockTargets) {
274
265
  setFileWriteLockRecursively(target, true);
275
266
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fraim-framework",
3
- "version": "2.0.106",
3
+ "version": "2.0.108",
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": {