fraim-framework 2.0.166 → 2.0.168

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 (60) hide show
  1. package/dist/src/ai-hub/catalog.js +43 -36
  2. package/dist/src/ai-hub/server.js +28 -5
  3. package/dist/src/cli/commands/init-project.js +1 -98
  4. package/dist/src/cli/commands/manager.js +40 -0
  5. package/dist/src/cli/commands/sync.js +17 -21
  6. package/dist/src/cli/fraim.js +2 -0
  7. package/dist/src/cli/utils/github-workflow-sync.js +12 -146
  8. package/dist/src/cli/utils/manager-pack-sync.js +188 -0
  9. package/dist/src/cli/utils/manager-publish.js +76 -0
  10. package/dist/src/cli/utils/user-config.js +20 -0
  11. package/dist/src/core/config-loader.js +9 -5
  12. package/dist/src/core/fraim-config-schema.generated.js +85 -31
  13. package/dist/src/core/manager-pack.js +26 -0
  14. package/dist/src/core/utils/local-registry-resolver.js +8 -1
  15. package/dist/src/first-run/install-state.js +1 -0
  16. package/dist/src/first-run/server.js +9 -0
  17. package/dist/src/first-run/session-service.js +117 -23
  18. package/dist/src/first-run/types.js +2 -5
  19. package/dist/src/local-mcp-server/learning-context-builder.js +45 -8
  20. package/dist/src/local-mcp-server/stdio-server.js +28 -0
  21. package/index.js +1 -1
  22. package/package.json +4 -1
  23. package/public/ai-hub/powerpoint-taskpane/index.html +236 -236
  24. package/public/ai-hub/powerpoint-taskpane/manifest.xml +29 -29
  25. package/public/ai-hub/review.css +13 -0
  26. package/public/ai-hub/script.js +199 -5
  27. package/public/ai-hub/styles.css +28 -0
  28. package/public/first-run/index.html +1 -1
  29. package/public/first-run/script.js +459 -530
  30. package/public/first-run/styles.css +288 -73
  31. package/public/portfolio/ashley.html +523 -0
  32. package/public/portfolio/auditya.html +83 -0
  33. package/public/portfolio/banke.html +83 -0
  34. package/public/portfolio/beza.html +659 -0
  35. package/public/portfolio/careena.html +632 -0
  36. package/public/portfolio/casey.html +568 -0
  37. package/public/portfolio/celia.html +490 -0
  38. package/public/portfolio/deidre.html +642 -0
  39. package/public/portfolio/gautam.html +597 -0
  40. package/public/portfolio/hari.html +469 -0
  41. package/public/portfolio/huxley.html +1354 -0
  42. package/public/portfolio/index.html +741 -0
  43. package/public/portfolio/maestro.html +518 -0
  44. package/public/portfolio/mandy.html +590 -0
  45. package/public/portfolio/mona.html +597 -0
  46. package/public/portfolio/pam.html +887 -0
  47. package/public/portfolio/procella.html +107 -0
  48. package/public/portfolio/qasm.html +569 -0
  49. package/public/portfolio/ricardo.html +489 -0
  50. package/public/portfolio/sade.html +560 -0
  51. package/public/portfolio/sam.html +654 -0
  52. package/public/portfolio/sechar.html +580 -0
  53. package/public/portfolio/sreya.html +599 -0
  54. package/public/portfolio/swen.html +601 -0
  55. package/dist/src/ai-hub/word-sideload.js +0 -95
  56. package/dist/src/cli/commands/test-mcp.js +0 -171
  57. package/dist/src/cli/setup/first-run.js +0 -242
  58. package/dist/src/core/config-writer.js +0 -75
  59. package/dist/src/core/utils/job-aliases.js +0 -47
  60. package/dist/src/core/utils/workflow-parser.js +0 -174
@@ -138,14 +138,6 @@ exports.FRAIM_CONFIG_SCHEMA = {
138
138
  }
139
139
  }
140
140
  },
