fraim-framework 2.0.56 → 2.0.58

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 (224) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/bin/fraim-mcp.js +14 -0
  3. package/bin/fraim.js +23 -0
  4. package/dist/src/cli/commands/init-project.js +10 -4
  5. package/dist/src/cli/commands/mcp.js +65 -0
  6. package/dist/src/cli/setup/mcp-config-generator.js +19 -16
  7. package/dist/src/fraim/issue-tracking/ado-provider.js +304 -0
  8. package/dist/src/fraim/issue-tracking/factory.js +63 -0
  9. package/dist/src/fraim/issue-tracking/github-provider.js +200 -0
  10. package/dist/src/fraim/issue-tracking/types.js +7 -0
  11. package/dist/src/fraim/issue-tracking-config.js +83 -0
  12. package/dist/src/local-mcp-server/stdio-server.js +207 -0
  13. package/dist/src/utils/workflow-parser.js +81 -0
  14. package/package.json +17 -12
  15. package/registry/scripts/pdf-styles.css +172 -0
  16. package/registry/scripts/prep-issue.sh +46 -4
  17. package/registry/scripts/profile-server.ts +131 -130
  18. package/registry/stubs/workflows/customer-development/user-survey-dispatch.md +1 -1
  19. package/registry/stubs/workflows/customer-development/users-to-target.md +1 -1
  20. package/registry/stubs/workflows/product-building/design.md +1 -1
  21. package/registry/stubs/workflows/product-building/implement.md +1 -1
  22. package/Claude.md +0 -1
  23. package/dist/registry/ai-manager-rules/customer-development-phases/phase1-customer-profiling.md +0 -101
  24. package/dist/registry/ai-manager-rules/customer-development-phases/phase2-platform-discovery.md +0 -235
  25. package/dist/registry/ai-manager-rules/customer-development-phases/phase3-prospect-qualification.md +0 -243
  26. package/dist/registry/ai-manager-rules/customer-development-phases/phase4-inventory-compilation.md +0 -206
  27. package/dist/registry/ai-manager-rules/design-phases/design-completeness-review.md +0 -73
  28. package/dist/registry/ai-manager-rules/design-phases/design-design.md +0 -145
  29. package/dist/registry/ai-manager-rules/implement-phases/implement-code.md +0 -283
  30. package/dist/registry/ai-manager-rules/implement-phases/implement-completeness-review.md +0 -120
  31. package/dist/registry/ai-manager-rules/implement-phases/implement-regression.md +0 -173
  32. package/dist/registry/ai-manager-rules/implement-phases/implement-repro.md +0 -104
  33. package/dist/registry/ai-manager-rules/implement-phases/implement-scoping.md +0 -100
  34. package/dist/registry/ai-manager-rules/implement-phases/implement-smoke.md +0 -237
  35. package/dist/registry/ai-manager-rules/implement-phases/implement-spike.md +0 -121
  36. package/dist/registry/ai-manager-rules/implement-phases/implement-validate.md +0 -375
  37. package/dist/registry/ai-manager-rules/retrospective.md +0 -116
  38. package/dist/registry/ai-manager-rules/shared-phases/address-pr-feedback.md +0 -188
  39. package/dist/registry/ai-manager-rules/shared-phases/submit-pr.md +0 -202
  40. package/dist/registry/ai-manager-rules/shared-phases/wait-for-pr-review.md +0 -170
  41. package/dist/registry/ai-manager-rules/spec-phases/spec-competitor-analysis.md +0 -105
  42. package/dist/registry/ai-manager-rules/spec-phases/spec-completeness-review.md +0 -66
  43. package/dist/registry/ai-manager-rules/spec-phases/spec-spec.md +0 -139
  44. package/dist/registry/ai-manager-rules/user-survey-phases/phase1-survey-scoping.md +0 -60
  45. package/dist/registry/ai-manager-rules/user-survey-phases/phase2-survey-build-linkedin.md +0 -23
  46. package/dist/registry/ai-manager-rules/user-survey-phases/phase3-survey-build-reddit.md +0 -22
  47. package/dist/registry/ai-manager-rules/user-survey-phases/phase4-survey-build-x.md +0 -21
  48. package/dist/registry/ai-manager-rules/user-survey-phases/phase5-survey-build-facebook.md +0 -19
  49. package/dist/registry/ai-manager-rules/user-survey-phases/phase6-survey-build-custom.md +0 -15
  50. package/dist/registry/ai-manager-rules/user-survey-phases/phase7-survey-dispatch.md +0 -45
  51. package/dist/registry/providers/ado.json +0 -19
  52. package/dist/registry/providers/github.json +0 -19
  53. package/dist/registry/scripts/cleanup-branch.js +0 -287
  54. package/dist/registry/scripts/evaluate-code-quality.js +0 -66
  55. package/dist/registry/scripts/exec-with-timeout.js +0 -142
  56. package/dist/registry/scripts/generate-engagement-emails.js +0 -705
  57. package/dist/registry/scripts/newsletter-helpers.js +0 -671
  58. package/dist/registry/scripts/profile-server.js +0 -388
  59. package/dist/registry/scripts/run-thank-you-workflow.js +0 -92
  60. package/dist/registry/scripts/send-newsletter-simple.js +0 -85
  61. package/dist/registry/scripts/send-thank-you-emails.js +0 -54
  62. package/dist/registry/scripts/validate-openapi-limits.js +0 -311
  63. package/dist/registry/scripts/validate-test-coverage.js +0 -262
  64. package/dist/registry/scripts/verify-test-coverage.js +0 -66
  65. package/dist/registry/templates/bootstrap/ARCHITECTURE-TEMPLATE.md +0 -53
  66. package/dist/registry/templates/bootstrap/CODE-QUALITY-REPORT-TEMPLATE.md +0 -37
  67. package/dist/registry/templates/bootstrap/TEST-COVERAGE-REPORT-TEMPLATE.md +0 -35
  68. package/dist/registry/templates/business-development/IDEATION-REPORT-TEMPLATE.md +0 -29
  69. package/dist/registry/templates/business-development/PRICING-STRATEGY-TEMPLATE.md +0 -126
  70. package/dist/registry/templates/customer-development/customer-interview-template.md +0 -99
  71. package/dist/registry/templates/customer-development/customer-persona-template.md +0 -69
  72. package/dist/registry/templates/customer-development/follow-up-email-templates.md +0 -132
  73. package/dist/registry/templates/customer-development/insight-analysis-template.md +0 -74
  74. package/dist/registry/templates/customer-development/prospect-inventory-template.csv +0 -3
  75. package/dist/registry/templates/customer-development/search-strategy-template.md +0 -123
  76. package/dist/registry/templates/customer-development/strategic-recommendations-template.md +0 -53
  77. package/dist/registry/templates/customer-development/thank-you-email-template.html +0 -124
  78. package/dist/registry/templates/customer-development/thank-you-note-template.md +0 -16
  79. package/dist/registry/templates/customer-development/triage-log-template.md +0 -278
  80. package/dist/registry/templates/customer-development/weekly-newsletter-template.html +0 -204
  81. package/dist/registry/templates/evidence/Design-Evidence.md +0 -30
  82. package/dist/registry/templates/evidence/Implementation-BugEvidence.md +0 -94
  83. package/dist/registry/templates/evidence/Implementation-FeatureEvidence.md +0 -129
  84. package/dist/registry/templates/evidence/Spec-Evidence.md +0 -19
  85. package/dist/registry/templates/help/HelpNeeded.md +0 -14
  86. package/dist/registry/templates/legal/NDA-TEMPLATE.md +0 -170
  87. package/dist/registry/templates/legal/PATENT-TEMPLATE.md +0 -372
  88. package/dist/registry/templates/legal/TRADEMARK-TEMPLATE.md +0 -339
  89. package/dist/registry/templates/legal/contract-review-checklist.md +0 -193
  90. package/dist/registry/templates/legal/review-report-template.md +0 -198
  91. package/dist/registry/templates/legal/saas-terms-template.md +0 -174
  92. package/dist/registry/templates/legal/sow-template.md +0 -117
  93. package/dist/registry/templates/legal/template-variables.md +0 -131
  94. package/dist/registry/templates/marketing/DOMAIN-REGISTRATION-TEMPLATE.md +0 -194
  95. package/dist/registry/templates/marketing/HBR-ARTICLE-TEMPLATE.md +0 -66
  96. package/dist/registry/templates/marketing/STORYTELLING-TEMPLATE.md +0 -130
  97. package/dist/registry/templates/marketing/WEBSITE-TEMPLATE.md +0 -262
  98. package/dist/registry/templates/marketing/github-pages-workflow.yml +0 -64
  99. package/dist/registry/templates/replicate/implementation-checklist.md +0 -39
  100. package/dist/registry/templates/replicate/use-cases-template.md +0 -88
  101. package/dist/registry/templates/retrospective/RETROSPECTIVE-TEMPLATE.md +0 -55
  102. package/dist/registry/templates/specs/BUGSPEC-TEMPLATE.md +0 -37
  103. package/dist/registry/templates/specs/FEATURESPEC-TEMPLATE.md +0 -66
  104. package/dist/registry/templates/specs/TECHSPEC-TEMPLATE.md +0 -39
  105. package/dist/registry/workflows/bootstrap/create-architecture.md +0 -38
  106. package/dist/registry/workflows/bootstrap/detect-broken-windows.md +0 -300
  107. package/dist/registry/workflows/bootstrap/evaluate-code-quality.md +0 -35
  108. package/dist/registry/workflows/bootstrap/verify-test-coverage.md +0 -36
  109. package/dist/registry/workflows/brainstorming/blue-sky-brainstorming.md +0 -211
  110. package/dist/registry/workflows/brainstorming/codebase-brainstorming.md +0 -165
  111. package/dist/registry/workflows/business-development/create-business-plan.md +0 -737
  112. package/dist/registry/workflows/business-development/ideate-business-opportunity.md +0 -55
  113. package/dist/registry/workflows/business-development/price-product.md +0 -325
  114. package/dist/registry/workflows/compliance/detect-compliance-requirements.md +0 -78
  115. package/dist/registry/workflows/compliance/generate-audit-evidence.md +0 -75
  116. package/dist/registry/workflows/compliance/soc2-evidence-generator.md +0 -332
  117. package/dist/registry/workflows/customer-development/insight-analysis.md +0 -156
  118. package/dist/registry/workflows/customer-development/insight-triage.md +0 -938
  119. package/dist/registry/workflows/customer-development/interview-preparation.md +0 -452
  120. package/dist/registry/workflows/customer-development/linkedin-outreach.md +0 -593
  121. package/dist/registry/workflows/customer-development/strategic-brainstorming.md +0 -146
  122. package/dist/registry/workflows/customer-development/thank-customers.md +0 -203
  123. package/dist/registry/workflows/customer-development/user-survey-dispatch.md +0 -60
  124. package/dist/registry/workflows/customer-development/users-to-target.md +0 -112
  125. package/dist/registry/workflows/customer-development/weekly-newsletter.md +0 -366
  126. package/dist/registry/workflows/deploy/cloud-deployment.md +0 -310
  127. package/dist/registry/workflows/improve-fraim/contribute.md +0 -32
  128. package/dist/registry/workflows/improve-fraim/file-issue.md +0 -32
  129. package/dist/registry/workflows/learning/build-skillset.md +0 -212
  130. package/dist/registry/workflows/learning/synthesize-learnings.md +0 -284
  131. package/dist/registry/workflows/legal/contract-review-analysis.md +0 -382
  132. package/dist/registry/workflows/legal/nda.md +0 -69
  133. package/dist/registry/workflows/legal/patent-filing.md +0 -76
  134. package/dist/registry/workflows/legal/saas-contract-development.md +0 -213
  135. package/dist/registry/workflows/legal/trademark-filing.md +0 -77
  136. package/dist/registry/workflows/marketing/content-creation.md +0 -37
  137. package/dist/registry/workflows/marketing/convert-to-pdf.md +0 -235
  138. package/dist/registry/workflows/marketing/create-modern-website.md +0 -456
  139. package/dist/registry/workflows/marketing/domain-registration.md +0 -323
  140. package/dist/registry/workflows/marketing/hbr-article.md +0 -73
  141. package/dist/registry/workflows/marketing/launch-checklist.md +0 -37
  142. package/dist/registry/workflows/marketing/marketing-strategy.md +0 -45
  143. package/dist/registry/workflows/marketing/storytelling.md +0 -65
  144. package/dist/registry/workflows/performance/analyze-performance.md +0 -65
  145. package/dist/registry/workflows/product-building/design.md +0 -103
  146. package/dist/registry/workflows/product-building/implement.md +0 -74
  147. package/dist/registry/workflows/product-building/iterate-on-pr-comments.md +0 -70
  148. package/dist/registry/workflows/product-building/prep-issue.md +0 -41
  149. package/dist/registry/workflows/product-building/prototype.md +0 -65
  150. package/dist/registry/workflows/product-building/resolve.md +0 -168
  151. package/dist/registry/workflows/product-building/retrospect.md +0 -86
  152. package/dist/registry/workflows/product-building/spec.md +0 -181
  153. package/dist/registry/workflows/product-building/test.md +0 -125
  154. package/dist/registry/workflows/productivity-report/productivity-report.md +0 -263
  155. package/dist/registry/workflows/quality-assurance/browser-validation.md +0 -221
  156. package/dist/registry/workflows/quality-assurance/iterative-improvement-cycle.md +0 -562
  157. package/dist/registry/workflows/replicate/replicate-discovery.md +0 -336
  158. package/dist/registry/workflows/replicate/replicate-to-issues.md +0 -324
  159. package/dist/registry/workflows/reviewer/review-implementation-vs-design-spec.md +0 -638
  160. package/dist/registry/workflows/reviewer/review-implementation-vs-feature-spec.md +0 -675
  161. package/dist/registry/workflows/startup-credits/aws-activate-application.md +0 -535
  162. package/dist/registry/workflows/startup-credits/google-cloud-application.md +0 -647
  163. package/dist/registry/workflows/startup-credits/microsoft-azure-application.md +0 -538
  164. package/dist/scripts/build-stub-registry.js +0 -108
  165. package/dist/src/ai-manager/ai-manager.js +0 -480
  166. package/dist/src/ai-manager/phase-flow.js +0 -357
  167. package/dist/src/ai-manager/types.js +0 -5
  168. package/dist/src/fraim-mcp-server.js +0 -1885
  169. package/dist/tests/debug-tools.js +0 -80
  170. package/dist/tests/shared-server-utils.js +0 -57
  171. package/dist/tests/test-add-ide.js +0 -283
  172. package/dist/tests/test-ai-coach-edge-cases.js +0 -420
  173. package/dist/tests/test-ai-coach-mcp-integration.js +0 -450
  174. package/dist/tests/test-ai-coach-performance.js +0 -328
  175. package/dist/tests/test-ai-coach-phase-content.js +0 -264
  176. package/dist/tests/test-ai-coach-workflows.js +0 -514
  177. package/dist/tests/test-cli.js +0 -228
  178. package/dist/tests/test-client-scripts-validation.js +0 -167
  179. package/dist/tests/test-complete-setup-flow.js +0 -110
  180. package/dist/tests/test-config-system.js +0 -279
  181. package/dist/tests/test-debug-session.js +0 -134
  182. package/dist/tests/test-end-to-end-hybrid-validation.js +0 -328
  183. package/dist/tests/test-enhanced-session-init.js +0 -188
  184. package/dist/tests/test-first-run-journey.js +0 -368
  185. package/dist/tests/test-fraim-issues.js +0 -59
  186. package/dist/tests/test-genericization.js +0 -44
  187. package/dist/tests/test-hybrid-script-execution.js +0 -340
  188. package/dist/tests/test-ide-detector.js +0 -46
  189. package/dist/tests/test-improved-setup.js +0 -121
  190. package/dist/tests/test-mcp-config-generator.js +0 -99
  191. package/dist/tests/test-mcp-connection.js +0 -107
  192. package/dist/tests/test-mcp-issue-integration.js +0 -156
  193. package/dist/tests/test-mcp-lifecycle-methods.js +0 -240
  194. package/dist/tests/test-mcp-shared-server.js +0 -308
  195. package/dist/tests/test-mcp-template-processing.js +0 -160
  196. package/dist/tests/test-modular-issue-tracking.js +0 -165
  197. package/dist/tests/test-node-compatibility.js +0 -95
  198. package/dist/tests/test-npm-install.js +0 -68
  199. package/dist/tests/test-package-size.js +0 -108
  200. package/dist/tests/test-pr-review-workflow.js +0 -307
  201. package/dist/tests/test-prep-issue.js +0 -129
  202. package/dist/tests/test-productivity-integration.js +0 -157
  203. package/dist/tests/test-script-location-independence.js +0 -198
  204. package/dist/tests/test-script-sync.js +0 -557
  205. package/dist/tests/test-server-utils.js +0 -32
  206. package/dist/tests/test-session-rehydration.js +0 -148
  207. package/dist/tests/test-setup-integration.js +0 -98
  208. package/dist/tests/test-setup-scenarios.js +0 -322
  209. package/dist/tests/test-standalone.js +0 -143
  210. package/dist/tests/test-stub-registry.js +0 -136
  211. package/dist/tests/test-sync-stubs.js +0 -143
  212. package/dist/tests/test-sync-version-update.js +0 -93
  213. package/dist/tests/test-telemetry.js +0 -193
  214. package/dist/tests/test-token-validator.js +0 -30
  215. package/dist/tests/test-user-journey.js +0 -236
  216. package/dist/tests/test-users-to-target-workflow.js +0 -253
  217. package/dist/tests/test-utils.js +0 -109
  218. package/dist/tests/test-wizard.js +0 -71
  219. package/dist/tests/test-workflow-discovery.js +0 -242
  220. package/labels.json +0 -52
  221. package/registry/agent-guardrails.md +0 -63
  222. package/registry/fraim.md +0 -48
  223. package/setup.js +0 -171
  224. package/tsconfig.json +0 -23
