fraim 2.0.128 → 2.0.130

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.
@@ -269,10 +269,13 @@ function readJobFrontmatter(filePath) {
269
269
  }
270
270
  }
271
271
  function findJobStubPath(projectPath, jobId) {
272
- // Walk the same layer set used by discoverEmployeeJobs (employee layers
273
- // are first-class jobs; manager layers are templates and don't carry
274
- // multi-phase frontmatter). Personalized layer wins over baseline.
275
- const layers = discoverLayers(projectPath, EMPLOYEE_JOB_LAYERS);
272
+ // Walk both employee and manager layer sets. The analytics surfaces can
273
+ // inspect any FRAIM job run, including manager-side jobs such as
274
+ // sleep-on-learnings.
275
+ const layers = discoverLayers(projectPath, [
276
+ ...EMPLOYEE_JOB_LAYERS,
277
+ ...MANAGER_JOB_LAYERS,
278
+ ]);
276
279
  let resolved = null;
277
280
  for (const layer of layers) {
278
281
  const candidate = path_1.default.join(layer.categoryDir, `${jobId}.md`);
@@ -13,7 +13,13 @@ const server_2 = require("../../ai-hub/server");
13
13
  function openBrowser(url) {
14
14
  try {
15
15
  if (process.platform === 'win32') {
16
- (0, child_process_1.spawn)('cmd.exe', ['/d', '/s', '/c', `start "" "${url}"`], { detached: true, stdio: 'ignore' }).unref();
16
+ // `cmd.exe /c start "" "<url>"` works in most cases but mis-parses on
17
+ // some Windows configurations (cmd treats the URL's `//` as a UNC
18
+ // path and the user sees a "Windows cannot find" dialog). The
19
+ // `rundll32 url.dll,FileProtocolHandler <url>` pattern is the
20
+ // documented protocol-handler entry point and bypasses cmd.exe
21
+ // entirely.
22
+ (0, child_process_1.spawn)('rundll32', ['url.dll,FileProtocolHandler', url], { detached: true, stdio: 'ignore' }).unref();
17
23
  return;
18
24
  }
19
25
  if (process.platform === 'darwin') {
@@ -47,6 +53,13 @@ const runFirstRun = async (options) => {
47
53
  if (!options.headless) {
48
54
  openBrowser(url);
49
55
  }
56
+ // Block forever — the wizard's /open-hub endpoint starts the Hub
57
+ // server in-process. Stopping the server here would kill the Hub.
58
+ // The user closes the terminal (Ctrl+C) when they're done. v2 (#355)
59
+ // replaces this in-process model with a detached launcher binary so
60
+ // the wizard CLI can exit cleanly while the Hub keeps running.
61
+ console.log(chalk_1.default.gray('When you finish the wizard, the Hub will be served from this terminal.'));
62
+ console.log(chalk_1.default.gray('Press Ctrl+C to stop everything when you are done.'));
50
63
  await server.waitForFinish();
51
64
  await server.stop();
52
65
  console.log(chalk_1.default.green('FRAIM first-run completed.'));
@@ -9,49 +9,16 @@ const fs_1 = __importDefault(require("fs"));
9
9
  const path_1 = __importDefault(require("path"));
10
10
  const os_1 = __importDefault(require("os"));
11
11
  const chalk_1 = __importDefault(require("chalk"));
12
- const prompts_1 = __importDefault(require("prompts"));
13
12
  const child_process_1 = require("child_process");
14
13
  const sync_1 = require("./sync");
15
14
  const platform_detection_1 = require("../utils/platform-detection");
16
- const version_utils_1 = require("../utils/version-utils");
17
15
  const ide_detector_1 = require("../setup/ide-detector");
18
16
  const codex_local_config_1 = require("../setup/codex-local-config");
19
17
  const claude_code_telemetry_1 = require("../setup/claude-code-telemetry");
20
- const provider_registry_1 = require("../providers/provider-registry");
21
18
  const fraim_gitignore_1 = require("../utils/fraim-gitignore");
22
- const config_writer_1 = require("../../core/config-writer");
23
19
  const agent_adapters_1 = require("../utils/agent-adapters");
24
20
  const project_fraim_paths_1 = require("../../core/utils/project-fraim-paths");
25
21
  const project_bootstrap_1 = require("../utils/project-bootstrap");
26
- const promptForJiraProjectKey = async (jiraBaseUrl) => {
27
- console.log(chalk_1.default.blue('\nJira Project Configuration'));
28
- console.log(chalk_1.default.gray(`Jira instance: ${jiraBaseUrl}`));
29
- console.log(chalk_1.default.gray('Enter the Jira project key for this repository (e.g., TEAM, PROJ, DEV)\n'));
30
- if (process.env.FRAIM_NON_INTERACTIVE) {
31
- const defaultKey = process.env.FRAIM_JIRA_PROJECT_KEY || 'PROJ';
32
- console.log(chalk_1.default.yellow(`\nNon-interactive mode: using Jira project key "${defaultKey}"`));
33
- return defaultKey;
34
- }
35
- const response = await (0, prompts_1.default)({
36
- type: 'text',
37
- name: 'projectKey',
38
- message: 'Jira Project Key:',
39
- validate: (value) => {
40
- if (!value || value.trim().length === 0) {
41
- return 'Project key is required';
42
- }
43
- if (!/^[A-Z][A-Z0-9]*$/.test(value.trim())) {
44
- return 'Project key must start with a letter and contain only uppercase letters and numbers';
45
- }
46
- return true;
47
- }
48
- });
49
- if (!response.projectKey) {
50
- console.log(chalk_1.default.red('\nJira project key is required for split mode'));
51
- process.exit(1);
52
- }
53
- return response.projectKey.trim().toUpperCase();
54
- };
55
22
  const checkGlobalSetup = () => {
56
23
  const fraimUserDir = process.env.FRAIM_USER_DIR || path_1.default.join(os_1.default.homedir(), '.fraim');
57
24
  const globalConfigPath = path_1.default.join(fraimUserDir, 'config.json');
@@ -158,6 +125,18 @@ const createGitHubLabels = (projectRoot) => {
158
125
  console.log(chalk_1.default.yellow(`Error reading labels.json: ${error.message}`));
159
126
  }
160
127
  };
128
+ function formatPlatformLabel(provider) {
129
+ switch (provider) {
130
+ case 'ado':
131
+ return 'Azure DevOps';
132
+ case 'github':
133
+ return 'GitHub';
134
+ case 'gitlab':
135
+ return 'GitLab';
136
+ default:
137
+ return provider.toUpperCase();
138
+ }
139
+ }
161
140
  const runInitProject = async (options = {}) => {
162
141
  console.log(chalk_1.default.blue('Initializing FRAIM project...'));
163
142
  const failHard = options.failHard ?? 'exit';
@@ -188,94 +167,7 @@ const runInitProject = async (options = {}) => {
188
167
  console.log(chalk_1.default.yellow(`${fraimDirDisplayPath} directory already exists`));
189
168
  (0, project_bootstrap_1.recordPathStatus)(result, fraimDirDisplayPath, false);
190
169
  }
191
- if (!fs_1.default.existsSync(configPath)) {
192
- const preferredMode = globalSetup.mode || 'integrated';
193
- result.mode = preferredMode;
194
- let config;
195
- if (preferredMode === 'conversational') {
196
- config = {
197
- version: (0, version_utils_1.getFraimVersion)(),
198
- project: {
199
- name: projectName
200
- },
201
- customizations: {}
202
- };
203
- console.log(chalk_1.default.blue(' conversational mode: no platform integration'));
204
- console.log(chalk_1.default.gray(` Project: ${projectName}`));
205
- }
206
- else {
207
- const detection = (0, platform_detection_1.detectPlatformFromGit)();
208
- if (detection.provider !== 'unknown' && detection.repository) {
209
- result.repositoryDetected = true;
210
- let issueTracking;
211
- const jiraConfig = globalSetup.providerConfigs?.jiraConfig;
212
- if (preferredMode === 'split' && globalSetup.tokens?.jira && jiraConfig?.baseUrl) {
213
- const projectKey = process.env.FRAIM_JIRA_PROJECT_KEY ||
214
- await promptForJiraProjectKey(jiraConfig.baseUrl);
215
- issueTracking = {
216
- provider: 'jira',
217
- baseUrl: jiraConfig.baseUrl,
218
- projectKey,
219
- email: jiraConfig.email
220
- };
221
- result.issueTrackingDetected = true;
222
- console.log(chalk_1.default.blue(` Code Repository: ${detection.provider.toUpperCase()}`));
223
- console.log(chalk_1.default.blue(` Issue Tracking: JIRA (${projectKey})`));
224
- }
225
- else {
226
- issueTracking = {
227
- provider: detection.provider,
228
- ...detection.repository
229
- };
230
- result.issueTrackingDetected = true;
231
- if (preferredMode === 'split') {
232
- result.mode = 'integrated';
233
- result.warnings.push('Split mode is not fully configured for this repo yet, so issue tracking fell back to the repository provider.');
234
- }
235
- const providerDef = await (0, provider_registry_1.getProvider)(detection.provider);
236
- console.log(chalk_1.default.blue(` Platform: ${providerDef?.displayName || detection.provider.toUpperCase()}`));
237
- }
238
- config = {
239
- version: (0, version_utils_1.getFraimVersion)(),
240
- project: {
241
- name: detection.repository.name || projectName
242
- },
243
- repository: detection.repository,
244
- issueTracking,
245
- customizations: {}
246
- };
247
- const repo = detection.repository;
248
- if (repo.owner && repo.name) {
249
- console.log(chalk_1.default.gray(` Repository: ${repo.owner}/${repo.name}`));
250
- }
251
- else if (repo.organization && repo.project && repo.name) {
252
- console.log(chalk_1.default.gray(` Organization: ${repo.organization}`));
253
- console.log(chalk_1.default.gray(` Project: ${repo.project}`));
254
- console.log(chalk_1.default.gray(` Repository: ${repo.name}`));
255
- }
256
- else if (repo.namespace && repo.name) {
257
- console.log(chalk_1.default.gray(` Namespace: ${repo.namespace || '(none)'}`));
258
- console.log(chalk_1.default.gray(` Repository: ${repo.name}`));
259
- }
260
- }
261
- else {
262
- config = {
263
- version: (0, version_utils_1.getFraimVersion)(),
264
- project: {
265
- name: projectName
266
- },
267
- customizations: {}
268
- };
269
- result.mode = 'conversational';
270
- result.warnings.push('No git remote detected. FRAIM fell back to conversational project setup.');
271
- console.log(chalk_1.default.yellow(' No git remote detected. Falling back to conversational mode.'));
272
- }
273
- }
274
- (0, config_writer_1.writeFraimConfig)(configPath, config);
275
- console.log(chalk_1.default.green(`Created ${configDisplayPath}`));
276
- (0, project_bootstrap_1.recordPathStatus)(result, configDisplayPath, true);
277
- }
278
- else {
170
+ if (fs_1.default.existsSync(configPath)) {
279
171
  (0, project_bootstrap_1.recordPathStatus)(result, configDisplayPath, false);
280
172
  try {
281
173
  const existingConfig = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
@@ -289,6 +181,48 @@ const runInitProject = async (options = {}) => {
289
181
  result.warnings.push(`Existing ${configDisplayPath} could not be parsed for summary details.`);
290
182
  }
291
183
  }
184
+ else {
185
+ const preferredMode = globalSetup.mode || 'integrated';
186
+ result.mode = preferredMode;
187
+ const detection = (0, platform_detection_1.detectPlatformFromGit)();
188
+ if (detection.provider !== 'unknown' && detection.repository) {
189
+ result.repositoryDetected = true;
190
+ result.projectName = detection.repository.name || projectName;
191
+ const jiraConfig = globalSetup.providerConfigs?.jiraConfig;
192
+ if (preferredMode === 'split' && globalSetup.tokens?.jira && jiraConfig?.baseUrl) {
193
+ result.issueTrackingDetected = true;
194
+ console.log(chalk_1.default.blue(` Code Repository: ${formatPlatformLabel(detection.provider)}`));
195
+ console.log(chalk_1.default.blue(' Issue Tracking: JIRA'));
196
+ }
197
+ else {
198
+ result.issueTrackingDetected = preferredMode !== 'conversational';
199
+ if (preferredMode === 'split') {
200
+ result.mode = 'integrated';
201
+ result.warnings.push('Split mode is not fully configured for this repo yet, so issue tracking will fall back to the repository provider during onboarding.');
202
+ }
203
+ console.log(chalk_1.default.blue(` Platform: ${formatPlatformLabel(detection.provider)}`));
204
+ }
205
+ const repo = detection.repository;
206
+ if (repo.owner && repo.name) {
207
+ console.log(chalk_1.default.gray(` Repository: ${repo.owner}/${repo.name}`));
208
+ }
209
+ else if (repo.organization && repo.project && repo.name) {
210
+ console.log(chalk_1.default.gray(` Organization: ${repo.organization}`));
211
+ console.log(chalk_1.default.gray(` Project: ${repo.project}`));
212
+ console.log(chalk_1.default.gray(` Repository: ${repo.name}`));
213
+ }
214
+ else if (repo.namespace && repo.name) {
215
+ console.log(chalk_1.default.gray(` Namespace: ${repo.namespace || '(none)'}`));
216
+ console.log(chalk_1.default.gray(` Repository: ${repo.name}`));
217
+ }
218
+ }
219
+ else {
220
+ result.mode = 'conversational';
221
+ result.warnings.push('No git remote detected. FRAIM fell back to conversational project setup.');
222
+ console.log(chalk_1.default.yellow(' No git remote detected. Falling back to conversational mode.'));
223
+ }
224
+ result.warnings.push(`${configDisplayPath} was not created by init-project. The manager \`project-onboarding\` job is the only supported config-writing path.`);
225
+ }
292
226
  ['jobs', 'ai-employee/jobs', 'ai-employee/skills', 'ai-manager/jobs', 'personalized-employee'].forEach((dir) => {
293
227
  const dirPath = path_1.default.join(fraimDir, dir);
294
228
  if (!fs_1.default.existsSync(dirPath)) {
@@ -45,11 +45,9 @@ const fs_1 = __importDefault(require("fs"));
45
45
  const path_1 = __importDefault(require("path"));
46
46
  const auto_mcp_setup_1 = require("../setup/auto-mcp-setup");
47
47
  const version_utils_1 = require("../utils/version-utils");
48
- const platform_detection_1 = require("../utils/platform-detection");
49
48
  const provider_client_1 = require("../api/provider-client");
50
49
  const provider_prompts_1 = require("../setup/provider-prompts");
51
50
  const provider_registry_1 = require("../providers/provider-registry");
52
- const init_project_1 = require("./init-project");
53
51
  const script_sync_utils_1 = require("../utils/script-sync-utils");
54
52
  function parseModeOption(mode) {
55
53
  if (mode === 'conversational' || mode === 'integrated' || mode === 'split') {
@@ -650,17 +648,6 @@ const runSetup = async (options) => {
650
648
  catch (e) {
651
649
  console.log(chalk_1.default.yellow(`⚠️ IDE integration encountered issues: ${e.message}`));
652
650
  }
653
- // Auto-run project init if we're in a git repo (only on initial setup)
654
- if (!isUpdate) {
655
- if ((0, platform_detection_1.isGitRepository)()) {
656
- console.log(chalk_1.default.blue('\n📁 Git repository detected — initializing project...'));
657
- await (0, init_project_1.runInitProject)();
658
- }
659
- else {
660
- console.log(chalk_1.default.yellow('\nℹ️ Not in a git repository — skipping project initialization.'));
661
- console.log(chalk_1.default.cyan(' To initialize a project later, cd into a repo and run: fraim init-project'));
662
- }
663
- }
664
651
  // Show mode-aware summary with clear value prop and next steps
665
652
  console.log(chalk_1.default.green('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
666
653
  console.log(chalk_1.default.green(' FRAIM is ready!'));
@@ -671,40 +658,78 @@ const runSetup = async (options) => {
671
658
  console.log(chalk_1.default.white(' your leadership gains clear optics on AI proficiency.'));
672
659
  console.log(chalk_1.default.gray('\n 120+ jobs across engineering, marketing, fundraising,'));
673
660
  console.log(chalk_1.default.gray(' legal, product, hiring, customer development, and more.'));
674
- // Show which IDEs were configured and how to use FRAIM in each
675
- const { detectInstalledIDEs: detectIDEs } = await Promise.resolve().then(() => __importStar(require('../setup/ide-detector')));
676
- const configuredIDEs = detectIDEs();
677
- if (configuredIDEs.length > 0) {
678
- const ideNames = configuredIDEs.map(ide => ide.name).join(', ');
679
- console.log(chalk_1.default.cyan(`\n FRAIM is configured for: `) + chalk_1.default.white(ideNames));
680
- console.log(chalk_1.default.cyan('\n Get started open any of those tools and:'));
661
+ // Ask how the user wants to work with their AI employees.
662
+ // Skip prompt when the user already chose on a previous run (R2.2/R3.2/R4.6).
663
+ const { writeSetupHandoffChoice, readSetupHandoffChoice } = await Promise.resolve().then(() => __importStar(require('../../core/utils/setup-preferences')));
664
+ const storedChoice = readSetupHandoffChoice();
665
+ let choice;
666
+ if (storedChoice) {
667
+ console.log(chalk_1.default.gray(`\n Using your saved preference: ${storedChoice === 'ide' ? 'In my IDE' : 'In FRAIM Hub'}`));
668
+ choice = storedChoice;
681
669
  }
682
670
  else {
683
- console.log(chalk_1.default.cyan('\n Get started open your AI tool and:'));
671
+ const userTypeResponse = await (0, prompts_1.default)({
672
+ type: 'select',
673
+ name: 'choice',
674
+ message: 'How would you like to work with your AI employees?',
675
+ choices: [
676
+ { title: 'In my IDE (Claude Code, Cursor, or another AI tool)', value: 'ide' },
677
+ { title: 'In FRAIM Hub (browser-based — no terminal needed)', value: 'hub' },
678
+ ],
679
+ });
680
+ choice = userTypeResponse.choice;
684
681
  }
685
- const { describeConfiguredInvocationSurfaces } = await Promise.resolve().then(() => __importStar(require('../setup/ide-global-integration')));
686
- const invocationSummaries = describeConfiguredInvocationSurfaces(configuredIDEs);
687
- invocationSummaries.forEach((summary) => {
688
- console.log(chalk_1.default.white(` ${summary}`));
689
- });
690
- console.log(chalk_1.default.white(' "What can FRAIM help me with?"'));
691
- console.log(chalk_1.default.gray('\n Just tell your AI what you need — FRAIM will find the right job.'));
692
- if (mode !== 'conversational') {
693
- console.log(chalk_1.default.cyan('\n To set up FRAIM in a specific project:'));
694
- console.log(chalk_1.default.white(' cd your-project && fraim init-project'));
695
- console.log(chalk_1.default.gray(' This enables project-specific customizations,'));
696
- console.log(chalk_1.default.gray(' GitHub workflows, and team learning.'));
697
- const configuredProviders = await Promise.all(Object.keys(tokens).map(async (id) => await (0, provider_registry_1.getProviderDisplayName)(id)));
698
- if (configuredProviders.length > 0) {
699
- console.log(chalk_1.default.gray(`\n Platforms: ${configuredProviders.join(', ')}`));
682
+ if (!choice)
683
+ return; // user cancelled (Ctrl+C)
684
+ writeSetupHandoffChoice(choice);
685
+ if (choice === 'ide') {
686
+ const { detectInstalledIDEs: detectIDEs } = await Promise.resolve().then(() => __importStar(require('../setup/ide-detector')));
687
+ const configuredIDEs = detectIDEs();
688
+ if (configuredIDEs.length > 0) {
689
+ const ideNames = configuredIDEs.map((ide) => ide.name).join(', ');
690
+ console.log(chalk_1.default.cyan(`\n FRAIM is configured for: `) + chalk_1.default.white(ideNames));
691
+ console.log(chalk_1.default.cyan('\n Open any of those tools and type:'));
700
692
  }
701
- const allProviderIds = await (0, provider_registry_1.getAllProviderIds)();
702
- const unconfiguredProviders = allProviderIds.filter(id => !tokens[id]);
703
- if (unconfiguredProviders.length > 0) {
704
- console.log(chalk_1.default.gray('\n To add more platforms later:'));
705
- unconfiguredProviders.forEach(id => {
706
- console.log(chalk_1.default.gray(` fraim setup --${id}`));
707
- });
693
+ else {
694
+ console.log(chalk_1.default.cyan('\n Open your AI tool and type:'));
695
+ }
696
+ const { describeConfiguredInvocationSurfaces } = await Promise.resolve().then(() => __importStar(require('../setup/ide-global-integration')));
697
+ const invocationSummaries = describeConfiguredInvocationSurfaces(configuredIDEs);
698
+ invocationSummaries.forEach((summary) => {
699
+ console.log(chalk_1.default.white(` ${summary}`));
700
+ });
701
+ console.log(chalk_1.default.white(' "What can FRAIM help me with?"'));
702
+ console.log(chalk_1.default.gray('\n Just tell your AI what you need — FRAIM will find the right job.'));
703
+ if (mode !== 'conversational') {
704
+ console.log(chalk_1.default.cyan('\n To set up FRAIM in a specific project:'));
705
+ console.log(chalk_1.default.white(' cd your-project && fraim init-project'));
706
+ console.log(chalk_1.default.gray(' This enables project-specific customizations,'));
707
+ console.log(chalk_1.default.gray(' GitHub workflows, and team learning.'));
708
+ const configuredProviders = await Promise.all(Object.keys(tokens).map(async (id) => await (0, provider_registry_1.getProviderDisplayName)(id)));
709
+ if (configuredProviders.length > 0) {
710
+ console.log(chalk_1.default.gray(`\n Platforms: ${configuredProviders.join(', ')}`));
711
+ }
712
+ const allProviderIds = await (0, provider_registry_1.getAllProviderIds)();
713
+ const unconfiguredProviders = allProviderIds.filter(id => !tokens[id]);
714
+ if (unconfiguredProviders.length > 0) {
715
+ console.log(chalk_1.default.gray('\n To add more platforms later:'));
716
+ unconfiguredProviders.forEach(id => {
717
+ console.log(chalk_1.default.gray(` fraim setup --${id}`));
718
+ });
719
+ }
720
+ }
721
+ }
722
+ else {
723
+ // Hub path: launch the Hub with firstRun=true and let the user pick a project there.
724
+ const { FirstRunSessionService } = await Promise.resolve().then(() => __importStar(require('../../first-run/session-service')));
725
+ const svc = new FirstRunSessionService({ key: 'setup-hub-launch', projectRoot: process.cwd() });
726
+ const result = await svc.openHub();
727
+ if (result.ok) {
728
+ console.log(chalk_1.default.green(`\n Hub is open at ${result.hubUrl}`));
729
+ console.log(chalk_1.default.gray(' Your AI employee will help you onboard your first project.'));
730
+ }
731
+ else {
732
+ console.log(chalk_1.default.yellow(`\n ${result.message}`));
708
733
  }
709
734
  }
710
735
  };
@@ -143,7 +143,14 @@ const runSync = async (options) => {
143
143
  console.log(chalk_1.default.cyan('Recommended: Use "npx fraim-framework@latest sync" instead.\n'));
144
144
  }
145
145
  const { syncFromRemote } = await Promise.resolve().then(() => __importStar(require('../utils/remote-sync')));
146
- if (options.local) {
146
+ // Allow `FRAIM_LOCAL_SYNC=1` to flip into local-mode without needing
147
+ // the --local CLI flag. The FRE's runProjectRow path doesn't surface
148
+ // a --local flag, but devs validating the FRE locally need a way to
149
+ // point sync at their localhost MCP server. With this env var set,
150
+ // any caller (including the FRE) routes through the local sync path
151
+ // exactly as if the user had passed --local.
152
+ const useLocal = options.local || process.env.FRAIM_LOCAL_SYNC === '1';
153
+ if (useLocal) {
147
154
  console.log(chalk_1.default.blue('Syncing FRAIM jobs from local server...'));
148
155
  const localPort = process.env.FRAIM_MCP_PORT ? parseInt(process.env.FRAIM_MCP_PORT) : (0, git_utils_1.getPort)();
149
156
  const localUrl = resolveExplicitLocalSyncUrl() || `http://localhost:${localPort}`;
@@ -0,0 +1,31 @@
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.workspaceConfigCommand = void 0;
7
+ exports.runWorkspaceConfigValidate = runWorkspaceConfigValidate;
8
+ const commander_1 = require("commander");
9
+ const fs_1 = require("fs");
10
+ const path_1 = __importDefault(require("path"));
11
+ const fraim_config_contract_1 = require("../../core/fraim-config-contract");
12
+ const project_fraim_paths_1 = require("../../core/utils/project-fraim-paths");
13
+ function resolveConfigPath(projectRoot, explicitPath) {
14
+ if (explicitPath) {
15
+ return path_1.default.resolve(explicitPath);
16
+ }
17
+ return (0, project_fraim_paths_1.getWorkspaceConfigPath)(projectRoot ? path_1.default.resolve(projectRoot) : process.cwd());
18
+ }
19
+ function runWorkspaceConfigValidate(options) {
20
+ const configPath = resolveConfigPath(options.projectRoot, options.configPath);
21
+ const rawConfig = JSON.parse((0, fs_1.readFileSync)(configPath, 'utf8'));
22
+ (0, fraim_config_contract_1.assertValidWorkspaceFraimConfig)(rawConfig);
23
+ console.log(`Validated ${path_1.default.relative(process.cwd(), configPath) || (0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('config.json')}`);
24
+ }
25
+ exports.workspaceConfigCommand = new commander_1.Command('workspace-config')
26
+ .description('Deterministically validate workspace fraim/config.json')
27
+ .addCommand(new commander_1.Command('validate')
28
+ .description('Validate fraim/config.json against the shared workspace config contract')
29
+ .option('--project-root <path>', 'Project root used to resolve fraim/config.json')
30
+ .option('--config-path <path>', 'Explicit path to fraim/config.json')
31
+ .action((options) => runWorkspaceConfigValidate(options)));
@@ -52,6 +52,7 @@ const mcp_1 = require("./commands/mcp");
52
52
  const migrate_project_fraim_1 = require("./commands/migrate-project-fraim");
53
53
  const hub_1 = require("./commands/hub");
54
54
  const first_run_1 = require("./commands/first-run");
55
+ const workspace_config_1 = require("./commands/workspace-config");
55
56
  const fs_1 = __importDefault(require("fs"));
56
57
  const path_1 = __importDefault(require("path"));
57
58
  const program = new commander_1.Command();
@@ -93,6 +94,7 @@ program.addCommand(mcp_1.mcpCommand);
93
94
  program.addCommand(migrate_project_fraim_1.migrateProjectFraimCommand);
94
95
  program.addCommand(hub_1.hubCommand);
95
96
  program.addCommand(first_run_1.firstRunCommand);
97
+ program.addCommand(workspace_config_1.workspaceConfigCommand);
96
98
  // Wait for async command initialization before parsing
97
99
  (async () => {
98
100
  // Import the initialization promise from setup command
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.describeConfiguredInvocationSurfaces = describeConfiguredInvocationSurfaces;
7
+ exports.describeOnboardingInvocationSurfaces = describeOnboardingInvocationSurfaces;
7
8
  exports.installSlashCommands = installSlashCommands;
8
9
  exports.installGlobalRules = installGlobalRules;
9
10
  /**
@@ -20,6 +21,24 @@ const ide_invocation_surfaces_1 = require("./ide-invocation-surfaces");
20
21
  function describeConfiguredInvocationSurfaces(installedIDEs) {
21
22
  return installedIDEs.map((ide) => (0, ide_invocation_surfaces_1.describeInvocationSurface)(ide.name, ide.invocationProfile));
22
23
  }
24
+ // Returns IDE-specific onboarding commands for the post-setup routing screen.
25
+ // Maps each IDE's invocation prefix to the project-onboarding task string so
26
+ // the user sees the exact command to type, not a generic FRAIM invocation.
27
+ function describeOnboardingInvocationSurfaces(installedIDEs) {
28
+ if (installedIDEs.length === 0)
29
+ return ['/fraim onboard this project'];
30
+ return installedIDEs.map((ide) => {
31
+ const profile = ide.invocationProfile;
32
+ if (profile === 'cursor-mention')
33
+ return `${ide.name}: @fraim onboard this project`;
34
+ if (profile === 'launch-phrase')
35
+ return `${ide.name}: onboard this project`;
36
+ if (profile === 'instructions-only')
37
+ return `${ide.name}: "onboard this project"`;
38
+ // claude-slash, vscode-prompt, codex-skill, windsurf-command, kiro-hashtag, gemini-command
39
+ return `${ide.name}: /fraim onboard this project`;
40
+ });
41
+ }
23
42
  /**
24
43
  * Install Claude FRAIM discovery artifacts at the user level.
25
44
  * Writes a skill to ~/.claude/skills/fraim/SKILL.md and a compatibility command
@@ -69,6 +69,10 @@ function ensureUserLevelDependencies(userFraimDir) {
69
69
  const baseDir = userFraimDir || (0, script_sync_utils_1.getUserFraimDir)();
70
70
  const pkgPath = path_1.default.join(baseDir, 'package.json');
71
71
  const nodeModulesDir = path_1.default.join(baseDir, 'node_modules');
72
+ const childEnv = { ...process.env };
73
+ delete childEnv.NODE_CHANNEL_FD;
74
+ delete childEnv.NODE_CHANNEL_SERIALIZATION_MODE;
75
+ delete childEnv.NODE_UNIQUE_ID;
72
76
  let pkg = {};
73
77
  if (fs_1.default.existsSync(pkgPath)) {
74
78
  try {
@@ -92,6 +96,7 @@ function ensureUserLevelDependencies(userFraimDir) {
92
96
  (0, child_process_1.execSync)('npm install --no-audit --no-fund --no-save --no-package-lock --omit=dev', {
93
97
  cwd: baseDir,
94
98
  stdio: 'pipe',
99
+ env: childEnv,
95
100
  });
96
101
  console.log(chalk_1.default.green(`✅ Installed: ${missing.join(', ')}`));
97
102
  }
@@ -22,11 +22,11 @@ function formatModeLabel(mode) {
22
22
  function getModeSpecificNextStep(mode) {
23
23
  switch (mode) {
24
24
  case 'conversational':
25
- return `The agent will focus on project context, validation commands, and durable repo rules. For a walkthrough, watch the onboarding videos at ${ONBOARDING_VIDEO_PLAYLIST_URL}.`;
25
+ return `The agent will create fraim/config.json during onboarding, then focus on project context, validation commands, and durable repo rules. For a walkthrough, watch the onboarding videos at ${ONBOARDING_VIDEO_PLAYLIST_URL}.`;
26
26
  case 'split':
27
- return `The agent will confirm the code-host and issue-tracker split, then ask only for the missing project details. For a walkthrough, watch the onboarding videos at ${ONBOARDING_VIDEO_PLAYLIST_URL}.`;
27
+ return `The agent will create fraim/config.json during onboarding, confirm the code-host and issue-tracker split, then ask only for the missing project details. For a walkthrough, watch the onboarding videos at ${ONBOARDING_VIDEO_PLAYLIST_URL}.`;
28
28
  default:
29
- return `The agent will review the detected repo setup, then ask only for the highest-value missing project details. For a walkthrough, watch the onboarding videos at ${ONBOARDING_VIDEO_PLAYLIST_URL}.`;
29
+ return `The agent will create fraim/config.json during onboarding, review the detected repo setup, then ask only for the highest-value missing project details. For a walkthrough, watch the onboarding videos at ${ONBOARDING_VIDEO_PLAYLIST_URL}.`;
30
30
  }
31
31
  }
32
32
  function createInitProjectResult(projectName, mode) {