fraim 2.0.154 → 2.0.160

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 (45) hide show
  1. package/README.md +1 -1
  2. package/dist/src/ai-hub/cert-store.js +70 -0
  3. package/dist/src/ai-hub/desktop-main.js +225 -50
  4. package/dist/src/ai-hub/hosts.js +135 -8
  5. package/dist/src/ai-hub/manager-turns.js +38 -0
  6. package/dist/src/ai-hub/office-sideload.js +138 -0
  7. package/dist/src/ai-hub/openclaw-bridge.js +239 -0
  8. package/dist/src/ai-hub/server.js +479 -48
  9. package/dist/src/ai-hub/word-sideload.js +95 -0
  10. package/dist/src/cli/commands/add-ide.js +9 -0
  11. package/dist/src/cli/commands/init-project.js +46 -34
  12. package/dist/src/cli/commands/login.js +1 -2
  13. package/dist/src/cli/commands/setup.js +0 -2
  14. package/dist/src/cli/commands/sync.js +41 -11
  15. package/dist/src/cli/doctor/checks/mcp-connectivity-checks.js +66 -2
  16. package/dist/src/cli/doctor/checks/workflow-checks.js +1 -65
  17. package/dist/src/cli/mcp/fraim-mcp-latest-launcher.js +136 -0
  18. package/dist/src/cli/mcp/mcp-server-registry.js +14 -10
  19. package/dist/src/cli/setup/auto-mcp-setup.js +1 -1
  20. package/dist/src/cli/setup/ide-invocation-surfaces.js +2 -2
  21. package/dist/src/cli/utils/fraim-gitignore.js +11 -0
  22. package/dist/src/cli/utils/github-workflow-sync.js +231 -0
  23. package/dist/src/cli/utils/managed-agent-paths.js +1 -1
  24. package/dist/src/cli/utils/project-bootstrap.js +6 -3
  25. package/dist/src/cli/utils/remote-sync.js +1 -1
  26. package/dist/src/core/ai-mentor.js +46 -37
  27. package/dist/src/core/config-loader.js +69 -2
  28. package/dist/src/core/fraim-config-schema.generated.js +267 -6
  29. package/dist/src/core/types.js +0 -1
  30. package/dist/src/core/utils/fraim-labels.js +182 -0
  31. package/dist/src/core/utils/git-utils.js +22 -1
  32. package/dist/src/core/utils/project-fraim-paths.js +58 -0
  33. package/dist/src/first-run/session-service.js +3 -3
  34. package/dist/src/first-run/types.js +1 -1
  35. package/dist/src/local-mcp-server/learning-context-builder.js +77 -52
  36. package/dist/src/local-mcp-server/stdio-server.js +212 -13
  37. package/package.json +6 -2
  38. package/public/ai-hub/index.html +289 -229
  39. package/public/ai-hub/powerpoint-taskpane/icon-64.png +0 -0
  40. package/public/ai-hub/powerpoint-taskpane/index.html +235 -0
  41. package/public/ai-hub/powerpoint-taskpane/manifest.xml +30 -0
  42. package/public/ai-hub/script.js +1155 -586
  43. package/public/ai-hub/styles.css +1226 -722
  44. package/public/first-run/index.html +35 -35
  45. package/public/first-run/script.js +667 -667
@@ -14,6 +14,39 @@
14
14
  * (PR or Conversation from ~/.fraim/config.json). Delivery phases live
15
15
  * server-side in the workflow; the proxy just fills in mode-specific content.
16
16
  */
17
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ var desc = Object.getOwnPropertyDescriptor(m, k);
20
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
21
+ desc = { enumerable: true, get: function() { return m[k]; } };
22
+ }
23
+ Object.defineProperty(o, k2, desc);
24
+ }) : (function(o, m, k, k2) {
25
+ if (k2 === undefined) k2 = k;
26
+ o[k2] = m[k];
27
+ }));
28
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
29
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
30
+ }) : function(o, v) {
31
+ o["default"] = v;
32
+ });
33
+ var __importStar = (this && this.__importStar) || (function () {
34
+ var ownKeys = function(o) {
35
+ ownKeys = Object.getOwnPropertyNames || function (o) {
36
+ var ar = [];
37
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
38
+ return ar;
39
+ };
40
+ return ownKeys(o);
41
+ };
42
+ return function (mod) {
43
+ if (mod && mod.__esModule) return mod;
44
+ var result = {};
45
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
46
+ __setModuleDefault(result, mod);
47
+ return result;
48
+ };
49
+ })();
17
50
  var __importDefault = (this && this.__importDefault) || function (mod) {
18
51
  return (mod && mod.__esModule) ? mod : { "default": mod };
19
52
  };