141
- "githubWorkflows": {
142
- "kind": "object",
143
- "properties": {
144
- "enabled": {
145
- "kind": "boolean"
146
- }
147
- }
148
- },
149
141
  "validation": {
150
142
  "kind": "object",
151
143
  "properties": {
@@ -312,6 +304,78 @@ exports.FRAIM_CONFIG_SCHEMA = {
312
304
  ]
313
305
  }
314
306
  }
307
+ },
308
+ "recruiting": {
309
+ "kind": "object",
310
+ "properties": {
311
+ "ats": {
312
+ "kind": "object",
313
+ "properties": {
314
+ "provider": {
315
+ "kind": "enum",
316
+ "values": [
317
+ "greenhouse",
318
+ "lever",
319
+ "ashby",
320
+ "none"
321
+ ]
322
+ },
323
+ "pipeline_source": {
324
+ "kind": "enum",
325
+ "values": [
326
+ "api",
327
+ "browser",
328
+ "csv",
329
+ "manual"
330
+ ]
331
+ }
332
+ }
333
+ },
334
+ "leadgen": {
335
+ "kind": "object",
336
+ "properties": {
337
+ "provider": {
338
+ "kind": "enum",
339
+ "values": [
340
+ "gem",
341
+ "linkedin-recruiter",
342
+ "apollo",
343
+ "outreach",
344
+ "none"
345
+ ]
346
+ },
347
+ "pipeline_source": {
348
+ "kind": "enum",
349
+ "values": [
350
+ "api",
351
+ "browser",
352
+ "csv",
353
+ "manual"
354
+ ]
355
+ }
356
+ }
357
+ },
358
+ "staleness_days": {
359
+ "kind": "object",
360
+ "properties": {
361
+ "sourcing": {
362
+ "kind": "number"
363
+ },
364
+ "applied": {
365
+ "kind": "number"
366
+ },
367
+ "screen": {
368
+ "kind": "number"
369
+ },
370
+ "interview": {
371
+ "kind": "number"
372
+ },
373
+ "offer": {
374
+ "kind": "number"
375
+ }
376
+ }
377
+ }
378
+ }
315
379
  }
316
380
  }
317
381
  },
@@ -335,23 +399,6 @@ exports.FRAIM_CONFIG_SCHEMA = {
335
399
  "autonomous"
336
400
  ]
337
401
  },