@@ -1,671 +0,0 @@
1
- #!/usr/bin/env tsx
2
- "use strict";
3
- /**
4
- * Newsletter Helper Functions
5
- *
6
- * DETERMINISTIC functions for AI agents to use when creating newsletters.
7
- * AI agents do the CREATIVE work (categorization, content writing).
8
- * These functions provide TOOLS (data fetching, email sending).
9
- */
10
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
- if (k2 === undefined) k2 = k;
12
- var desc = Object.getOwnPropertyDescriptor(m, k);
13
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
- desc = { enumerable: true, get: function() { return m[k]; } };
15
- }
16
- Object.defineProperty(o, k2, desc);
17
- }) : (function(o, m, k, k2) {
18
- if (k2 === undefined) k2 = k;
19
- o[k2] = m[k];
20
- }));
21
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
- Object.defineProperty(o, "default", { enumerable: true, value: v });
23
- }) : function(o, v) {
24
- o["default"] = v;
25
- });
26
- var __importStar = (this && this.__importStar) || (function () {
27
- var ownKeys = function(o) {
28
- ownKeys = Object.getOwnPropertyNames || function (o) {
29
- var ar = [];
30
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
- return ar;
32
- };
33
- return ownKeys(o);
34
- };
35
- return function (mod) {
36
- if (mod && mod.__esModule) return mod;
37
- var result = {};
38
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
- __setModuleDefault(result, mod);
40
- return result;
41
- };
42
- })();
43
- Object.defineProperty(exports, "__esModule", { value: true });
44
- exports.getLatestNewsletterDate = getLatestNewsletterDate;
45
- exports.getResolvedIssuesForNewsletter = getResolvedIssuesForNewsletter;
46
- exports.getAllActiveExecutives = getAllActiveExecutives;
47
- exports.getPotentialCustomers = getPotentialCustomers;
48
- exports.sendNewsletterToExecutives = sendNewsletterToExecutives;
49
- const child_process_1 = require("child_process");
50
- const fs_1 = require("fs");
51
- const mongodb_1 = require("mongodb");
52
- const path_1 = require("path");
53
- // Self-contained utility functions (no FRAIM internal imports)
54
- function determineDatabaseName() {
55
- const env = process.env.NODE_ENV || process.env.ENVIRONMENT;
56
- // If explicitly set to ppe/staging, use that; otherwise default to prod
57
- if (env === 'ppe' || env === 'staging') {
58
- return 'fraim_ppe';
59
- }
60
- // Default to production
61
- return process.env.MONGO_DB_NAME || 'fraim_prod';
62
- }
63
- function determineSchema(branchName) {
64
- if (branchName.includes('ppe') || branchName.includes('staging'))
65
- return 'ppe';
66
- return 'prod';
67
- }
68
- function getCurrentGitBranch() {
69
- try {
70
- return (0, child_process_1.execSync)('git branch --show-current', { encoding: 'utf8' }).trim();
71
- }
72
- catch (error) {
73
- return 'master';
74
- }
75
- }
76
- function loadClientConfig() {
77
- const configPath = (0, path_1.join)(process.cwd(), '.fraim', 'config.json');
78
- if (!(0, fs_1.existsSync)(configPath)) {
79
- throw new Error('.fraim/config.json not found. Run fraim init first.');
80
- }
81
- return JSON.parse((0, fs_1.readFileSync)(configPath, 'utf-8'));
82
- }
83
- function getEnvOr(keys, fallback) {
84
- for (const key of keys) {
85
- const value = process.env[key];
86
- if (value && value.length)
87
- return value;
88
- }
89
- return fallback;
90
- }
91
- // Load configuration
92
- const config = loadClientConfig();
93
- const fraimConfig = {
94
- repoOwner: config.git.repoOwner || 'mathursrus',
95
- repoName: config.git.repoName || 'fraim-repo',
96
- personaName: config.persona.name,
97
- identityCollection: config.database?.identityCollection || 'Identity',
98
- executiveCollection: config.database?.executiveCollection || 'Executive',
99
- defaultEmail: getEnvOr(['FRAIM_DEFAULT_EMAIL'], 'agent@example.com'),
100
- prodDefaultEmail: getEnvOr(['PROD_FRAIM_DEFAULT_EMAIL'], 'agent@example.com'),
101
- defaultAccessToken: getEnvOr(['FRAIM_DEFAULT_ACCESS_TOKEN'], ''),
102
- defaultRefreshToken: getEnvOr(['FRAIM_DEFAULT_REFRESH_TOKEN'], ''),
103
- prodAccessToken: getEnvOr(['PROD_FRAIM_DEFAULT_ACCESS_TOKEN'], ''),
104
- prodRefreshToken: getEnvOr(['PROD_FRAIM_DEFAULT_REFRESH_TOKEN'], ''),
105
- defaultOAuthClientId: getEnvOr(['FRAIM_DEFAULT_OAUTH_CLIENT_ID'], ''),
106
- defaultOAuthClientSecret: getEnvOr(['FRAIM_DEFAULT_OAUTH_CLIENT_SECRET'], ''),
107
- prodOAuthClientId: getEnvOr(['PROD_FRAIM_DEFAULT_OAUTH_CLIENT_ID'], ''),
108
- prodOAuthClientSecret: getEnvOr(['PROD_FRAIM_DEFAULT_OAUTH_CLIENT_SECRET'], ''),
109
- webAppUrl: config.marketing?.websiteUrl || getEnvOr(['FRAIM_WEB_APP_URL'], 'http://localhost:3000'),
110
- chatUrl: config.marketing?.chatUrl || getEnvOr(['FRAIM_CHAT_URL'], ''),
111
- };
112
- // Helper functions for database operations
113
- function getProductionDatabase() {
114
- const mongoUrl = process.env.PROD_MONGO_DATABASE_URL || process.env.MONGO_DATABASE_URL;
115
- if (!mongoUrl) {
116
- throw new Error('PROD_MONGO_DATABASE_URL or MONGO_DATABASE_URL environment variable is required');
117
- }
118
- return new mongodb_1.MongoClient(mongoUrl);
119
- }
120
- /**
121
- * Get database name, defaulting to production
122
- */
123
- function getDatabaseName() {
124
- const env = process.env.NODE_ENV || process.env.ENVIRONMENT;
125
- // If explicitly set to ppe/staging, use that; otherwise default to prod
126
- if (env === 'ppe' || env === 'staging') {
127
- return determineDatabaseName();
128
- }
129
- // Default to production
130
- return process.env.MONGO_DB_NAME || 'fraim_prod';
131
- }
132
- /**
133
- * Get collection name with schema prefix (defaults to prod schema)
134
- */
135
- function getCollectionName(baseName) {
136
- const env = process.env.NODE_ENV || process.env.ENVIRONMENT;
137
- // If explicitly set to ppe/staging, use that schema; otherwise default to prod
138
- if (env === 'ppe' || env === 'staging') {
139
- const schema = determineSchema(getCurrentGitBranch());
140
- return `${schema}_${baseName}`;
141
- }
142
- // Default to prod schema
143
- return `prod_${baseName}`;
144
- }
145
- // Reuse existing email infrastructure - implemented inline to avoid FRAIM internal imports
146
- /**
147
- * Find executive by email in database (defaults to production)
148
- */
149
- async function findExecutiveByEmail(email) {
150
- const client = getProductionDatabase();
151
- try {
152
- await client.connect();
153
- const dbName = getDatabaseName();
154
- const db = client.db(dbName);
155
- const executive = await db.collection(getCollectionName('Executive')).findOne({ email: email.toLowerCase() });
156
- return executive;
157
- }
158
- finally {
159
- await client.close();
160
- }
161
- }
162
- /**
163
- * Get the persona email for an executive from the identity collection
164
- */
165
- async function getPersonaEmailForExecutive(executiveId) {
166
- const { MongoClient } = await Promise.resolve().then(() => __importStar(require('mongodb')));
167
- const mongoUrl = process.env.PROD_MONGO_DATABASE_URL || process.env.MONGO_DATABASE_URL;
168
- if (!mongoUrl) {
169
- console.warn(`⚠️ PROD_MONGO_DATABASE_URL not set, using default ${fraimConfig.personaName} email`);
170
- return fraimConfig.defaultEmail;
171
- }
172
- const client = new MongoClient(mongoUrl);
173
- try {
174
- await client.connect();
175
- const dbName = getDatabaseName();
176
- const db = client.db(dbName);
177
- const collectionName = getCollectionName(fraimConfig.identityCollection);
178
- // Query the identity collection (defaults to prod)
179
- let identity = await db.collection(collectionName).findOne({
180
- executive_id: executiveId,
181
- status: 'active'
182
- });
183
- if (!identity) {
184
- identity = await db.collection(collectionName).findOne({
185
- executive_id: executiveId
186
- });
187
- }
188
- if (identity && identity.email) {
189
- return identity.email;
190
- }
191
- return fraimConfig.defaultEmail;
192
- }
193
- catch (error) {
194
- console.warn(`⚠️ Could not get ${fraimConfig.personaName} email for executive ${executiveId}:`, error);
195
- return fraimConfig.defaultEmail;
196
- }
197
- finally {
198
- await client.close();
199
- }
200
- }
201
- /**
202
- * Get the latest date from existing newsletter files
203
- * Returns the most recent date when newsletter was sent, or null if no files exist
204
- */
205
- function getLatestNewsletterDate() {
206
- const newslettersDir = 'docs/customer-development/newsletters';
207
- if (!(0, fs_1.existsSync)(newslettersDir)) {
208
- return null;
209
- }
210
- const files = (0, fs_1.readdirSync)(newslettersDir).filter(f => f.startsWith('newsletter-') && f.endsWith('.json'));
211
- if (files.length === 0) {
212
- return null;
213
- }
214
- // Extract dates from filenames (e.g., newsletter-2025-11-01.json)
215
- const dates = [];
216
- for (const file of files) {
217
- const dateMatch = file.match(/newsletter-(\d{4}-\d{2}-\d{2})\.json/);
218
- if (dateMatch) {
219
- const filePath = (0, path_1.join)(newslettersDir, file);
220
- // Verify content is valid if needed, but for date extraction filename is enough
221
- dates.push(dateMatch[1]);
222
- }
223
- }
224
- if (dates.length === 0) {
225
- return null;
226
- }
227
- // Return the most recent date
228
- dates.sort();
229
- const latestDate = dates[dates.length - 1];
230
- console.log(`📅 Latest newsletter date found: ${latestDate}`);
231
- return latestDate;
232
- }
233
- /**
234
- * Get resolved issues for newsletter
235
- * DETERMINISTIC: Just fetches issues, doesn't categorize or filter
236
- * AI agent decides what to include and how to present it
237
- *
238
- * @param date Optional date string (YYYY-MM-DD). If not provided, uses latest newsletter date or last 7 days
239
- */
240
- async function getResolvedIssuesForNewsletter(date) {
241
- if (!date) {
242
- // Try to get the latest newsletter date
243
- const latestNewsletterDate = getLatestNewsletterDate();
244
- if (latestNewsletterDate) {
245
- date = latestNewsletterDate;
246
- console.log(`📋 Using latest newsletter date as starting point: ${date}`);
247
- }
248
- else {
249
- // Fallback to last 7 days if no previous newsletters exist
250
- const fallbackDate = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
251
- date = fallbackDate;
252
- console.log(`📋 No previous newsletters found, using last 7 days: ${date}`);
253
- }
254
- }
255
- console.log(`📋 Fetching issues resolved since ${date}...`);
256
- // Get all closed issues from the specified date (not just user-reported)
257
- const command = `gh search issues --repo=${fraimConfig.repoOwner}/${fraimConfig.repoName} --state=closed --closed=">${date}" --json number,title,body,labels,closedAt,author --limit 100`;
258
- const output = (0, child_process_1.execSync)(command, { encoding: 'utf-8' });
259
- const issues = JSON.parse(output);
260
- console.log(`✅ Found ${issues.length} resolved issues`);
261
- return issues;
262
- }
263
- /**
264
- * Get all active executives (defaults to production database)
265
- * DETERMINISTIC: Just fetches executive list
266
- */
267
- async function getAllActiveExecutives() {
268
- const mongoUrl = process.env.PROD_MONGO_DATABASE_URL || process.env.MONGO_DATABASE_URL;
269
- if (!mongoUrl) {
270
- throw new Error('PROD_MONGO_DATABASE_URL or MONGO_DATABASE_URL environment variable is required');
271
- }
272
- const client = new mongodb_1.MongoClient(mongoUrl);
273
- try {
274
- await client.connect();
275
- const dbName = getDatabaseName();
276
- const collectionName = getCollectionName('Executive');
277
- console.log(`[getAllActiveExecutives] Querying database: ${dbName}, collection: ${collectionName}`);
278
- const db = client.db(dbName);
279
- const executives = await db.collection(collectionName)
280
- .find({})
281
- .toArray();
282
- console.log(`[getAllActiveExecutives] Found ${executives.length} executives in ${dbName}.${collectionName}`);
283
- // If no executives found in prod, check PPE as fallback
284
- if (executives.length === 0 && dbName === 'fraim_prod') {
285
- console.log(`[getAllActiveExecutives] No executives in prod, checking PPE as fallback...`);
286
- const ppeDb = client.db('fraim_ppe');
287
- const ppeExecutives = await ppeDb.collection('ppe_Executive')
288
- .find({})
289
- .toArray();
290
- console.log(`[getAllActiveExecutives] Found ${ppeExecutives.length} executives in fraim_ppe.ppe_Executive`);
291
- return ppeExecutives;
292
- }
293
- return executives;
294
- }
295
- finally {
296
- await client.close();
297
- }
298
- }
299
- /**
300
- * Get all potential customers (leads who have expressed interest)
301
- * DETERMINISTIC: Just fetches potential customer list from JSON file
302
- */
303
- function getPotentialCustomers() {
304
- const potentialCustomersPath = (0, path_1.join)(process.cwd(), 'docs', 'customer-development', 'potential-customers.json');
305
- if (!(0, fs_1.existsSync)(potentialCustomersPath)) {
306
- return [];
307
- }
308
- try {
309
- const content = (0, fs_1.readFileSync)(potentialCustomersPath, 'utf-8');
310
- return JSON.parse(content);
311
- }
312
- catch (error) {
313
- console.error('❌ Error reading potential customers file:', error);
314
- return [];
315
- }
316
- }
317
- /**
318
- * Send newsletter to all executives and potential customers
319
- * DETERMINISTIC: Just sends emails, no decisions
320
- *
321
- * AI agents call this AFTER user approves the newsletter JSON
322
- * @param newsletterPath Path to newsletter JSON file
323
- * @param filterEmails Optional array of email addresses to send to (for testing)
324
- * @param filterExecIds Optional array of executive IDs to send to (for testing)
325
- * @param includePotentialCustomers Whether to include potential customers in the send (default: true)
326
- * @param showOnly If true, only show recipient list without sending
327
- */
328
- async function sendNewsletterToExecutives(newsletterPath, filterEmails, filterExecIds, includePotentialCustomers = true, showOnly = false, includeExecutives = true) {
329
- console.log(`📧 Sending newsletter from: ${newsletterPath}`);
330
- if (!(0, fs_1.existsSync)(newsletterPath)) {
331
- throw new Error(`Newsletter file not found: ${newsletterPath}`);
332
- }
333
- const newsletter = JSON.parse((0, fs_1.readFileSync)(newsletterPath, 'utf-8'));
334
- const htmlPath = newsletterPath.replace('.json', '.html');
335
- if (!(0, fs_1.existsSync)(htmlPath)) {
336
- throw new Error(`Newsletter HTML not found: ${htmlPath}. Generate it first.`);
337
- }
338
- const html = (0, fs_1.readFileSync)(htmlPath, 'utf-8');
339
- let executives = [];
340
- // Get executives only if includeExecutives is true
341
- if (includeExecutives) {
342
- executives = await getAllActiveExecutives();
343
- // Filter executives if filters are provided
344
- if (filterEmails && filterEmails.length > 0) {
345
- console.log(`🔍 Filtering to ${filterEmails.length} email(s): ${filterEmails.join(', ')}`);
346
- executives = executives.filter(exec => filterEmails.includes(exec.email));
347
- }
348
- if (filterExecIds && filterExecIds.length > 0) {
349
- console.log(`🔍 Filtering to ${filterExecIds.length} executive ID(s): ${filterExecIds.join(', ')}`);
350
- executives = executives.filter(exec => exec.id && filterExecIds.includes(exec.id));
351
- }
352
- }
353
- // Get potential customers if requested
354
- let potentialCustomers = [];
355
- if (includePotentialCustomers) {
356
- potentialCustomers = getPotentialCustomers();
357
- // Filter potential customers if filterEmails is provided
358
- if (filterEmails && filterEmails.length > 0) {
359
- potentialCustomers = potentialCustomers.filter(customer => filterEmails.includes(customer.email));
360
- }
361
- }
362
- if (includeExecutives) {
363
- console.log(`📊 Found ${executives.length} executive(s) to send to`);
364
- }
365
- if (includePotentialCustomers) {
366
- console.log(`📊 Found ${potentialCustomers.length} potential customer(s) to send to`);
367
- }
368
- if (showOnly) {
369
- console.log(`\n📋 Recipient List (--showonly mode - no emails will be sent):\n`);
370
- if (includeExecutives && executives.length > 0) {
371
- console.log(`\n👥 ACTIVE EXECUTIVES (${executives.length}):\n`);
372
- for (const exec of executives) {
373
- const fromEmail = exec.id
374
- ? await getPersonaEmailForExecutive(exec.id)
375
- : fraimConfig.defaultEmail;
376
- const fromDisplayName = `${fraimConfig.personaName} - ${exec.name}'s AI Executive Assistant`;
377
- console.log(` ${exec.name || 'Unknown Name'}`);
378
- console.log(` To: ${exec.email || 'No email'}`);
379
- console.log(` From Email: ${fromEmail}`);
380
- console.log(` From Display Name: ${fromDisplayName}`);
381
- console.log(` Executive ID: ${exec.id || 'No ID'}`);
382
- console.log('');
383
- }
384
- }
385
- if (potentialCustomers.length > 0) {
386
- const defaultEmail = fraimConfig.prodDefaultEmail || 'agent@example.com';
387
- const defaultDisplayName = `${fraimConfig.personaName} - Your AI Executive Assistant`;
388
- console.log(`\n🎯 POTENTIAL CUSTOMERS (${potentialCustomers.length}):\n`);
389
- potentialCustomers.forEach((customer, index) => {
390
- console.log(` ${customer.name || 'Unknown Name'}`);
391
- console.log(` To: ${customer.email || 'No email'}`);
392
- console.log(` From Email: ${defaultEmail}`);
393
- console.log(` From Display Name: ${defaultDisplayName}`);
394
- console.log(` Source: ${customer.source || 'Unknown'}`);
395
- console.log('');
396
- });
397
- }
398
- const totalRecipients = (includeExecutives ? executives.length : 0) + (includePotentialCustomers ? potentialCustomers.length : 0);
399
- console.log(`✅ Total: ${totalRecipients} recipient(s) would receive the newsletter`);
400
- return;
401
- }
402
- console.log(`\n📧 Sending newsletter...\n`);
403
- // Send to active executives (from their personal Persona email) - only if includeExecutives is true
404
- if (includeExecutives) {
405
- for (const executive of executives) {
406
- try {
407
- console.log(`📧 Sending to executive ${executive.name} (${executive.email})...`);
408
- const fromEmail = executive.id
409
- ? await getPersonaEmailForExecutive(executive.id)
410
- : fraimConfig.defaultEmail;
411
- const fromDisplayName = `${fraimConfig.personaName} - ${executive.name}'s AI Executive Assistant`;
412
- // Remove emojis from subject line for ASCII compatibility
413
- const subject = newsletter.content.weekTitle.replace(/[^\x00-\x7F]/g, '').trim();
414
- const plainText = generatePlainText(newsletter);
415
- // Get tokens and send (reuses existing infrastructure)
416
- const personaTokens = await getPersonaTokens(executive.id);
417
- const executiveData = await findExecutiveByEmail(executive.email);
418
- await sendEmailViaGmail({
419
- to: executive.email,
420
- subject,
421
- plainTextBody: plainText,
422
- htmlBody: html,
423
- fromEmail,
424
- fromDisplayName
425
- }, executiveData, personaTokens);
426
- console.log(`✅ Sent to ${executive.email}`);
427
- }
428
- catch (error) {
429
- console.error(`❌ Failed to send to ${executive.email}:`, error);
430
- }
431
- }
432
- }
433
- // Send to potential customers (from PROD_FRAIM_DEFAULT_EMAIL)
434
- const defaultEmail = fraimConfig.prodDefaultEmail || 'agent@example.com';
435
- const defaultDisplayName = `${fraimConfig.personaName} - Your AI Executive Assistant`;
436
- const defaultAccessToken = fraimConfig.prodAccessToken || '';
437
- const defaultRefreshToken = fraimConfig.prodRefreshToken || '';
438
- if (!defaultAccessToken || !defaultRefreshToken) {
439
- console.warn('⚠️ PROD_FRAIM_DEFAULT_ACCESS_TOKEN or PROD_FRAIM_DEFAULT_REFRESH_TOKEN not set. Skipping potential customers.');
440
- }
441
- else {
442
- for (const customer of potentialCustomers) {
443
- try {
444
- console.log(`📧 Sending to potential customer ${customer.name} (${customer.email})...`);
445
- // Remove emojis from subject line for ASCII compatibility
446
- const subject = newsletter.content.weekTitle.replace(/[^\x00-\x7F]/g, '').trim();
447
- const plainText = generatePlainText(newsletter);
448
- // Send from default Persona email with default tokens
449
- await sendEmailViaGmail({
450
- to: customer.email,
451
- subject,
452
- plainTextBody: plainText,
453
- htmlBody: html,
454
- fromEmail: defaultEmail,
455
- fromDisplayName: defaultDisplayName
456
- }, undefined, { access_token: defaultAccessToken, refresh_token: defaultRefreshToken });
457
- console.log(`✅ Sent to ${customer.email}`);
458
- }
459
- catch (error) {
460
- console.error(`❌ Failed to send to ${customer.email}:`, error);
461
- }
462
- }
463
- }
464
- console.log(`\n✅ Newsletter sending complete!`);
465
- if (includeExecutives) {
466
- console.log(` Sent to ${executives.length} executive(s)`);
467
- }
468
- if (includePotentialCustomers) {
469
- console.log(` Sent to ${potentialCustomers.length} potential customer(s)`);
470
- }
471
- process.exit(0);
472
- }
473
- /**
474
- * Get Persona tokens for executive
475
- */
476
- async function getPersonaTokens(executiveId) {
477
- if (!executiveId)
478
- return null;
479
- const mongoUrl = process.env.PROD_MONGO_DATABASE_URL || process.env.MONGO_DATABASE_URL;
480
- if (!mongoUrl)
481
- return null;
482
- const client = new mongodb_1.MongoClient(mongoUrl);
483
- try {
484
- await client.connect();
485
- const dbName = getDatabaseName();
486
- const db = client.db(dbName);
487
- const collectionName = getCollectionName(fraimConfig.identityCollection);
488
- // Check for identity by executive ID
489
- const identity = await db.collection(collectionName).findOne({
490
- executive_id: executiveId,
491
- status: 'active'
492
- }) || await db.collection(collectionName).findOne({
493
- executive_id: executiveId
494
- });
495
- if (identity && identity.access_token && identity.refresh_token) {
496
- return {
497
- access_token: identity.access_token,
498
- refresh_token: identity.refresh_token
499
- };
500
- }
501
- return null;
502
- }
503
- finally {
504
- await client.close();
505
- }
506
- }
507
- /**
508
- * Generate plain text version
509
- */
510
- function generatePlainText(newsletter) {
511
- const { content } = newsletter;
512
- let text = `${content.weekTitle}\n`;
513
- text += `${content.weekSubtitle}\n`;
514
- text += `Week of ${content.weekDate}\n\n`;
515
- text += `${content.openingMessage}\n\n`;
516
- // Hero feature(s) - support both single heroFeature and array of heroFeatures
517
- const heroFeatures = content.heroFeatures || (content.heroFeature ? [content.heroFeature] : []);
518
- if (heroFeatures.length > 0) {
519
- const heroLabel = heroFeatures.length > 1 ? '✨ HERO FEATURES OF THE WEEK' : '✨ FEATURE OF THE WEEK';
520
- text += `${heroLabel}\n\n`;
521
- heroFeatures.forEach((hero, index) => {
522
- if (heroFeatures.length > 1) {
523
- text += `HERO FEATURE ${index + 1}:\n`;
524
- }
525
- text += `${hero.title}\n`;
526
- text += `${hero.description}\n`;
527
- if (hero.impact) {
528
- text += `Impact: ${hero.impact}\n`;
529
- }
530
- text += `\n`;
531
- });
532
- }
533
- if (content.newFeatures && content.newFeatures.length > 0) {
534
- text += `NEW FEATURES:\n`;
535
- content.newFeatures.forEach((f) => {
536
- text += `• ${f.title}: ${f.description}\n`;
537
- });
538
- text += `\n`;
539
- }
540
- if (content.improvements && content.improvements.length > 0) {
541
- text += `IMPROVEMENTS:\n`;
542
- content.improvements.forEach((i) => {
543
- text += `• ${i.title}: ${i.description}\n`;
544
- });
545
- text += `\n`;
546
- }
547
- if (content.bugFixes && content.bugFixes.length > 0) {
548
- text += `BUG FIXES:\n`;
549
- content.bugFixes.forEach((b) => {
550
- text += `• ${b.title}: ${b.description}\n`;
551
- });
552
- text += `\n`;
553
- }
554
- if (content.testimonial) {
555
- text += `\n"${content.testimonial.text}"\n`;
556
- text += `— ${content.testimonial.author}, ${content.testimonial.role}\n\n`;
557
- }
558
- if (content.comingNext) {
559
- text += `COMING NEXT:\n${content.comingNext}\n\n`;
560
- }
561
- text += `\nWith gratitude,\n${fraimConfig.personaName}\nYour AI Executive Assistant\n\n`;
562
- text += `Visit: ${fraimConfig.webAppUrl ? `${fraimConfig.webAppUrl}/wellness/${fraimConfig.personaName.toLowerCase()}` : '#'}\n`;
563
- return text;
564
- }
565
- /**
566
- * Send email via Gmail API (reuses existing pattern)
567
- */
568
- async function sendEmailViaGmail(params, executive, personaTokens) {
569
- const { to, subject, plainTextBody, htmlBody, fromEmail, fromDisplayName } = params;
570
- let accessToken;
571
- let refreshToken;
572
- let isProdToken = false; // Track if we're using PROD tokens
573
- if (personaTokens?.access_token && personaTokens?.refresh_token) {
574
- accessToken = personaTokens.access_token;
575
- refreshToken = personaTokens.refresh_token;
576
- // Check if these are PROD tokens by comparing with PROD_FRAIM_DEFAULT_REFRESH_TOKEN
577
- const prodRefreshToken = fraimConfig.prodRefreshToken || '';
578
- if (prodRefreshToken && refreshToken === prodRefreshToken) {
579
- isProdToken = true;
580
- }
581
- }
582
- else if (executive?.personaAccessToken && executive?.personaRefreshToken) {
583
- accessToken = executive.personaAccessToken;
584
- refreshToken = executive.personaRefreshToken;
585
- }
586
- else {
587
- accessToken = fraimConfig.defaultAccessToken || '';
588
- refreshToken = fraimConfig.defaultRefreshToken || '';
589
- if (!accessToken || !refreshToken) {
590
- throw new Error(`${fraimConfig.personaName} Gmail tokens not found`);
591
- }
592
- }
593
- const boundary = `boundary_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
594
- const emailMessage = [
595
- `To: ${to}`,
596
- `From: ${fromDisplayName} <${fromEmail}>`,
597
- `Subject: ${subject}`,
598
- `Content-Type: multipart/alternative; boundary="${boundary}"`,
599
- `MIME-Version: 1.0`,
600
- ``,
601
- `--${boundary}`,
602
- `Content-Type: text/plain; charset=utf-8`,
603
- ``,
604
- plainTextBody,
605
- ``,
606
- `--${boundary}`,
607
- `Content-Type: text/html; charset=utf-8`,
608
- ``,
609
- htmlBody,
610
- ``,
611
- `--${boundary}--`
612
- ].join('\r\n');
613
- // Note: base64 encoding without newlines is important
614
- const url = 'https://gmail.googleapis.com/gmail/v1/users/me/messages/send';
615
- const requestBody = {
616
- raw: Buffer.from(emailMessage).toString('base64')
617
- .replace(/\+/g, '-')
618
- .replace(/\//g, '_')
619
- .replace(/=+$/, '')
620
- };
621
- const response = await fetch(url, {
622
- method: 'POST',
623
- headers: {
624
- 'Authorization': `Bearer ${accessToken}`,
625
- 'Content-Type': 'application/json',
626
- },
627
- body: JSON.stringify(requestBody)
628
- });
629
- if (!response.ok) {
630
- if (response.status === 401) {
631
- // Token refresh logic - use PROD OAuth credentials if using PROD tokens
632
- const clientId = isProdToken
633
- ? fraimConfig.prodOAuthClientId
634
- : fraimConfig.defaultOAuthClientId;
635
- const clientSecret = isProdToken
636
- ? fraimConfig.prodOAuthClientSecret
637
- : fraimConfig.defaultOAuthClientSecret;
638
- if (!clientId || !clientSecret) {
639
- throw new Error(`OAuth credentials not found for token refresh${isProdToken ? ' (PROD)' : ''}`);
640
- }
641
- const refreshResponse = await fetch('https://oauth2.googleapis.com/token', {
642
- method: 'POST',
643
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
644
- body: new URLSearchParams({
645
- client_id: clientId,
646
- client_secret: clientSecret,
647
- refresh_token: refreshToken,
648
- grant_type: 'refresh_token'
649
- })
650
- });
651
- if (!refreshResponse.ok) {
652
- throw new Error('Failed to refresh access token');
653
- }
654
- const tokenData = await refreshResponse.json();
655
- // Retry with new token
656
- const retryResponse = await fetch(url, {
657
- method: 'POST',
658
- headers: {
659
- 'Authorization': `Bearer ${tokenData.access_token}`,
660
- 'Content-Type': 'application/json',
661
- },
662
- body: JSON.stringify(requestBody)
663
- });
664
- if (!retryResponse.ok) {
665
- throw new Error(`Gmail API error after refresh: ${retryResponse.status}`);
666
- }
667
- return;
668
- }
669
- throw new Error(`Gmail API error: ${response.status}`);
670
- }
671
- }