@@ -380,6 +413,8 @@ class FraimLocalMCPServer {
380
413
  this.otlpServer = null;
381
414
  this.isShutdown = false;
382
415
  this.mentoringResponseCache = null;
416
+ this.connectSyncInFlight = null;
417
+ this.latestConnectSyncWarning = null;
383
418
  this.writer = writer || process.stdout.write.bind(process.stdout);
384
419
  this.remoteUrl = process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me';
385
420
  this.apiKey = this.loadApiKey();
@@ -554,6 +589,125 @@ class FraimLocalMCPServer {
554
589
  }
555
590
  }
556
591
  }
592
+ getConnectSyncMaxAgeMs() {
593
+ const raw = process.env.FRAIM_SYNC_ON_CONNECT_MAX_AGE_MS;
594
+ if (!raw)
595
+ return FraimLocalMCPServer.DEFAULT_CONNECT_SYNC_MAX_AGE_MS;
596
+ const parsed = Number(raw);
597
+ if (!Number.isFinite(parsed) || parsed < 0) {
598
+ return FraimLocalMCPServer.DEFAULT_CONNECT_SYNC_MAX_AGE_MS;
599
+ }
600
+ return Math.floor(parsed);
601
+ }
602
+ shouldUseLocalSyncTarget() {
603
+ if (process.env.FRAIM_LOCAL_SYNC === '1') {
604
+ return true;
605
+ }
606
+ try {
607
+ const parsed = new URL(this.remoteUrl);
608
+ return parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname === '::1';
609
+ }
610
+ catch {
611
+ return false;
612
+ }
613
+ }
614
+ getLocalCatalogMetadataPath(projectRoot) {
615
+ return (0, path_1.join)((0, project_fraim_paths_1.getWorkspaceFraimDir)(projectRoot), FraimLocalMCPServer.CONNECT_SYNC_METADATA_PATH);
616
+ }
617
+ readLocalCatalogMetadata(projectRoot) {
618
+ const metadataPath = this.getLocalCatalogMetadataPath(projectRoot);
619
+ if (!(0, fs_1.existsSync)(metadataPath)) {
620
+ return null;
621
+ }
622
+ try {
623
+ return JSON.parse((0, fs_1.readFileSync)(metadataPath, 'utf8'));
624
+ }
625
+ catch {
626
+ return null;
627
+ }
628
+ }
629
+ writeLocalCatalogMetadata(projectRoot) {
630
+ const metadataPath = this.getLocalCatalogMetadataPath(projectRoot);
631
+ const metadata = {
632
+ localVersion: this.localVersion,
633
+ mode: this.shouldUseLocalSyncTarget() ? 'local' : 'remote',
634
+ remoteUrl: this.remoteUrl,
635
+ syncedAt: new Date().toISOString()
636
+ };
637
+ (0, fs_1.writeFileSync)(metadataPath, JSON.stringify(metadata, null, 2), 'utf8');
638
+ }
639
+ getLocalCatalogSyncReason(projectRoot) {
640
+ const catalogRoots = [
641
+ (0, path_1.join)(projectRoot, 'fraim', 'ai-employee', 'jobs'),
642
+ (0, path_1.join)(projectRoot, 'fraim', 'ai-manager', 'jobs')
643
+ ];
644
+ if (catalogRoots.some((catalogRoot) => !(0, fs_1.existsSync)(catalogRoot))) {
645
+ return 'local job catalog is missing';
646
+ }
647
+ const metadata = this.readLocalCatalogMetadata(projectRoot);
648
+ if (!metadata) {
649
+ return 'local catalog has no freshness metadata';
650
+ }
651
+ if (metadata.localVersion !== this.localVersion) {
652
+ return `local catalog was synced by FRAIM ${metadata.localVersion}`;
653
+ }
654
+ const syncedAtMs = Date.parse(metadata.syncedAt);
655
+ if (!Number.isFinite(syncedAtMs)) {
656
+ return 'local catalog has invalid freshness metadata';
657
+ }
658
+ const maxAgeMs = this.getConnectSyncMaxAgeMs();
659
+ if (Date.now() - syncedAtMs > maxAgeMs) {
660
+ return `local catalog is older than ${maxAgeMs}ms`;
661
+ }
662
+ return null;
663
+ }
664
+ async performLocalCatalogSync(projectRoot) {
665
+ const { runSync } = await Promise.resolve().then(() => __importStar(require('../cli/commands/sync')));
666
+ await runSync({
667
+ projectRoot,
668
+ skipUpdates: true,
669
+ local: this.shouldUseLocalSyncTarget(),
670
+ failHard: 'throw'
671
+ });
672
+ this.writeLocalCatalogMetadata(projectRoot);
673
+ }
674
+ async ensureFreshLocalCatalogOnConnect(requestId) {
675
+ if (process.env.FRAIM_DISABLE_SYNC_ON_CONNECT === '1') {
676
+ this.latestConnectSyncWarning = null;
677
+ return;
678
+ }
679
+ if (this.connectSyncInFlight) {
680
+ await this.connectSyncInFlight;
681
+ return;
682
+ }
683
+ this.connectSyncInFlight = (async () => {
684
+ const projectRoot = this.findProjectRoot();
685
+ this.latestConnectSyncWarning = null;
686
+ if (!projectRoot || !(0, project_fraim_paths_1.workspaceFraimExists)(projectRoot)) {
687
+ return;
688
+ }
689
+ const reason = this.getLocalCatalogSyncReason(projectRoot);
690
+ if (!reason) {
691
+ return;
692
+ }
693
+ this.log(`[req:${requestId}] Refreshing local FRAIM catalog before fraim_connect because ${reason}`);
694
+ try {
695
+ await this.performLocalCatalogSync(projectRoot);
696
+ this.log(`[req:${requestId}] Local FRAIM catalog refresh complete`);
697
+ }
698
+ catch (error) {
699
+ const message = error?.message || String(error);
700
+ this.latestConnectSyncWarning = `Local FRAIM catalog refresh failed: ${message}. Discovery will fall back to remote FRAIM tools until sync succeeds.`;
701
+ this.logError(`[req:${requestId}] ${this.latestConnectSyncWarning}`);
702
+ }
703
+ })();
704
+ try {
705
+ await this.connectSyncInFlight;
706
+ }
707
+ finally {
708
+ this.connectSyncInFlight = null;
709
+ }
710
+ }
557
711
  /**
558
712
  * Automatically detect machine information
559
713
  */