338
- "contextResolver": {
339
- "kind": "object",
340
- "properties": {
341
- "scriptPath": {
342
- "kind": "string"
343
- },
344
- "arguments": {
345
- "kind": "array",
346
- "element": {
347
- "kind": "string"
348
- }
349
- },
350
- "timeoutMs": {
351
- "kind": "number"
352
- }
353
- }
354
- },
355
402
  "playbooks": {
356
403
  "kind": "object",
357
404
  "properties": {
@@ -560,8 +607,6 @@ exports.SUPPORTED_FRAIM_CONFIG_PATHS = [
560
607
  "customizations.designSystem",
561
608
  "customizations.designSystem.path",
562
609
  "customizations.designSystem.brand",
563
- "customizations.githubWorkflows",
564
- "customizations.githubWorkflows.enabled",
565
610
  "customizations.validation",
566
611
  "customizations.validation.buildCommand",
567
612
  "customizations.validation.testSuiteCommand",
@@ -600,14 +645,23 @@ exports.SUPPORTED_FRAIM_CONFIG_PATHS = [
600
645
  "integrations.itsm.accessScript",
601
646
  "integrations.identity",
602
647
  "integrations.identity.provider",
648
+ "integrations.recruiting",
649
+ "integrations.recruiting.ats",
650
+ "integrations.recruiting.ats.provider",
651
+ "integrations.recruiting.ats.pipeline_source",
652
+ "integrations.recruiting.leadgen",
653
+ "integrations.recruiting.leadgen.provider",
654
+ "integrations.recruiting.leadgen.pipeline_source",
655
+ "integrations.recruiting.staleness_days",
656
+ "integrations.recruiting.staleness_days.sourcing",
657
+ "integrations.recruiting.staleness_days.applied",
658
+ "integrations.recruiting.staleness_days.screen",
659
+ "integrations.recruiting.staleness_days.interview",
660
+ "integrations.recruiting.staleness_days.offer",
603
661
  "automation",
604
662
  "automation.support",
605
663
  "automation.support.startMode",
606
664
  "automation.support.defaultDecisionMode",
607
- "automation.support.contextResolver",
608
- "automation.support.contextResolver.scriptPath",
609
- "automation.support.contextResolver.arguments",
610
- "automation.support.contextResolver.timeoutMs",
611
665
  "automation.support.playbooks",
612
666
  "automation.support.playbooks.directory",
613
667
  "automation.support.playbooks.decisionCommand",
@@ -0,0 +1,26 @@
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.MANAGER_PACK_RELATIVE_PATH_RE = exports.MANAGER_LEARNING_FILE_RE = exports.MANAGER_RULES_RELATIVE_PATH = exports.MANAGER_CONTEXT_RELATIVE_PATH = void 0;
7
+ exports.isManagerPackRelativePath = isManagerPackRelativePath;
8
+ exports.managerPackRelativePathForFileName = managerPackRelativePathForFileName;
9
+ const path_1 = __importDefault(require("path"));
10
+ exports.MANAGER_CONTEXT_RELATIVE_PATH = 'context/manager_context.md';
11
+ exports.MANAGER_RULES_RELATIVE_PATH = 'rules/manager_rules.md';
12
+ exports.MANAGER_LEARNING_FILE_RE = /^(?!org-)[A-Za-z0-9._%+@-]+-(mistake-patterns|preferences|manager-coaching|validated-patterns)\.md$/;
13
+ exports.MANAGER_PACK_RELATIVE_PATH_RE = /^(context\/manager_context\.md|rules\/manager_rules\.md|learnings\/(?!org-)[A-Za-z0-9._%+@-]+-(mistake-patterns|preferences|manager-coaching|validated-patterns)\.md)$/;
14
+ function isManagerPackRelativePath(value) {
15
+ return exports.MANAGER_PACK_RELATIVE_PATH_RE.test(value);
16
+ }
17
+ function managerPackRelativePathForFileName(fileName) {
18
+ const base = path_1.default.basename(fileName);
19
+ if (base === 'manager_context.md')
20
+ return exports.MANAGER_CONTEXT_RELATIVE_PATH;
21
+ if (base === 'manager_rules.md')
22
+ return exports.MANAGER_RULES_RELATIVE_PATH;
23
+ if (exports.MANAGER_LEARNING_FILE_RE.test(base))
24
+ return `learnings/${base}`;
25
+ return null;
26
+ }
@@ -371,9 +371,11 @@ class LocalRegistryResolver {
371
371
  if (!this.hasLocalOverride(path)) {
372
372
  const syncedLocalContent = this.readSyncedLocalFile(path);
373
373
  if (syncedLocalContent !== null) {
374
+ // Synced baseline (fraim/ai-employee), not personalized.
374
375
  return {
375
376
  content: syncedLocalContent,
376
377
  source: 'local',
378
+ personalized: false,
377
379
  inherited: false
378
380
  };
379
381
  }
@@ -384,6 +386,7 @@ class LocalRegistryResolver {
384
386
  return {
385
387
  content,
386
388
  source: 'remote',
389
+ personalized: false,
387
390
  inherited: false
388
391
  };
389
392
  }
@@ -408,6 +411,7 @@ class LocalRegistryResolver {
408
411
  return {
409
412
  content,
410
413
  source: 'remote',
414
+ personalized: false,
411
415
  inherited: false
412
416
  };
413
417
  }
@@ -422,13 +426,16 @@ class LocalRegistryResolver {
422
426
  return {
423
427
  content,
424
428
  source: 'remote',
429
+ personalized: false,
425
430
  inherited: false
426
431
  };
427
432
  }
428
- // Build result
433
+ // Build result — a local override hit means this came from the
434
+ // personalized-employee layer (the manager taught/customized it).
429
435
  const result = {
430
436
  content: resolved.content,
431
437
  source: 'local',
438
+ personalized: true,
432
439
  inherited: resolved.imports.length > 0,
433
440
  imports: resolved.imports.length > 0 ? resolved.imports : undefined
434
441
  };
@@ -31,6 +31,7 @@ function createInitialFirstRunState(key) {
31
31
  installKeyRef: maskInstallKey(key),
32
32
  platform: process.platform,
33
33
  agentId: 'claude-code',
34
+ agentInstalls: {},
34
35
  rows: (0, types_1.createInitialRows)(),
35
36
  resourcesUrl: types_1.FIRST_RUN_RESOURCES_URL,
36
37
  createdAt: now,
@@ -272,6 +272,15 @@ class FirstRunServer {
272
272
  return res.status(500).json({ error: error instanceof Error ? error.message : 'Could not check agent.' });
273
273
  }
274
274
  });
275
+ this.app.post('/api/first-run/done-recruiting', async (_req, res) => {
276
+ try {
277
+ const result = await this.sessionService.finishAgentRecruiting();
278
+ return res.json(result);
279
+ }
280
+ catch (error) {
281
+ return res.status(500).json({ error: error instanceof Error ? error.message : 'Could not finish local agent setup.' });
282
+ }
283
+ });
275
284
  // Hub-launch helper - starts an AiHubServer for the chosen project and
276
285
  // opens the user's browser. v2 (#355) replaces the in-process spawn with
277
286
  // a durable launcher binary that survives independently.
@@ -184,6 +184,14 @@ function buildConfiguredSurfaces() {
184
184
  status: 'configured',
185
185
  }));
186
186
  }
187
+ function surfaceForAgent(option) {
188
+ return {
189
+ id: option.id,
190
+ name: option.label,
191
+ invocationHint: `${option.label}: /fraim onboard this project`,
192
+ status: 'configured',
193
+ };
194
+ }
187
195
  class FirstRunSessionService {
188
196
  constructor(options) {
189
197
  this.key = options.key;
@@ -294,15 +302,15 @@ class FirstRunSessionService {
294
302
  }
295
303
  updateAgentSummaryRow() {
296
304
  const agentRow = this.getRow('agent');
297
- const surfaces = this.fakeMode === 'no-agents' ? [] : buildConfiguredSurfaces();
305
+ const surfaces = this.getConfiguredAgentSurfaces();
298
306
  if (surfaces.length > 0) {
299
307
  agentRow.status = 'ok';
300
- agentRow.verb = `${surfaces.length} AI Employee${surfaces.length === 1 ? '' : 's'} ready`;
308
+ agentRow.verb = `${surfaces.length} local AI agent${surfaces.length === 1 ? '' : 's'} ready`;
301
309
  agentRow.detail = surfaces.map((surface) => surface.name).join(', ');
302
310
  }
303
311
  else {
304
312
  agentRow.status = 'manual-required';
305
- agentRow.verb = "let's recruit AI Employees";
313
+ agentRow.verb = 'No problem, we will install AI agents next.';
306
314
  delete agentRow.detail;
307
315
  }
308
316
  this.state.setupResult = {
@@ -313,6 +321,36 @@ class FirstRunSessionService {
313
321
  completedAt: new Date().toISOString(),
314
322
  };
315
323
  }
324
+ getReadyInstalledAgentSurfaces() {
325
+ const installs = this.state.agentInstalls || {};
326
+ return types_1.FIRST_RUN_AGENT_OPTIONS
327
+ .filter((option) => installs[option.id]?.status === 'ready')
328
+ .map(surfaceForAgent);
329
+ }
330
+ getConfiguredAgentSurfaces() {
331
+ const byId = new Map();
332
+ for (const surface of this.getReadyInstalledAgentSurfaces()) {
333
+ byId.set(surface.id, surface);
334
+ }
335
+ if (this.fakeMode !== 'no-agents') {
336
+ for (const surface of buildConfiguredSurfaces()) {
337
+ byId.set(surface.id, surface);
338
+ }
339
+ }
340
+ return Array.from(byId.values());
341
+ }
342
+ setAgentInstallStatus(agentId, status, message) {
343
+ const option = findAgentOption(agentId);
344
+ if (!option)
345
+ return;
346
+ this.state.agentInstalls = this.state.agentInstalls || {};
347
+ this.state.agentInstalls[agentId] = {
348
+ status,
349
+ label: option.label,
350
+ updatedAt: new Date().toISOString(),
351
+ ...(message ? { message } : {}),
352
+ };
353
+ }
316
354
  applyFakeStateOnLoad(mode) {
317
355
  const setStatus = (rowId, status, verb, detail) => {
318
356
  const row = this.getRow(rowId);
@@ -325,7 +363,7 @@ class FirstRunSessionService {
325
363
  setStatus('node', 'pending', "we'll install");
326
364
  setStatus('git', 'pending', "we'll install");
327
365
  setStatus('fraim', 'pending', "we'll install");
328
- setStatus('agent', 'pending', "we'll check for AI Employees");
366
+ setStatus('agent', 'pending', "we'll check for local AI agents");
329
367
  delete this.state.setupResult;
330
368
  return;
331
369
  }
@@ -333,7 +371,7 @@ class FirstRunSessionService {
333
371
  setStatus('node', 'ok', 'v20.11.1 detected');
334
372
  setStatus('git', 'ok', 'git version 2.45 detected');
335
373
  setStatus('fraim', 'ok', 'fraim-framework detected');
336
- setStatus('agent', 'ok', '1 AI Employee ready', 'Claude Code');
374
+ setStatus('agent', 'ok', '1 local AI agent ready', 'Claude Code');
337
375
  this.state.setupResult = {
338
376
  mode: 'conversational',
339
377
  configuredSurfaces: [
@@ -348,8 +386,8 @@ class FirstRunSessionService {
348
386
  if (mode === 'no-agents') {
349
387
  setStatus('node', 'ok', 'v20.11.1 detected');
350
388
  setStatus('git', 'ok', 'git version 2.45 detected');
351
- setStatus('fraim', 'ok', 'fraim-framework detected');
352
- setStatus('agent', 'manual-required', "let's recruit AI Employees");
389
+ setStatus('fraim', 'pending', "we'll configure FRAIM after local agents are ready");
390
+ setStatus('agent', 'manual-required', 'No problem, we will install AI agents next.');
353
391
  this.state.setupResult = {
354
392
  mode: 'conversational',
355
393
  configuredSurfaces: [],
@@ -363,7 +401,7 @@ class FirstRunSessionService {
363
401
  setStatus('node', 'ok', 'v20.11.1 installed');
364
402
  setStatus('git', 'ok', '2.45 installed');
365
403
  setStatus('fraim', 'ok', 'fraim-framework installed');
366
- setStatus('agent', 'pending', "we'll check for AI Employees");
404
+ setStatus('agent', 'pending', "we'll check for local AI agents");
367
405
  delete this.state.setupResult;
368
406
  return;
369
407
  }
@@ -371,7 +409,7 @@ class FirstRunSessionService {
371
409
  setStatus('node', 'ok', 'v20.11.1 detected');
372
410
  setStatus('git', 'ok', 'git version 2.45 detected');
373
411
  setStatus('fraim', 'ok', 'fake-mode fraim installed');
374
- setStatus('agent', 'ok', 'fake-mode AI Employee ready', 'Claude Code');
412
+ setStatus('agent', 'ok', 'fake-mode local AI agent ready', 'Claude Code');
375
413
  this.state.setupResult = {
376
414
  mode: 'conversational',
377
415
  configuredSurfaces: [
@@ -493,16 +531,31 @@ class FirstRunSessionService {
493
531
  this.persist();
494
532
  return this.respond('git not found — continuing without it. Install git later if you plan to do code delivery work.', true);
495
533
  }
496
- async runFraimRow() {
534
+ async runFraimRow(forceConfigure = false) {
497
535
  const row = this.getRow('fraim');
498
536
  if (this.fakeMode) {
499
537
  row.status = 'ok';
500
538
  row.verb = 'FRAIM ready (fake-mode)';
539
+ const surfaces = this.getConfiguredAgentSurfaces();
540
+ this.state.setupResult = {
541
+ mode: 'conversational',
542
+ configuredSurfaces: surfaces,
543
+ failedSurfaces: [],
544
+ detectedSurfaceCount: surfaces.length,
545
+ completedAt: new Date().toISOString(),
546
+ };
501
547
  this.persist();
502
548
  return this.respond('Fake-mode fraim ok.', true);
503
549
  }
504
550
  try {
505
- if (!commandVersion('fraim')) {
551
+ if (!forceConfigure && !commandVersion('fraim')) {
552
+ const prefix = path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'node');
553
+ fs_1.default.mkdirSync(prefix, { recursive: true });
554
+ row.streamOutput = 'Installing FRAIM on this machine...';
555
+ this.persist();
556
+ await runProcess('npm', ['install', '-g', 'fraim@latest'], { npm_config_prefix: prefix });
557
+ }
558
+ else if (forceConfigure && !commandVersion('fraim')) {
506
559
  const prefix = path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'node');
507
560
  fs_1.default.mkdirSync(prefix, { recursive: true });
508
561
  row.streamOutput = 'Installing FRAIM on this machine...';
@@ -526,6 +579,14 @@ class FirstRunSessionService {
526
579
  row.status = 'ok';
527
580
  row.verb = 'Ready — open a new terminal before running fraim commands.';
528
581
  delete row.streamOutput;
582
+ const surfaces = this.getConfiguredAgentSurfaces();
583
+ this.state.setupResult = {
584
+ mode: 'conversational',
585
+ configuredSurfaces: surfaces,
586
+ failedSurfaces: [],
587
+ detectedSurfaceCount: surfaces.length,
588
+ completedAt: new Date().toISOString(),
589
+ };
529
590
  this.persist();
530
591
  return this.respond('FRAIM is ready. Open a new terminal window so your PATH update takes effect before running fraim commands.', true);
531
592
  }
@@ -543,7 +604,7 @@ class FirstRunSessionService {
543
604
  const row = this.getRow('agent');
544
605
  if (request.errorActionId === 'skip') {
545
606
  row.status = 'manual-required';
546
- row.verb = "let's recruit AI Employees";
607
+ row.verb = 'No problem, we will install AI agents next.';
547
608
  this.state.setupResult = {
548
609
  mode: 'conversational',
549
610
  configuredSurfaces: [],
@@ -552,21 +613,21 @@ class FirstRunSessionService {
552
613
  completedAt: new Date().toISOString(),
553
614
  };
554
615
  this.persist();
555
- return this.respond('AI Employee recruiting deferred.', true);
616
+ return this.respond('A local AI agent is required before FRAIM setup can finish.', false);
556
617
  }
557
618
  if (this.fakeMode === 'agent-install-fails') {
558
- this.setRowError(row, 'Checking for installed AI Employees and configuring one for this project', this.fakeStderr, [
619
+ this.setRowError(row, 'Checking for installed local AI agents and configuring one for this project', this.fakeStderr, [
559
620
  { id: 'retry', label: 'Retry', variant: 'primary' },
560
621
  { id: 'alternative', label: 'Try alternative', variant: 'secondary' },
561
- { id: 'skip', label: 'Skip and continue', variant: 'ghost' },
622
+ { id: 'alternative', label: 'Manual setup help', variant: 'ghost' },
562
623
  ]);
563
624
  this.persist();
564
- return this.respond('AI Employee recruiting failed.', false);
625
+ return this.respond('Local AI agent setup failed.', false);
565
626
  }
566
627
  this.updateAgentSummaryRow();
567
628
  this.persist();
568
629
  const count = this.state.setupResult?.detectedSurfaceCount || 0;
569
- return this.respond(count > 0 ? 'AI Employees are ready.' : 'No AI Employees found.', true);
630
+ return this.respond(count > 0 ? 'Local AI agents are ready.' : 'No problem, we will install AI agents next.', true);
570
631
  }
571
632
  /**
572
633
  * Update the current agent selection (inline `Change…` picker).
@@ -617,7 +678,20 @@ class FirstRunSessionService {
617
678
  if (!option) {
618
679
  return { ok: false, message: `Unknown agent: ${agentId}` };
619
680
  }
681
+ this.setAgentInstallStatus(agentId, 'installing', `Installing ${option.label}.`);
682
+ this.persist();
620
683
  if (this.fakeMode) {
684
+ if (this.fakeMode === 'agent-install-fails') {
685
+ const stderr = process.env.FRAIM_FIRST_RUN_FAKE_STDERR || `Failed to install ${option.label}.`;
686
+ this.setAgentInstallStatus(agentId, 'failed', stderr);
687
+ this.persist();
688
+ return {
689
+ ok: false,
690
+ message: stderr,
691
+ };
692
+ }
693
+ this.setAgentInstallStatus(agentId, 'needs-sign-in', `Sign in to ${option.label} to activate it.`);
694
+ this.persist();
621
695
  return {
622
696
  ok: true,
623
697
  message: `${option.label} installed successfully.`,
@@ -634,11 +708,9 @@ class FirstRunSessionService {
634
708
  if (!ver) {
635
709
  throw new Error(`${option.label} install completed, but the CLI is not runnable from FRAIM's managed PATH.`);
636
710
  }
637
- const detectedIDEs = (0, ide_detector_1.detectInstalledIDEs)();
638
- if (detectedIDEs.length > 0) {
639
- await (0, auto_mcp_setup_1.autoConfigureMCP)(this.key, {}, detectedIDEs.map((ide) => ide.name), {});
640
- }
711
+ this.setAgentInstallStatus(agentId, 'needs-sign-in', `Sign in to ${option.label} to activate it.`);
641
712
  appendInstallLog(`agent-installed ${agentId}`);
713
+ this.persist();
642
714
  return {
643
715
  ok: true,
644
716
  message: `${option.label} installed successfully.`,
@@ -649,6 +721,8 @@ class FirstRunSessionService {
649
721
  }
650
722
  catch (error) {
651
723
  const detail = error instanceof Error ? error.message : 'Unknown error';
724
+ this.setAgentInstallStatus(agentId, 'failed', detail);
725
+ this.persist();
652
726
  appendInstallLog(`agent-install-failed ${agentId} ${detail}`);
653
727
  return { ok: false, message: `Failed to install ${option.label}: ${detail}` };
654
728
  }
@@ -683,20 +757,40 @@ class FirstRunSessionService {
683
757
  return { ok: false, ready: false, message: `Unknown agent: ${agentId}` };
684
758
  }
685
759
  if (this.fakeMode) {
760
+ this.setAgentInstallStatus(agentId, 'ready', `${option.label} is ready.`);
761
+ this.updateAgentSummaryRow();
762
+ this.persist();
686
763
  return { ok: true, ready: true, message: `${option.label} is ready (fake-mode).` };
687
764
  }
688
765
  const ver = commandVersion(option.launchCommand, (0, managed_agent_paths_1.getManagedAgentBinDirs)());
689
766
  if (ver) {
767
+ this.setAgentInstallStatus(agentId, 'ready', `${option.label} is ready.`);
690
768
  this.updateAgentSummaryRow();
691
769
  this.persist();
692
770
  return { ok: true, ready: true, message: `${option.label} is ready.` };
693
771
  }
772
+ this.setAgentInstallStatus(agentId, 'needs-sign-in', `${option.label} is not detected yet.`);
773
+ this.persist();
694
774
  return {
695
775
  ok: true,
696
776
  ready: false,
697
777
  message: `${option.label} is not detected yet. Make sure sign-in is complete and try again.`,
698
778
  };
699
779
  }
780
+ async finishAgentRecruiting() {
781
+ this.updateAgentSummaryRow();
782
+ const readyCount = this.state.setupResult?.detectedSurfaceCount || 0;
783
+ if (readyCount < 1) {
784
+ this.persist();
785
+ return this.respond('At least one local AI agent is required before FRAIM setup can finish.', false);
786
+ }
787
+ const fraimRow = this.getRow('fraim');
788
+ fraimRow.status = 'pending';
789
+ fraimRow.verb = 'configuring FRAIM for local AI agents';
790
+ this.persist();
791
+ appendInstallLog(`agent-recruiting-complete ready=${readyCount}`);
792
+ return this.runFraimRow(true);
793
+ }
700
794
  openTerminalWithCommand(command) {
701
795
  if (process.platform === 'win32') {
702
796
  (0, child_process_1.spawn)('cmd.exe', ['/c', 'start', 'cmd.exe', '/k', command], { detached: true, stdio: 'ignore' }).unref();
@@ -730,7 +824,7 @@ class FirstRunSessionService {
730
824
  async openHub() {
731
825
  if (this.fakeMode) {
732
826
  // Tests don't actually want a Hub server running — just confirm intent.
733
- return { ok: true, message: 'Fake-mode Hub open requested.', hubUrl: 'http://127.0.0.1:0/ai-hub/?firstRun=true' };
827
+ return { ok: true, message: 'Fake-mode Hub open requested.', hubUrl: 'http://127.0.0.1:0/ai-hub/' };
734
828
  }
735
829
  // Hub launches a real CLI process, so folder-only config surfaces are not
736
830
  // enough here. Require a runnable command in the managed or ambient PATH.
@@ -748,7 +842,7 @@ class FirstRunSessionService {
748
842
  const port = await findAvailablePort(43091);
749
843
  const hubServer = new AiHubServer({ projectPath: this.state.workspacePath });
750
844
  await hubServer.start(port);
751
- const hubUrl = `http://127.0.0.1:${port}/ai-hub/?firstRun=true`;
845
+ const hubUrl = `http://127.0.0.1:${port}/ai-hub/`;
752
846
  appendInstallLog(`hub-opened ${hubUrl}`);
753
847
  return {
754
848
  ok: true,
@@ -16,8 +16,8 @@ exports.derivePrimaryButtonLabel = derivePrimaryButtonLabel;
16
16
  exports.FIRST_RUN_ROW_IDS = [
17
17
  'node',
18
18
  'git',
19
- 'fraim',
20
19
  'agent',
20
+ 'fraim',
21
21
  ];
22
22
  exports.FIRST_RUN_PROMPT = 'Onboard this project';
23
23
  exports.FIRST_RUN_RESOURCES_URL = 'https://fraimworks.ai/resources.html';
@@ -67,8 +67,8 @@ function createInitialRows() {
67
67
  return [
68
68
  { id: 'node', label: 'Node.js', status: 'pending', verb: "we'll install" },
69
69
  { id: 'git', label: 'git', status: 'pending', verb: "we'll install", optional: true },
70
+ { id: 'agent', label: 'Locally installed AI agents', status: 'pending', verb: "we'll detect local AI agents" },
70
71
  { id: 'fraim', label: 'FRAIM', status: 'pending', verb: "we'll set up FRAIM" },
71
- { id: 'agent', label: 'Execution surfaces', status: 'pending', verb: "we'll detect execution surfaces" },
72
72
  ];
73
73
  }
74
74
  /**
@@ -87,9 +87,6 @@ function derivePrimaryButtonLabel(rows) {
87
87
  if (allOk)
88
88
  return 'Get Started';
89
89
  // Skip-path: every required row is ok-or-manual-required — nothing left for the wizard.
90
- if (required.every((row) => row.status === 'ok' || row.status === 'manual-required')) {
91
- return 'Get Started';
92
- }
93
90
  if (required.some((row) => row.status === 'ok'))
94
91
  return 'Continue';
95
92
  return 'Set up FRAIM';