fraim-framework 2.0.124 → 2.0.127

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.
Files changed (46) hide show
  1. package/bin/fraim.js +1 -1
  2. package/dist/src/ai-hub/catalog.js +280 -44
  3. package/dist/src/ai-hub/desktop-main.js +2 -2
  4. package/dist/src/ai-hub/hosts.js +384 -10
  5. package/dist/src/ai-hub/server.js +255 -9
  6. package/dist/src/cli/commands/add-ide.js +4 -3
  7. package/dist/src/cli/commands/first-run.js +61 -0
  8. package/dist/src/cli/commands/hub.js +4 -4
  9. package/dist/src/cli/commands/init-project.js +4 -4
  10. package/dist/src/cli/commands/setup.js +4 -3
  11. package/dist/src/cli/commands/sync.js +21 -2
  12. package/dist/src/cli/doctor/checks/ide-config-checks.js +20 -2
  13. package/dist/src/cli/fraim.js +2 -0
  14. package/dist/src/cli/mcp/ide-formats.js +29 -1
  15. package/dist/src/cli/mcp/mcp-server-registry.js +1 -0
  16. package/dist/src/cli/setup/auto-mcp-setup.js +14 -8
  17. package/dist/src/cli/setup/ide-detector.js +32 -1
  18. package/dist/src/cli/setup/ide-global-integration.js +5 -1
  19. package/dist/src/cli/setup/ide-invocation-surfaces.js +70 -17
  20. package/dist/src/cli/setup/mcp-config-generator.js +12 -1
  21. package/dist/src/cli/utils/agent-adapters.js +12 -2
  22. package/dist/src/cli/utils/project-bootstrap.js +4 -3
  23. package/dist/src/core/quality-evidence.js +81 -8
  24. package/dist/src/core/utils/git-utils.js +32 -7
  25. package/dist/src/core/utils/job-aliases.js +47 -0
  26. package/dist/src/core/utils/workflow-parser.js +3 -5
  27. package/dist/src/first-run/install-state.js +68 -0
  28. package/dist/src/first-run/server.js +153 -0
  29. package/dist/src/first-run/session-service.js +302 -0
  30. package/dist/src/first-run/types.js +40 -0
  31. package/dist/src/local-mcp-server/agent-token-prices.js +114 -0
  32. package/dist/src/local-mcp-server/codex-token-adapter.js +232 -0
  33. package/dist/src/local-mcp-server/learning-context-builder.js +21 -8
  34. package/dist/src/local-mcp-server/otlp-metrics-receiver.js +7 -1
  35. package/dist/src/local-mcp-server/stdio-server.js +70 -17
  36. package/dist/src/local-mcp-server/token-adapter-registry.js +64 -0
  37. package/dist/src/local-mcp-server/usage-collector.js +25 -0
  38. package/index.js +83 -83
  39. package/package.json +7 -1
  40. package/public/ai-hub/index.html +149 -102
  41. package/public/ai-hub/script.js +1154 -271
  42. package/public/ai-hub/styles.css +753 -450
  43. package/public/first-run/index.html +221 -0
  44. package/public/first-run/script.js +361 -0
  45. package/dist/src/cli/services/device-flow-service.js +0 -83
  46. package/dist/src/local-mcp-server/prometheus-scraper.js +0 -152
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEPRECATED_FRAIM_JOB_NAMES = void 0;
4
+ exports.resolveFraimJobName = resolveFraimJobName;
5
+ exports.isListableFraimJob = isListableFraimJob;
6
+ const DEPRECATED_TO_CANONICAL_JOB_MAP = {
7
+ 'learn-and-scale': 'upskill-employee',
8
+ 'model-behavior': 'upskill-employee',
9
+ 'promote-learning': 'upskill-employee',
10
+ 'refine-jobs': 'upskill-employee',
11
+ 'refine-skills': 'upskill-employee'
12
+ };
13
+ const DIRECT_JOB_ALIASES = {
14
+ 'sleep on learnings': 'sleep-on-learnings'
15
+ };
16
+ exports.DEPRECATED_FRAIM_JOB_NAMES = new Set(Object.keys(DEPRECATED_TO_CANONICAL_JOB_MAP));
17
+ function normalizeJobLookupInput(input) {
18
+ return input.trim().toLowerCase().replace(/[_\s]+/g, '-');
19
+ }
20
+ function resolveFraimJobName(input) {
21
+ const normalizedJobName = normalizeJobLookupInput(input);
22
+ const directAliasTarget = DIRECT_JOB_ALIASES[input.trim().toLowerCase()];
23
+ if (directAliasTarget) {
24
+ return {
25
+ requestedJobName: input,
26
+ normalizedJobName,
27
+ canonicalJobName: directAliasTarget
28
+ };
29
+ }
30
+ const deprecatedAliasTarget = DEPRECATED_TO_CANONICAL_JOB_MAP[normalizedJobName];
31
+ if (deprecatedAliasTarget) {
32
+ return {
33
+ requestedJobName: input,
34
+ normalizedJobName,
35
+ canonicalJobName: deprecatedAliasTarget,
36
+ deprecatedAliasTarget
37
+ };
38
+ }
39
+ return {
40
+ requestedJobName: input,
41
+ normalizedJobName,
42
+ canonicalJobName: normalizedJobName
43
+ };
44
+ }
45
+ function isListableFraimJob(jobName) {
46
+ return !exports.DEPRECATED_FRAIM_JOB_NAMES.has(normalizeJobLookupInput(jobName));
47
+ }
@@ -5,15 +5,13 @@ const fs_1 = require("fs");
5
5
  const path_1 = require("path");