@@ -1025,6 +1179,7 @@ class FraimLocalMCPServer {
1025
1179
  if (toolName === 'fraim_connect' && !finalizedResponse.error) {
1026
1180
  const text = finalizedResponse.result?.content?.[0]?.text;
1027
1181
  if (typeof text === 'string') {
1182
+ let responseText = text;
1028
1183
  const emailMatch = text.match(/Your identity for this session: \*\*([^*]+)\*\*/);
1029
1184
  if (emailMatch) {
1030
1185
  const userEmail = emailMatch[1].trim();
@@ -1034,10 +1189,14 @@ class FraimLocalMCPServer {
1034
1189
  const workspaceRoot = this.findProjectRoot() || process.cwd();
1035
1190
  const learningSection = (0, learning_context_builder_js_1.buildLearningContextSection)(workspaceRoot, userEmail, false);
1036
1191
  if (learningSection) {
1037
- finalizedResponse.result.content[0].text = text + learningSection;
1192
+ responseText += learningSection;
1038
1193
  this.log(`[req:${requestId}] Injected learning context for ${userEmail} from ${workspaceRoot}`);
1039
1194
  }
1040
1195
  }
1196
+ if (this.latestConnectSyncWarning) {
1197
+ responseText += `\n\n## Local Catalog\n${this.latestConnectSyncWarning}`;
1198
+ }
1199
+ finalizedResponse.result.content[0].text = responseText;
1041
1200
  }
1042
1201
  }
1043
1202
  // 4. After get_fraim_job succeeds, inject learning context with job-focus frame.
@@ -1461,6 +1620,7 @@ class FraimLocalMCPServer {
1461
1620
  // Special handling for fraim_connect - automatically inject machine and repo info
1462
1621
  if (request.method === 'tools/call' && request.params?.name === 'fraim_connect') {
1463
1622
  this.log(`[req:${requestId}] Intercepting fraim_connect to inject machine/repo info`);
1623
+ await this.ensureFreshLocalCatalogOnConnect(requestId);
1464
1624
  const args = request.params.arguments || {};
1465
1625
  // REQUIRED: Auto-detect and inject machine info
1466
1626
  const detectedMachine = this.detectMachineInfo();
@@ -1755,9 +1915,7 @@ class FraimLocalMCPServer {
1755
1915
  this.log(`✅ Local override found for get_fraim_job: ${name}`);
1756
1916
  let responseText = overview.overview;
1757
1917
  if (!overview.isSimple) {
1758
- const phaseAuthority = await mentor.getPhaseAuthorityContent();
1759
- if (phaseAuthority)
1760
- responseText = `${phaseAuthority}\n\n---\n\n${responseText}`;
1918
+ responseText = `${mentor.getCompactPhaseAuthority()}\n\n${responseText}`;
1761
1919
  responseText += `\n\n---\n\n**This job has phases.** Use \`seekMentoring\` to get phase-specific instructions.`;
1762
1920
  }
1763
1921
  // Inject local learning context for job requests (RFC 177).
@@ -1778,25 +1936,64 @@ class FraimLocalMCPServer {
1778
1936
  }
1779
1937
  }
1780
1938
  }
1781
- // DISCOVERY AGGREGATION: Merge local and remote jobs
1782
1939
  if (toolName === 'list_fraim_jobs') {
1783
1940
  const response = await this._doProxyToRemote(request, requestId);
1784
1941
  if (!response.error && response.result?.content?.[0]?.text) {
1785
1942
  try {
1786
- const resolver = this.getRegistryResolver(requestSessionId);
1787
- const localItems = await resolver.listItems('job');
1788
- if (localItems.length > 0) {
1789
- this.log(`📦 Aggregating ${localItems.length} local jobs into remote response`);
1943
+ const projectRoot = this.findProjectRoot();
1944
+ const uniqueLocalItems = new Map();
1945
+ if (projectRoot) {
1946
+ const personalizedJobsDir = (0, path_1.join)(projectRoot, 'fraim', 'personalized-employee', 'jobs');
1947
+ if ((0, fs_1.existsSync)(personalizedJobsDir)) {
1948
+ const collectLocalJobPaths = (dir, currentRel = '') => {
1949
+ const results = [];
1950
+ const entries = (0, fs_1.readdirSync)(dir, { withFileTypes: true });
1951
+ for (const entry of entries) {
1952
+ const rel = currentRel ? `${currentRel}/${entry.name}` : entry.name;
1953
+ const full = (0, path_1.join)(dir, entry.name);
1954
+ if (entry.isDirectory()) {
1955
+ results.push(...collectLocalJobPaths(full, rel));
1956
+ }
1957
+ else if (entry.isFile() && entry.name.endsWith('.md')) {
1958
+ results.push(rel);
1959
+ }
1960
+ }
1961
+ return results;
1962
+ };
1963
+ const localJobPaths = collectLocalJobPaths(personalizedJobsDir);
1964
+ for (const relPath of localJobPaths) {
1965
+ const normalizedName = relPath.split('/').pop()?.replace(/\.md$/, '').trim() || '';
1966
+ if (!normalizedName || uniqueLocalItems.has(normalizedName)) {
1967
+ continue;
1968
+ }
1969
+ const category = relPath.includes('/')
1970
+ ? relPath.split('/').slice(0, -1).join('/') || 'personalized-employee'
1971
+ : 'personalized-employee';
1972
+ uniqueLocalItems.set(normalizedName, {
1973
+ name: normalizedName,
1974
+ path: relPath,
1975
+ category
1976
+ });
1977
+ }
1978
+ }
1979
+ }
1980
+ if (uniqueLocalItems.size > 0) {
1981
+ const sortedLocalItems = Array.from(uniqueLocalItems.values())
1982
+ .sort((a, b) => a.category.localeCompare(b.category) || a.name.localeCompare(b.name));
1790
1983
  let combinedText = response.result.content[0].text;
1791
- combinedText += `\n\n## Local & Personalized Jobs (${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)()})\n\n`;
1792
- for (const item of localItems) {
1793
- combinedText += `- **${item.name}**: ${item.description || '(No description available)'}\n`;
1984
+ combinedText += `\n\n## Local Personalized Jobs (${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('personalized-employee/jobs/')})\n`;
1985
+ combinedText += 'These local jobs override catalog jobs of the same name when you call `get_fraim_job`.\n';
1986
+ for (const item of sortedLocalItems) {
1987
+ const categorySuffix = item.category && item.category !== 'personalized-employee'
1988
+ ? ` (${item.category})`
1989
+ : '';
1990
+ combinedText += `- **${item.name}**${categorySuffix}\n`;
1794
1991
  }
1795
1992
  response.result.content[0].text = combinedText;
1796
1993
  }
1797
1994
  }
1798
1995
  catch (error) {
1799
- this.log(`⚠️ Discovery aggregation failed: ${error.message}`);
1996
+ this.log(`⚠️ Local personalized job listing failed: ${error.message}`);
1800
1997
  }
1801
1998
  }
1802
1999
  return response;
@@ -2128,6 +2325,8 @@ FraimLocalMCPServer.AGENT_RESOLUTION_NOTICE = [
2128
2325
  ].join('\n');
2129
2326
  FraimLocalMCPServer.FALLBACK_ALERT_MARKER = 'PROXY_FALLBACK_ALERT';
2130
2327
  FraimLocalMCPServer.SESSION_HEADER = 'x-fraim-session-id';
2328
+ FraimLocalMCPServer.CONNECT_SYNC_METADATA_PATH = '.sync-metadata.json';
2329
+ FraimLocalMCPServer.DEFAULT_CONNECT_SYNC_MAX_AGE_MS = 24 * 60 * 60 * 1000;
2131
2330
  // Start server if run directly
2132
2331
  if (require.main === module) {
2133
2332
  const server = new FraimLocalMCPServer();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fraim",
3
- "version": "2.0.154",
3
+ "version": "2.0.160",
4
4
  "description": "FRAIM CLI - Framework for Rigor-based AI Management (alias for fraim-framework)",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -9,7 +9,7 @@
9
9
  "scripts": {
10
10
  "dev": "tsx --watch src/fraim-mcp-server.ts > server.log 2>&1",
11
11
  "dev:prod": "npm run build && node dist/src/fraim-mcp-server.js > server.log 2>&1",
12
- "build": "tsx scripts/build-fraim-config-schema-template.ts && npm run typecheck:scripts && tsc && npm run build:stubs && npm run build:fraim-brain && node scripts/copy-registry.js && npm run validate:registry && npm run validate:fraim-pro-assets && tsx scripts/validate-purity.ts",
12
+ "build": "tsx scripts/build-fraim-config-schema-template.ts && npm run typecheck:scripts && tsc && npm run build:stubs && npm run build:fraim-brain && node scripts/copy-registry.js && npm run validate:registry && npm run validate:fraim-pro-assets && npm run validate:employee-catalog && tsx scripts/validate-purity.ts",
13
13
  "build:stubs": "tsx scripts/build-stub-registry.ts",
14
14
  "build:fraim-brain": "node scripts/generate-fraim-brain.js",
15
15
  "test-all": "npm run test && npm run test:isolated && npm run test:ui",
@@ -23,6 +23,8 @@
23
23
  "test:ui": "playwright test --workers=1",
24
24
  "test:ui:headed": "playwright test --headed --workers=1",
25
25
  "hub:desktop": "npm run build && electron dist/src/ai-hub/desktop-main.js",
26
+ "hub:dev": "tsx scripts/start-hub-dev.ts",
27
+ "firstrun:dev": "tsx scripts/start-firstrun-dev.ts",
26
28
  "start:fraim": "tsx src/fraim-mcp-server.ts",
27
29
  "dev:fraim": "tsx --watch src/fraim-mcp-server.ts",
28
30
  "serve:website": "node fraim-pro/serve.js",
@@ -47,6 +49,7 @@
47
49
  "validate:registry-references": "tsx scripts/validate-registry-references.ts",
48
50
  "validate:brain-mapping": "tsx scripts/validate-brain-mapping.ts",
49
51
  "validate:fraim-pro-assets": "tsx scripts/validate-fraim-pro-assets.ts",
52
+ "validate:employee-catalog": "tsx scripts/validate-employee-catalog.ts",
50
53
  "validate:jobs": "tsx scripts/validate-jobs.ts",
51
54
  "validate:platform-agnostic": "tsx scripts/validate-platform-agnostic.ts",
52
55
  "validate:skills": "tsx scripts/validate-skills.ts",
@@ -148,6 +151,7 @@
148
151
  "nodemailer": "^8.0.3",
149
152
  "prompts": "^2.4.2",
150
153
  "resend": "^6.9.3",
154
+ "selfsigned": "^5.5.0",
151
155
  "semver": "^7.7.4",
152
156
  "stripe": "^20.3.1",
153
157
  "toml": "^3.0.0",