6
6
  class WorkflowParser {
7
7
  static extractMetadataBlock(content) {
8
- // Allow leading comments and whitespace before frontmatter
9
- const frontmatterMatch = content.match(/^[\s\S]*?---\r?\n([\s\S]+?)\r?\n---/);
8
+ const frontmatterMatch = content.match(/^---\r?\n([\s\S]+?)\r?\n---/);
10
9
  if (frontmatterMatch) {
11
10
  try {
12
- const startIndex = frontmatterMatch.index || 0;
13
11
  return {
14
12
  state: 'valid',
15
13
  metadata: JSON.parse(frontmatterMatch[1]),
16
- bodyStartIndex: startIndex + frontmatterMatch[0].length
14
+ bodyStartIndex: frontmatterMatch[0].length
17
15
  };
18
16
  }
19
17
  catch {
@@ -109,7 +107,7 @@ class WorkflowParser {
109
107
  overview = contentAfterMetadata;
110
108
  }
111
109
  const phases = new Map();
112
- const phaseSections = restOfContent.split(/^##\s+Phase:\s*/im);
110
+ const phaseSections = restOfContent.split(/^##\s+Phase:\s+/m);
113
111
  if (!metadata.phases) {
114
112
  metadata.phases = {};
115
113
  }
@@ -0,0 +1,68 @@
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.createInitialFirstRunState = createInitialFirstRunState;
7
+ exports.loadFirstRunState = loadFirstRunState;
8
+ exports.saveFirstRunState = saveFirstRunState;
9
+ exports.clearFirstRunState = clearFirstRunState;
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const path_1 = __importDefault(require("path"));
12
+ const script_sync_utils_1 = require("../cli/utils/script-sync-utils");
13
+ const types_1 = require("./types");
14
+ function getStatePath() {
15
+ return path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'install-state.json');
16
+ }
17
+ function ensureUserDir() {
18
+ fs_1.default.mkdirSync((0, script_sync_utils_1.getUserFraimDir)(), { recursive: true });
19
+ }
20
+ function maskInstallKey(key) {
21
+ const normalized = key.trim();
22
+ if (normalized.length <= 8) {
23
+ return '<redacted>';
24
+ }
25
+ return `${normalized.slice(0, 4)}...${normalized.slice(-4)}`;
26
+ }
27
+ function createInitialFirstRunState(key) {
28
+ const now = new Date().toISOString();
29
+ return {
30
+ version: 1,
31
+ installKeyRef: maskInstallKey(key),
32
+ platform: process.platform,
33
+ detectedAgents: [],
34
+ configuredAgents: [],
35
+ restartDeferredAgents: [],
36
+ resourcesUrl: types_1.FIRST_RUN_RESOURCES_URL,
37
+ stepStates: (0, types_1.createDefaultStepStates)(),
38
+ createdAt: now,
39
+ updatedAt: now,
40
+ };
41
+ }
42
+ function loadFirstRunState() {
43
+ const statePath = getStatePath();
44
+ if (!fs_1.default.existsSync(statePath)) {
45
+ return null;
46
+ }
47
+ try {
48
+ const state = JSON.parse(fs_1.default.readFileSync(statePath, 'utf8'));
49
+ if (typeof state.installKeyRef === 'string' && !state.installKeyRef.includes('...')) {
50
+ state.installKeyRef = maskInstallKey(state.installKeyRef);
51
+ }
52
+ return state;
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ }
58
+ function saveFirstRunState(state) {
59
+ ensureUserDir();
60
+ state.updatedAt = new Date().toISOString();
61
+ fs_1.default.writeFileSync(getStatePath(), JSON.stringify(state, null, 2));
62
+ }
63
+ function clearFirstRunState() {
64
+ const statePath = getStatePath();
65
+ if (fs_1.default.existsSync(statePath)) {
66
+ fs_1.default.unlinkSync(statePath);
67
+ }
68
+ }
@@ -0,0 +1,153 @@
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.FirstRunServer = void 0;
7
+ const express_1 = __importDefault(require("express"));
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const child_process_1 = require("child_process");
11
+ function resolveFirstRunPublicDir() {
12
+ const candidates = [
13
+ path_1.default.resolve(process.cwd(), 'public/first-run'),
14
+ path_1.default.resolve(__dirname, '..', '..', 'public/first-run'),
15
+ path_1.default.resolve(__dirname, '..', '..', '..', 'public/first-run'),
16
+ ];
17
+ for (const candidate of candidates) {
18
+ if (fs_1.default.existsSync(candidate)) {
19
+ return candidate;
20
+ }
21
+ }
22
+ throw new Error('Could not locate public/first-run assets.');
23
+ }
24
+ function pickProjectPath() {
25
+ if (process.platform === 'win32') {
26
+ const script = [
27
+ 'Add-Type -AssemblyName System.Windows.Forms',
28
+ '$dialog = New-Object System.Windows.Forms.FolderBrowserDialog',
29
+ '$dialog.ShowNewFolderButton = $true',
30
+ 'if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {',
31
+ ' Write-Output $dialog.SelectedPath',
32
+ '}',
33
+ ].join('; ');
34
+ const result = (0, child_process_1.spawnSync)('powershell', ['-NoProfile', '-Command', script], {
35
+ encoding: 'utf8',
36
+ });
37
+ return result.status === 0 ? result.stdout.trim() || null : null;
38
+ }
39
+ if (process.platform === 'darwin') {
40
+ const result = (0, child_process_1.spawnSync)('osascript', ['-e', 'POSIX path of (choose folder with prompt "Select a FRAIM project folder")'], {
41
+ encoding: 'utf8',
42
+ });
43
+ return result.status === 0 ? result.stdout.trim() || null : null;
44
+ }
45
+ const result = (0, child_process_1.spawnSync)('bash', ['-lc', 'zenity --file-selection --directory 2>/dev/null || kdialog --getexistingdirectory 2>/dev/null'], {
46
+ encoding: 'utf8',
47
+ });
48
+ return result.status === 0 ? result.stdout.trim() || null : null;
49
+ }
50
+ class FirstRunServer {
51
+ constructor(options) {
52
+ this.app = (0, express_1.default)();
53
+ this.sessionService = options.sessionService;
54
+ this.finishPromise = new Promise((resolve) => {
55
+ this.finishResolver = resolve;
56
+ });
57
+ this.app.use(express_1.default.json());
58
+ this.app.use('/api/first-run', (req, res, next) => {
59
+ if (req.method === 'GET') {
60
+ return next();
61
+ }
62
+ const requestToken = req.header('x-fraim-first-run-token');
63
+ if (requestToken !== this.sessionService.getRequestToken()) {
64
+ return res.status(403).json({ error: 'Invalid first-run session token.' });
65
+ }
66
+ return next();
67
+ });
68
+ this.app.use('/first-run', express_1.default.static(resolveFirstRunPublicDir()));
69
+ this.app.get('/health', (_req, res) => {
70
+ res.json({ status: 'ok', service: 'fraim-first-run' });
71
+ });
72
+ this.registerRoutes();
73
+ }
74
+ async start(port) {
75
+ await new Promise((resolve, reject) => {
76
+ this.httpServer = this.app.listen(port, '127.0.0.1');
77
+ this.httpServer.once('listening', () => resolve());
78
+ this.httpServer.once('error', (error) => reject(error));
79
+ });
80
+ }
81
+ async waitForFinish() {
82
+ await this.finishPromise;
83
+ }
84
+ async stop() {
85
+ if (!this.httpServer) {
86
+ return;
87
+ }
88
+ await new Promise((resolve, reject) => {
89
+ this.httpServer.close((error) => {
90
+ if (error) {
91
+ reject(error);
92
+ return;
93
+ }
94
+ resolve();
95
+ });
96
+ });
97
+ this.httpServer = undefined;
98
+ }
99
+ registerRoutes() {
100
+ this.app.get('/api/first-run/session', (_req, res) => {
101
+ res.json(this.sessionService.getSession());
102
+ });
103
+ this.app.post('/api/first-run/prereqs', (_req, res) => {
104
+ res.json(this.sessionService.runPrereqChecks());
105
+ });
106
+ this.app.post('/api/first-run/agents/select', (req, res) => {
107
+ if (!req.body.agentId) {
108
+ return res.status(400).json({ error: 'agentId is required.' });
109
+ }
110
+ return res.json(this.sessionService.selectAgent(req.body.agentId));
111
+ });
112
+ this.app.post('/api/first-run/configure', async (_req, res) => {
113
+ try {
114
+ return res.json(await this.sessionService.configureFraim());
115
+ }
116
+ catch (error) {
117
+ return res.status(500).json({ error: error instanceof Error ? error.message : 'Could not configure FRAIM.' });
118
+ }
119
+ });
120
+ this.app.post('/api/first-run/project-path/pick', (_req, res) => {
121
+ try {
122
+ const selectedPath = pickProjectPath();
123
+ if (!selectedPath) {
124
+ return res.status(204).end();
125
+ }
126
+ return res.json({ path: selectedPath });
127
+ }
128
+ catch (error) {
129
+ return res.status(500).json({ error: error instanceof Error ? error.message : 'Could not open the folder picker.' });
130
+ }
131
+ });
132
+ this.app.post('/api/first-run/project', async (req, res) => {
133
+ if (!req.body.projectPath) {
134
+ return res.status(400).json({ error: 'projectPath is required.' });
135
+ }
136
+ try {
137
+ return res.json(await this.sessionService.initializeProject(req.body.projectPath, req.body.initializeGit !== false));
138
+ }
139
+ catch (error) {
140
+ return res.status(500).json({ error: error instanceof Error ? error.message : 'Could not initialize the project.' });
141
+ }
142
+ });
143
+ this.app.post('/api/first-run/launch', (_req, res) => {
144
+ return res.json(this.sessionService.launchAndProbe());
145
+ });
146
+ this.app.post('/api/first-run/finish', (_req, res) => {
147
+ const result = this.sessionService.finish();
148
+ this.finishResolver?.();
149
+ return res.json(result);
150
+ });
151
+ }
152
+ }
153
+ exports.FirstRunServer = FirstRunServer;
@@ -0,0 +1,302 @@
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.FirstRunSessionService = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const os_1 = __importDefault(require("os"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const crypto_1 = __importDefault(require("crypto"));
11
+ const child_process_1 = require("child_process");
12
+ const ide_detector_1 = require("../cli/setup/ide-detector");
13
+ const auto_mcp_setup_1 = require("../cli/setup/auto-mcp-setup");
14
+ const setup_1 = require("../cli/commands/setup");
15
+ const init_project_1 = require("../cli/commands/init-project");
16
+ const script_sync_utils_1 = require("../cli/utils/script-sync-utils");
17
+ const types_1 = require("./types");
18
+ const install_state_1 = require("./install-state");
19
+ function commandExists(command) {
20
+ const executable = process.platform === 'win32' ? 'cmd.exe' : command;
21
+ const args = process.platform === 'win32'
22
+ ? ['/d', '/s', '/c', `${command} --version`]
23
+ : ['--version'];
24
+ const result = (0, child_process_1.spawnSync)(executable, args, {
25
+ encoding: 'utf8',
26
+ stdio: ['ignore', 'ignore', 'ignore'],
27
+ shell: false,
28
+ });
29
+ return result.status === 0;
30
+ }
31
+ function ensureOutputDirs() {
32
+ fs_1.default.mkdirSync((0, script_sync_utils_1.getUserFraimDir)(), { recursive: true });
33
+ fs_1.default.mkdirSync(path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'last-install'), { recursive: true });
34
+ }
35
+ function getNextPromptPath() {
36
+ return path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'last-install', 'next-prompt.txt');
37
+ }
38
+ function getInstallLogPath() {
39
+ return path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'install-log.txt');
40
+ }
41
+ function appendInstallLog(line) {
42
+ ensureOutputDirs();
43
+ fs_1.default.appendFileSync(getInstallLogPath(), `[${new Date().toISOString()}] ${line}${os_1.default.EOL}`);
44
+ }
45
+ function getLaunchInstruction(agentId, projectRoot) {
46
+ const cwd = projectRoot || process.cwd();
47
+ switch (agentId) {
48
+ case 'claude-code':
49
+ return `cd "${cwd}" && claude`;
50
+ case 'codex':
51
+ return `cd "${cwd}" && codex`;
52
+ case 'gemini-cli':
53
+ return `cd "${cwd}" && gemini`;
54
+ default:
55
+ return `cd "${cwd}"`;
56
+ }
57
+ }
58
+ function maybeLaunchInteractiveShell(agentId, projectRoot) {
59
+ const commandLine = getLaunchInstruction(agentId, projectRoot);
60
+ try {
61
+ if (process.platform === 'win32') {
62
+ (0, child_process_1.spawn)('cmd.exe', ['/d', '/s', '/c', `start "" cmd /k ${commandLine}`], {
63
+ detached: true,
64
+ stdio: 'ignore',
65
+ }).unref();
66
+ return true;
67
+ }
68
+ if (process.platform === 'darwin') {
69
+ (0, child_process_1.spawn)('open', ['-a', 'Terminal', projectRoot], {
70
+ detached: true,
71
+ stdio: 'ignore',
72
+ }).unref();
73
+ return true;
74
+ }
75
+ (0, child_process_1.spawn)('x-terminal-emulator', ['-e', 'bash', '-lc', commandLine], {
76
+ detached: true,
77
+ stdio: 'ignore',
78
+ }).unref();
79
+ return true;
80
+ }
81
+ catch {
82
+ return false;
83
+ }
84
+ }
85
+ function probeCodex(projectRoot) {
86
+ const result = (0, child_process_1.spawnSync)('codex', ['exec', '--json', '--skip-git-repo-check', '--dangerously-bypass-approvals-and-sandbox'], {
87
+ cwd: projectRoot,
88
+ input: 'Reply exactly "FRAIM_READY" if FRAIM is available in this workspace. Otherwise reply exactly "NOT_READY".',
89
+ encoding: 'utf8',
90
+ timeout: 60000,
91
+ });
92
+ const output = `${result.stdout || ''}${result.stderr || ''}`.trim();
93
+ return { ok: result.status === 0 && output.toUpperCase().includes('FRAIM'), output };
94
+ }
95
+ function probeClaude(projectRoot) {
96
+ const result = (0, child_process_1.spawnSync)('claude', ['-p', '--verbose', '--output-format', 'stream-json', '--dangerously-skip-permissions'], {
97
+ cwd: projectRoot,
98
+ input: 'Reply exactly "FRAIM_READY" if FRAIM is available in this workspace. Otherwise reply exactly "NOT_READY".',
99
+ encoding: 'utf8',
100
+ timeout: 60000,
101
+ });
102
+ const output = `${result.stdout || ''}${result.stderr || ''}`.trim();
103
+ return { ok: result.status === 0 && output.toUpperCase().includes('FRAIM'), output };
104
+ }
105
+ function probeGemini(projectRoot) {
106
+ const result = (0, child_process_1.spawnSync)('gemini', ['--help'], {
107
+ cwd: projectRoot,
108
+ encoding: 'utf8',
109
+ timeout: 30000,
110
+ });
111
+ const output = `${result.stdout || ''}${result.stderr || ''}`.trim();
112
+ return {
113
+ ok: result.status === 0,
114
+ output: output || 'Gemini CLI responded to --help; runtime FRAIM probe still requires manual confirmation.',
115
+ };
116
+ }
117
+ class FirstRunSessionService {
118
+ constructor(options) {
119
+ this.key = options.key;
120
+ this.headless = options.headless === true;
121
+ this.fakeMode = process.env.FRAIM_FIRST_RUN_FAKE === '1';
122
+ this.requestToken = crypto_1.default.randomUUID();
123
+ this.state = options.resume ? (0, install_state_1.loadFirstRunState)() || (0, install_state_1.createInitialFirstRunState)(options.key) : (0, install_state_1.createInitialFirstRunState)(options.key);
124
+ if (options.projectRoot) {
125
+ this.state.workspacePath = path_1.default.resolve(options.projectRoot);
126
+ this.state.stepStates.project = 'running';
127
+ (0, install_state_1.saveFirstRunState)(this.state);
128
+ }
129
+ }
130
+ persist() {
131
+ (0, install_state_1.saveFirstRunState)(this.state);
132
+ }
133
+ markStep(step, status) {
134
+ this.state.stepStates[step] = status;
135
+ this.persist();
136
+ }
137
+ detectAgents() {
138
+ if (this.fakeMode) {
139
+ return types_1.FIRST_RUN_AGENT_OPTIONS.map((agent) => ({
140
+ id: agent.id,
141
+ label: agent.label,
142
+ detected: true,
143
+ detail: 'Fake test-mode agent available.',
144
+ }));
145
+ }
146
+ const detected = (0, ide_detector_1.detectInstalledIDEs)();
147
+ return types_1.FIRST_RUN_AGENT_OPTIONS.map((agent) => {
148
+ const ide = detected.find((entry) => entry.aliases?.some((alias) => agent.detectAliases.includes(alias)) ||
149
+ agent.detectAliases.some((alias) => entry.name.toLowerCase().includes(alias)));
150
+ return {
151
+ id: agent.id,
152
+ label: agent.label,
153
+ detected: Boolean(ide) || commandExists(agent.launchCommand),
154
+ detail: ide ? `Config path: ${ide.configPath}` : 'CLI/config not detected yet.',
155
+ };
156
+ });
157
+ }
158
+ getRequestToken() {
159
+ return this.requestToken;
160
+ }
161
+ getSession() {
162
+ const agents = this.detectAgents();
163
+ this.state.detectedAgents = agents.filter((agent) => agent.detected).map((agent) => agent.id);
164
+ this.persist();
165
+ return {
166
+ state: this.state,
167
+ agents,
168
+ prompt: types_1.FIRST_RUN_PROMPT,
169
+ headless: this.headless,
170
+ requestToken: this.requestToken,
171
+ };
172
+ }
173
+ runPrereqChecks() {
174
+ this.markStep('prereqs', 'running');
175
+ const checks = [
176
+ { label: 'node', ok: commandExists('node') },
177
+ { label: 'npx', ok: commandExists('npx') },
178
+ { label: 'git', ok: commandExists('git') },
179
+ ];
180
+ const failures = checks.filter((check) => !check.ok).map((check) => check.label);
181
+ const message = failures.length === 0
182
+ ? 'Node.js, npx, and git are available.'
183
+ : `Missing prerequisites: ${failures.join(', ')}.`;
184
+ appendInstallLog(`prereq-check ${message}`);
185
+ this.markStep('prereqs', failures.length === 0 ? 'complete' : 'failed');
186
+ if (failures.length === 0) {
187
+ this.markStep('agent', 'running');
188
+ }
189
+ return { ok: failures.length === 0, message, state: this.state };
190
+ }
191
+ selectAgent(agentId) {
192
+ this.state.selectedAgentId = agentId;
193
+ this.markStep('agent', 'complete');
194
+ this.markStep('configure', 'running');
195
+ appendInstallLog(`agent-selected ${agentId}`);
196
+ return {
197
+ ok: true,
198
+ message: `Selected ${types_1.FIRST_RUN_AGENT_OPTIONS.find((agent) => agent.id === agentId)?.label || agentId}.`,
199
+ state: this.state,
200
+ launchCommand: types_1.FIRST_RUN_AGENT_OPTIONS.find((agent) => agent.id === agentId)?.loginCommand,
201
+ };
202
+ }
203
+ async configureFraim() {
204
+ this.markStep('configure', 'running');
205
+ (0, setup_1.saveGlobalConfig)(this.key, 'conversational', {}, {});
206
+ if (!this.fakeMode) {
207
+ const selectedAgent = this.state.selectedAgentId ? (0, ide_detector_1.findIDEByName)(this.state.selectedAgentId) : undefined;
208
+ const targetNames = selectedAgent ? [selectedAgent.name] : undefined;
209
+ await (0, auto_mcp_setup_1.autoConfigureMCP)(this.key, {}, targetNames, {});
210
+ this.state.configuredAgents = this.detectAgents().filter((agent) => agent.detected).map((agent) => agent.id);
211
+ }
212
+ else {
213
+ this.state.configuredAgents = this.state.selectedAgentId ? [this.state.selectedAgentId] : [];
214
+ }
215
+ appendInstallLog(`configure configuredAgents=${this.state.configuredAgents.join(',')}`);
216
+ this.markStep('configure', 'complete');
217
+ this.markStep('project', 'running');
218
+ return {
219
+ ok: true,
220
+ message: 'Saved FRAIM global configuration and wrote agent config where available.',
221
+ state: this.state,
222
+ };
223
+ }
224
+ async initializeProject(projectPath, initializeGit = true) {
225
+ const resolvedPath = path_1.default.resolve(projectPath);
226
+ fs_1.default.mkdirSync(resolvedPath, { recursive: true });
227
+ this.markStep('project', 'running');
228
+ if (initializeGit && commandExists('git')) {
229
+ try {
230
+ (0, child_process_1.execSync)('git rev-parse --git-dir', { cwd: resolvedPath, stdio: 'ignore' });
231
+ }
232
+ catch {
233
+ (0, child_process_1.execSync)('git init', { cwd: resolvedPath, stdio: 'ignore' });
234
+ }
235
+ }
236
+ await (0, init_project_1.runInitProject)({ projectRoot: resolvedPath });
237
+ this.state.workspacePath = resolvedPath;
238
+ this.markStep('project', 'complete');
239
+ this.markStep('launch', 'running');
240
+ appendInstallLog(`project-initialized ${resolvedPath}`);
241
+ return {
242
+ ok: true,
243
+ message: `Initialized FRAIM in ${resolvedPath}.`,
244
+ state: this.state,
245
+ };
246
+ }
247
+ launchAndProbe() {
248
+ const agentId = this.state.selectedAgentId;
249
+ const projectRoot = this.state.workspacePath;
250
+ if (!agentId || !projectRoot) {
251
+ this.markStep('launch', 'failed');
252
+ return {
253
+ ok: false,
254
+ message: 'Select an agent and project folder before launch.',
255
+ state: this.state,
256
+ };
257
+ }
258
+ const launchCommand = getLaunchInstruction(agentId, projectRoot);
259
+ const opened = this.fakeMode ? true : maybeLaunchInteractiveShell(agentId, projectRoot);
260
+ let probeResult = { ok: true, output: 'Fake test-mode launch succeeded.' };
261
+ if (!this.fakeMode) {
262
+ if (agentId === 'codex') {
263
+ probeResult = probeCodex(projectRoot);
264
+ }
265
+ else if (agentId === 'claude-code') {
266
+ probeResult = probeClaude(projectRoot);
267
+ }
268
+ else {
269
+ probeResult = probeGemini(projectRoot);
270
+ }
271
+ }
272
+ this.state.lastLaunchCommand = launchCommand;
273
+ this.state.lastProbeMessage = probeResult.output;
274
+ this.markStep('launch', probeResult.ok ? 'complete' : 'failed');
275
+ if (probeResult.ok) {
276
+ this.markStep('finish', 'running');
277
+ }
278
+ appendInstallLog(`launch agent=${agentId} opened=${opened} probeOk=${probeResult.ok}`);
279
+ return {
280
+ ok: probeResult.ok,
281
+ message: opened
282
+ ? 'Agent launch was attempted and the probe completed.'
283
+ : 'Agent launch could not be opened automatically; use the provided command and review the probe output.',
284
+ state: this.state,
285
+ launchCommand,
286
+ output: probeResult.output,
287
+ };
288
+ }
289
+ finish() {
290
+ ensureOutputDirs();
291
+ fs_1.default.writeFileSync(getNextPromptPath(), `${types_1.FIRST_RUN_PROMPT}${os_1.default.EOL}`, 'utf8');
292
+ this.state.nextPrompt = types_1.FIRST_RUN_PROMPT;
293
+ this.markStep('finish', 'complete');
294
+ appendInstallLog('finish prompt-written');
295
+ return {
296
+ ok: true,
297
+ message: 'Wrote the next prompt artifact and completed first-run.',
298
+ state: this.state,
299
+ };
300
+ }
301
+ }
302
+ exports.FirstRunSessionService = FirstRunSessionService;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FIRST_RUN_AGENT_OPTIONS = exports.FIRST_RUN_RESOURCES_URL = exports.FIRST_RUN_PROMPT = void 0;
4
+ exports.createDefaultStepStates = createDefaultStepStates;
5
+ exports.FIRST_RUN_PROMPT = 'Onboard this project';
6
+ exports.FIRST_RUN_RESOURCES_URL = 'https://fraimworks.ai/resources.html';
7
+ exports.FIRST_RUN_AGENT_OPTIONS = [
8
+ {
9
+ id: 'claude-code',
10
+ label: 'Claude Code',
11
+ detectAliases: ['claude-code', 'claude code', 'claude'],
12
+ loginCommand: 'claude',
13
+ launchCommand: 'claude',
14
+ },
15
+ {
16
+ id: 'codex',
17
+ label: 'Codex',
18
+ detectAliases: ['codex'],
19
+ loginCommand: 'codex login',
20
+ launchCommand: 'codex',
21
+ },
22
+ {
23
+ id: 'gemini-cli',
24
+ label: 'Gemini CLI',
25
+ detectAliases: ['gemini-cli', 'gemini cli', 'gemini'],
26
+ loginCommand: 'gemini',
27
+ launchCommand: 'gemini',
28
+ },
29
+ ];
30
+ function createDefaultStepStates() {
31
+ return {
32
+ welcome: 'pending',
33
+ prereqs: 'pending',
34
+ agent: 'pending',
35
+ configure: 'pending',
36
+ project: 'pending',
37
+ launch: 'pending',
38
+ finish: 'pending',
39
+ };
40
+ }