nod-shout 0.1.0

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 (110) hide show
  1. package/README.md +82 -0
  2. package/TASK-AGENT-POSTS.md +112 -0
  3. package/assets/shout-default.svg +5 -0
  4. package/bin/shout +68 -0
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +29 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/lib/ai.d.ts +13 -0
  10. package/dist/lib/ai.d.ts.map +1 -0
  11. package/dist/lib/ai.js +135 -0
  12. package/dist/lib/ai.js.map +1 -0
  13. package/dist/lib/content-filter.d.ts +74 -0
  14. package/dist/lib/content-filter.d.ts.map +1 -0
  15. package/dist/lib/content-filter.js +188 -0
  16. package/dist/lib/content-filter.js.map +1 -0
  17. package/dist/lib/context-extractor.d.ts +39 -0
  18. package/dist/lib/context-extractor.d.ts.map +1 -0
  19. package/dist/lib/context-extractor.js +170 -0
  20. package/dist/lib/context-extractor.js.map +1 -0
  21. package/dist/lib/match-engine.d.ts +31 -0
  22. package/dist/lib/match-engine.d.ts.map +1 -0
  23. package/dist/lib/match-engine.js +322 -0
  24. package/dist/lib/match-engine.js.map +1 -0
  25. package/dist/lib/metadata.d.ts +7 -0
  26. package/dist/lib/metadata.d.ts.map +1 -0
  27. package/dist/lib/metadata.js +311 -0
  28. package/dist/lib/metadata.js.map +1 -0
  29. package/dist/lib/skills.d.ts +3 -0
  30. package/dist/lib/skills.d.ts.map +1 -0
  31. package/dist/lib/skills.js +20 -0
  32. package/dist/lib/skills.js.map +1 -0
  33. package/dist/lib/supabase.d.ts +2 -0
  34. package/dist/lib/supabase.d.ts.map +1 -0
  35. package/dist/lib/supabase.js +8 -0
  36. package/dist/lib/supabase.js.map +1 -0
  37. package/dist/tools/collections.d.ts +3 -0
  38. package/dist/tools/collections.d.ts.map +1 -0
  39. package/dist/tools/collections.js +142 -0
  40. package/dist/tools/collections.js.map +1 -0
  41. package/dist/tools/intros.d.ts +3 -0
  42. package/dist/tools/intros.d.ts.map +1 -0
  43. package/dist/tools/intros.js +483 -0
  44. package/dist/tools/intros.js.map +1 -0
  45. package/dist/tools/links.d.ts +3 -0
  46. package/dist/tools/links.d.ts.map +1 -0
  47. package/dist/tools/links.js +424 -0
  48. package/dist/tools/links.js.map +1 -0
  49. package/dist/tools/posts.d.ts +3 -0
  50. package/dist/tools/posts.d.ts.map +1 -0
  51. package/dist/tools/posts.js +212 -0
  52. package/dist/tools/posts.js.map +1 -0
  53. package/dist/tools/settings.d.ts +3 -0
  54. package/dist/tools/settings.d.ts.map +1 -0
  55. package/dist/tools/settings.js +45 -0
  56. package/dist/tools/settings.js.map +1 -0
  57. package/dist/tools/shout_agent_curate.d.ts +28 -0
  58. package/dist/tools/shout_agent_curate.d.ts.map +1 -0
  59. package/dist/tools/shout_agent_curate.js +80 -0
  60. package/dist/tools/shout_agent_curate.js.map +1 -0
  61. package/dist/tools/social.d.ts +3 -0
  62. package/dist/tools/social.d.ts.map +1 -0
  63. package/dist/tools/social.js +169 -0
  64. package/dist/tools/social.js.map +1 -0
  65. package/dist/types.d.ts +60 -0
  66. package/dist/types.d.ts.map +1 -0
  67. package/dist/types.js +3 -0
  68. package/dist/types.js.map +1 -0
  69. package/package.json +24 -0
  70. package/quick-test.ts +22 -0
  71. package/regenerate-summaries.ts +111 -0
  72. package/save-jeffries-shout.ts +38 -0
  73. package/save-openai-shout.ts +35 -0
  74. package/save-prcarly.ts +46 -0
  75. package/save-shout.ts +35 -0
  76. package/save-techcrunch-shout.ts +59 -0
  77. package/save-zdnet-shout.ts +36 -0
  78. package/skills/collection-routing/SKILL.md +34 -0
  79. package/skills/link-summary/SKILL.md +53 -0
  80. package/skills/tagging-and-routing/SKILL.md +54 -0
  81. package/src/index.ts +32 -0
  82. package/src/lib/ai.ts +166 -0
  83. package/src/lib/content-filter.ts +258 -0
  84. package/src/lib/metadata.ts +353 -0
  85. package/src/lib/skills.ts +21 -0
  86. package/src/lib/supabase.ts +12 -0
  87. package/src/tools/collections.ts +182 -0
  88. package/src/tools/links.ts +524 -0
  89. package/src/tools/posts.ts +264 -0
  90. package/src/tools/settings.ts +55 -0
  91. package/src/tools/shout_agent_curate.ts +95 -0
  92. package/src/tools/social.ts +206 -0
  93. package/src/types.ts +66 -0
  94. package/supabase/.temp/cli-latest +1 -0
  95. package/supabase/.temp/gotrue-version +1 -0
  96. package/supabase/.temp/pooler-url +1 -0
  97. package/supabase/.temp/postgres-version +1 -0
  98. package/supabase/.temp/project-ref +1 -0
  99. package/supabase/.temp/rest-version +1 -0
  100. package/supabase/.temp/storage-migration +1 -0
  101. package/supabase/.temp/storage-version +1 -0
  102. package/supabase/migrations/001_initial_schema.sql +147 -0
  103. package/supabase/migrations/20260317010000_decouple_profiles_from_auth.sql +9 -0
  104. package/supabase/migrations/20260317020000_agent_curation.sql +10 -0
  105. package/supabase/migrations/20260320000000_agent_posts.sql +41 -0
  106. package/supabase/migrations/20260320120000_fix_draft_fk.sql +2 -0
  107. package/supabase/migrations/20260320130000_fix_identity.sql +17 -0
  108. package/supabase/migrations/20260320170000_intros.sql +118 -0
  109. package/test-model-comparison.ts +89 -0
  110. package/tsconfig.json +19 -0
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Content filter for shout posts — strips PII, proprietary data,
3
+ * and sensitive information before anything touches the public page.
4
+ *
5
+ * Two layers:
6
+ * 1. Regex — catches format-based PII (emails, phones, keys, etc.)
7
+ * 2. LLM — catches context-based leaks ("our revenue is...", "client X told me...")
8
+ *
9
+ * Runs on all text fields (take, summary, title, description)
10
+ * before insert into supabase.
11
+ */
12
+ const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
13
+ // Regex patterns for common PII
14
+ const PII_PATTERNS = [
15
+ // Email addresses
16
+ { pattern: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, replacement: '[email removed]', label: 'email' },
17
+ // Phone numbers (US formats)
18
+ { pattern: /(\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}/g, replacement: '[phone removed]', label: 'phone' },
19
+ // SSN
20
+ { pattern: /\b\d{3}[-\s]?\d{2}[-\s]?\d{4}\b/g, replacement: '[ssn removed]', label: 'ssn' },
21
+ // Credit card numbers (basic)
22
+ { pattern: /\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/g, replacement: '[card removed]', label: 'credit_card' },
23
+ // API keys / tokens (common prefixes)
24
+ { pattern: /\b(sk-[a-zA-Z0-9_-]{20,}|sk-ant-[a-zA-Z0-9_-]{20,}|sk-proj-[a-zA-Z0-9_-]{20,}|ghp_[a-zA-Z0-9]{36,}|gho_[a-zA-Z0-9]{36,}|xoxb-[a-zA-Z0-9-]+|xoxp-[a-zA-Z0-9-]+|AKIA[A-Z0-9]{16})\b/g, replacement: '[api_key removed]', label: 'api_key' },
25
+ // Passwords in context
26
+ { pattern: /(?:password|passwd|pwd|secret|token)\s*[:=]\s*\S+/gi, replacement: '[credential removed]', label: 'password' },
27
+ // IP addresses (internal)
28
+ { pattern: /\b(?:10|172\.(?:1[6-9]|2\d|3[01])|192\.168)\.\d{1,3}\.\d{1,3}\b/g, replacement: '[internal_ip removed]', label: 'internal_ip' },
29
+ // Street addresses (basic US pattern)
30
+ { pattern: /\b\d{1,5}\s+[A-Z][a-zA-Z]*\s+(?:St|Street|Ave|Avenue|Blvd|Boulevard|Dr|Drive|Ln|Lane|Rd|Road|Way|Ct|Court|Pl|Place)\.?\b/gi, replacement: '[address removed]', label: 'address' },
31
+ ];
32
+ // Financial/proprietary data patterns
33
+ const PROPRIETARY_PATTERNS = [
34
+ // Dollar amounts over $999 (likely sensitive business figures)
35
+ { pattern: /\$\d{1,3}(?:,\d{3})+(?:\.\d{2})?/g, replacement: '[amount removed]', label: 'large_dollar' },
36
+ // Revenue/ARR/MRR mentions with numbers
37
+ { pattern: /(?:revenue|arr|mrr|profit|loss|burn|runway|valuation|cap table|equity)\s*(?:of|is|was|:)?\s*\$?[\d,.]+[kKmMbB]?/gi, replacement: '[financial_data removed]', label: 'financial' },
38
+ // Contract/deal terms
39
+ { pattern: /(?:contract|agreement|nda|term sheet|sow|msa)\s+(?:with|for|from)\s+[A-Z][a-zA-Z\s]+(?:Inc|LLC|Ltd|Corp|Co)\.?/gi, replacement: '[contract_info removed]', label: 'contract' },
40
+ ];
41
+ export function filterPII(text) {
42
+ if (!text)
43
+ return { text: text || '', filtered: false, removals: [] };
44
+ let result = text;
45
+ const removals = [];
46
+ for (const { pattern, replacement, label } of [...PII_PATTERNS, ...PROPRIETARY_PATTERNS]) {
47
+ const matches = result.match(pattern);
48
+ if (matches) {
49
+ removals.push(`${label} (${matches.length}x)`);
50
+ result = result.replace(pattern, replacement);
51
+ }
52
+ }
53
+ return {
54
+ text: result,
55
+ filtered: removals.length > 0,
56
+ removals,
57
+ };
58
+ }
59
+ /**
60
+ * Filter all text fields on a shout before saving.
61
+ * Returns the filtered fields and a report of what was removed.
62
+ */
63
+ export function filterShoutContent(fields) {
64
+ const takeResult = filterPII(fields.take);
65
+ const summaryResult = filterPII(fields.summary);
66
+ const titleResult = filterPII(fields.title);
67
+ const descResult = filterPII(fields.description);
68
+ const allRemovals = [
69
+ ...takeResult.removals.map(r => `take: ${r}`),
70
+ ...summaryResult.removals.map(r => `summary: ${r}`),
71
+ ...titleResult.removals.map(r => `title: ${r}`),
72
+ ...descResult.removals.map(r => `description: ${r}`),
73
+ ];
74
+ return {
75
+ take: takeResult.text || null,
76
+ summary: summaryResult.text || null,
77
+ title: titleResult.text || null,
78
+ description: descResult.text || null,
79
+ filterReport: allRemovals.length > 0 ? `PII filter removed: ${allRemovals.join('; ')}` : null,
80
+ };
81
+ }
82
+ const LLM_FILTER_PROMPT = `You are a content safety filter for a public social profile page (like Twitter for AI agents). Your job is to check if text contains private/confidential information that should NOT be posted publicly.
83
+
84
+ ALLOW (these are safe — do NOT flag):
85
+ - Public news, articles, blog posts, open-source projects
86
+ - General opinions, commentary, takes on public topics
87
+ - Professional interests, skills, industry knowledge
88
+ - Publicly known company info (funding rounds reported in press, public products)
89
+ - Names of public figures, companies, or products mentioned in public contexts
90
+ - Dollar amounts from public sources (article says "raised $10M" = fine)
91
+ - Technical discussions, code patterns, architecture opinions
92
+ - Meta-commentary ABOUT data types or categories (e.g. "this tool catches revenue leaks" is talking about the concept, not leaking actual revenue)
93
+ - Descriptions of what a tool filters, blocks, or protects against — mentioning "PII", "revenue", "deal terms" as CATEGORIES is not the same as sharing actual PII or revenue figures
94
+ - Product announcements, feature descriptions, build logs
95
+
96
+ BLOCK (these contain private data — flag these):
97
+ - Someone's ACTUAL personal contact info shared in private context (not from a public webpage)
98
+ - SPECIFIC internal business metrics with real numbers not publicly disclosed ("our revenue is $2.3M", "client pays $45k/month")
99
+ - Details from private conversations, meetings, or emails that identify SPECIFIC people + their SPECIFIC sensitive info
100
+ - ACTUAL client/customer names paired with ACTUAL deal terms, pricing, or contract details
101
+ - Health, legal, or financial information about SPECIFIC private individuals with REAL identifying details
102
+ - Login credentials, internal URLs, private API endpoints
103
+
104
+ KEY DISTINCTION: talking about categories of sensitive data ("catches things like revenue figures") is NOT the same as sharing actual sensitive data ("our revenue is $2.3M"). The first is safe. The second is not.
105
+
106
+ Respond with EXACTLY one of:
107
+ SAFE
108
+ or
109
+ BLOCK: [one-sentence reason]
110
+
111
+ Do not explain further. Do not hedge. When in doubt, default to SAFE. Only block when you see ACTUAL specific private data, not descriptions of data types.`;
112
+ export async function llmContentFilter(text) {
113
+ if (!OPENAI_API_KEY || !text || text.length < 20) {
114
+ return { safe: true, reason: null, redactedText: null };
115
+ }
116
+ try {
117
+ const response = await fetch('https://api.openai.com/v1/chat/completions', {
118
+ method: 'POST',
119
+ headers: {
120
+ 'Content-Type': 'application/json',
121
+ 'Authorization': `Bearer ${OPENAI_API_KEY}`,
122
+ },
123
+ body: JSON.stringify({
124
+ model: 'gpt-4o-mini',
125
+ messages: [
126
+ { role: 'system', content: LLM_FILTER_PROMPT },
127
+ { role: 'user', content: `Check this text:\n\n${text}` },
128
+ ],
129
+ temperature: 0,
130
+ max_tokens: 100,
131
+ }),
132
+ });
133
+ if (!response.ok) {
134
+ console.error(`[content-filter] LLM check failed: ${response.status}`);
135
+ // fail open — if LLM is down, trust the regex layer
136
+ return { safe: true, reason: null, redactedText: null };
137
+ }
138
+ const data = await response.json();
139
+ const reply = (data.choices?.[0]?.message?.content || '').trim();
140
+ if (reply.startsWith('SAFE')) {
141
+ return { safe: true, reason: null, redactedText: null };
142
+ }
143
+ if (reply.startsWith('BLOCK')) {
144
+ const reason = reply.replace(/^BLOCK:\s*/, '');
145
+ return { safe: false, reason, redactedText: null };
146
+ }
147
+ // unclear response — default safe (avoid false positives)
148
+ return { safe: true, reason: null, redactedText: null };
149
+ }
150
+ catch (err) {
151
+ console.error('[content-filter] LLM filter error:', err);
152
+ // fail open
153
+ return { safe: true, reason: null, redactedText: null };
154
+ }
155
+ }
156
+ /**
157
+ * Full content filter: regex + LLM.
158
+ * Use this for user-facing text (takes, posts, commentary).
159
+ * Skip LLM for metadata from fetched web pages (titles, descriptions) — those are public by definition.
160
+ */
161
+ export async function filterShoutContentFull(fields) {
162
+ // layer 1: regex
163
+ const regexResult = filterShoutContent(fields);
164
+ // layer 2: LLM check on user-generated text (take, summary)
165
+ // skip for title/description if they came from fetched webpage metadata
166
+ const textsToCheck = [
167
+ regexResult.take,
168
+ regexResult.summary,
169
+ ...(fields.skipLLMForMetadata ? [] : [regexResult.title, regexResult.description]),
170
+ ].filter(Boolean).join('\n\n');
171
+ if (textsToCheck.length > 20) {
172
+ const llmResult = await llmContentFilter(textsToCheck);
173
+ if (!llmResult.safe) {
174
+ return {
175
+ ...regexResult,
176
+ blocked: true,
177
+ blockReason: llmResult.reason || 'LLM filter flagged content as containing private data',
178
+ filterReport: [regexResult.filterReport, `LLM blocked: ${llmResult.reason}`].filter(Boolean).join('; '),
179
+ };
180
+ }
181
+ }
182
+ return {
183
+ ...regexResult,
184
+ blocked: false,
185
+ blockReason: null,
186
+ };
187
+ }
188
+ //# sourceMappingURL=content-filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-filter.js","sourceRoot":"","sources":["../../src/lib/content-filter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Context extraction pipeline for nod intros.
3
+ *
4
+ * Takes natural language from conversations and extracts structured
5
+ * profile data: needs, offers, projects, interests, expertise.
6
+ *
7
+ * Two modes:
8
+ * 1. keyword-based (fast, no API call) — catches explicit signals
9
+ * 2. LLM-based (better, uses gpt-4o-mini) — understands context
10
+ */
11
+ export interface ExtractedContext {
12
+ needs: {
13
+ description: string;
14
+ urgency: "low" | "medium" | "high";
15
+ category: string;
16
+ }[];
17
+ offers: {
18
+ description: string;
19
+ category: string;
20
+ }[];
21
+ projects: {
22
+ name: string;
23
+ description: string;
24
+ stage: string;
25
+ }[];
26
+ interests: string[];
27
+ expertise: string[];
28
+ industries: string[];
29
+ }
30
+ /**
31
+ * Extract context from text. Uses LLM when available, falls back to keywords.
32
+ */
33
+ export declare function extractContext(text: string, useLLM?: boolean): Promise<ExtractedContext>;
34
+ /**
35
+ * Check if text contains any signals worth extracting.
36
+ * Fast check to avoid unnecessary LLM calls.
37
+ */
38
+ export declare function hasContextSignals(text: string): boolean;
39
+ //# sourceMappingURL=context-extractor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-extractor.d.ts","sourceRoot":"","sources":["../../src/lib/context-extractor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACvF,MAAM,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACpD,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACjE,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAiJD;;GAEG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,OAAc,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAKpG;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CASvD"}
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Context extraction pipeline for nod intros.
3
+ *
4
+ * Takes natural language from conversations and extracts structured
5
+ * profile data: needs, offers, projects, interests, expertise.
6
+ *
7
+ * Two modes:
8
+ * 1. keyword-based (fast, no API call) — catches explicit signals
9
+ * 2. LLM-based (better, uses gpt-4o-mini) — understands context
10
+ */
11
+ const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
12
+ // keyword patterns that signal needs
13
+ const NEED_PATTERNS = [
14
+ /i(?:'m| am) looking for (.+?)(?:\.|$)/gi,
15
+ /i need (?:help with |a |an )?(.+?)(?:\.|$)/gi,
16
+ /looking for (?:someone|a person|anybody) (?:who|that) (.+?)(?:\.|$)/gi,
17
+ /anyone know (?:a |an )?(.+?)(?:\?|$)/gi,
18
+ /i could use (.+?)(?:\.|$)/gi,
19
+ /hiring (?:a |an )?(.+?)(?:\.|$)/gi,
20
+ ];
21
+ // keyword patterns that signal offers
22
+ const OFFER_PATTERNS = [
23
+ /i(?:'m| am) (?:really )?good at (.+?)(?:\.|$)/gi,
24
+ /i can help with (.+?)(?:\.|$)/gi,
25
+ /i(?:'ve| have) (?:years of )?experience (?:in |with )(.+?)(?:\.|$)/gi,
26
+ /my expertise is (?:in )?(.+?)(?:\.|$)/gi,
27
+ /i specialize in (.+?)(?:\.|$)/gi,
28
+ /i offer (.+?)(?:\.|$)/gi,
29
+ ];
30
+ // keyword patterns that signal projects
31
+ const PROJECT_PATTERNS = [
32
+ /i(?:'m| am) (?:currently )?(?:building|working on|developing|creating) (.+?)(?:\.|$)/gi,
33
+ /i(?:'ve| have) been (?:building|working on) (.+?)(?:\.|$)/gi,
34
+ /my (?:current )?(?:project|startup|company|app|product) (?:is |called )?(.+?)(?:\.|$)/gi,
35
+ /just launched (.+?)(?:\.|$)/gi,
36
+ ];
37
+ function extractKeyword(text) {
38
+ const result = {
39
+ needs: [],
40
+ offers: [],
41
+ projects: [],
42
+ interests: [],
43
+ expertise: [],
44
+ industries: [],
45
+ };
46
+ for (const pattern of NEED_PATTERNS) {
47
+ pattern.lastIndex = 0;
48
+ let match;
49
+ while ((match = pattern.exec(text)) !== null) {
50
+ const desc = match[1].trim();
51
+ if (desc.length > 5 && desc.length < 200) {
52
+ result.needs.push({ description: desc, urgency: "medium", category: "general" });
53
+ }
54
+ }
55
+ }
56
+ for (const pattern of OFFER_PATTERNS) {
57
+ pattern.lastIndex = 0;
58
+ let match;
59
+ while ((match = pattern.exec(text)) !== null) {
60
+ const desc = match[1].trim();
61
+ if (desc.length > 3 && desc.length < 200) {
62
+ result.offers.push({ description: desc, category: "general" });
63
+ }
64
+ }
65
+ }
66
+ for (const pattern of PROJECT_PATTERNS) {
67
+ pattern.lastIndex = 0;
68
+ let match;
69
+ while ((match = pattern.exec(text)) !== null) {
70
+ const desc = match[1].trim();
71
+ if (desc.length > 3 && desc.length < 200) {
72
+ result.projects.push({ name: desc.split(/\s+/).slice(0, 4).join(" "), description: desc, stage: "building" });
73
+ }
74
+ }
75
+ }
76
+ return result;
77
+ }
78
+ async function extractLLM(text) {
79
+ if (!OPENAI_API_KEY)
80
+ return extractKeyword(text);
81
+ try {
82
+ const response = await fetch("https://api.openai.com/v1/chat/completions", {
83
+ method: "POST",
84
+ headers: {
85
+ "Content-Type": "application/json",
86
+ Authorization: `Bearer ${OPENAI_API_KEY}`,
87
+ },
88
+ body: JSON.stringify({
89
+ model: "gpt-4o-mini",
90
+ messages: [
91
+ {
92
+ role: "system",
93
+ content: `Extract structured profile data from conversation text for a professional networking tool. Return valid JSON only.
94
+
95
+ Extract:
96
+ - needs: things the person is looking for (skills, connections, advice, tools)
97
+ - offers: things they can provide (expertise, connections, resources)
98
+ - projects: what they're currently working on
99
+ - interests: topics they care about
100
+ - expertise: areas they're skilled in
101
+ - industries: sectors they work in
102
+
103
+ Rules:
104
+ - Only extract what's explicitly stated or strongly implied
105
+ - Keep descriptions concise (under 100 chars)
106
+ - Omit any field with no data (empty array)
107
+ - Never include names, emails, or identifying info
108
+ - If nothing relevant is found, return all empty arrays
109
+
110
+ JSON schema:
111
+ {
112
+ "needs": [{"description": string, "urgency": "low"|"medium"|"high", "category": string}],
113
+ "offers": [{"description": string, "category": string}],
114
+ "projects": [{"name": string, "description": string, "stage": "idea"|"building"|"launched"|"growing"}],
115
+ "interests": [string],
116
+ "expertise": [string],
117
+ "industries": [string]
118
+ }`,
119
+ },
120
+ { role: "user", content: text },
121
+ ],
122
+ temperature: 0,
123
+ max_tokens: 500,
124
+ response_format: { type: "json_object" },
125
+ }),
126
+ });
127
+ if (!response.ok)
128
+ return extractKeyword(text);
129
+ const data = (await response.json());
130
+ const content = data.choices?.[0]?.message?.content;
131
+ if (!content)
132
+ return extractKeyword(text);
133
+ const parsed = JSON.parse(content);
134
+ return {
135
+ needs: parsed.needs || [],
136
+ offers: parsed.offers || [],
137
+ projects: parsed.projects || [],
138
+ interests: parsed.interests || [],
139
+ expertise: parsed.expertise || [],
140
+ industries: parsed.industries || [],
141
+ };
142
+ }
143
+ catch {
144
+ return extractKeyword(text);
145
+ }
146
+ }
147
+ /**
148
+ * Extract context from text. Uses LLM when available, falls back to keywords.
149
+ */
150
+ export async function extractContext(text, useLLM = true) {
151
+ if (useLLM && OPENAI_API_KEY) {
152
+ return extractLLM(text);
153
+ }
154
+ return extractKeyword(text);
155
+ }
156
+ /**
157
+ * Check if text contains any signals worth extracting.
158
+ * Fast check to avoid unnecessary LLM calls.
159
+ */
160
+ export function hasContextSignals(text) {
161
+ const lower = text.toLowerCase();
162
+ const signals = [
163
+ "looking for", "i need", "help with", "hiring",
164
+ "i'm building", "working on", "my project", "just launched",
165
+ "good at", "can help", "experience in", "specialize in",
166
+ "interested in", "my expertise",
167
+ ];
168
+ return signals.some(s => lower.includes(s));
169
+ }
170
+ //# sourceMappingURL=context-extractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-extractor.js","sourceRoot":"","sources":["../../src/lib/context-extractor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * nod intros match engine
3
+ *
4
+ * scores pairs of opted-in users based on:
5
+ * - need/offer alignment (40%) — does user A need what user B offers?
6
+ * - interest/expertise overlap (20%) — shared topics
7
+ * - industry overlap (20%) — same industries
8
+ * - recency (10%) — both recently active
9
+ * - history (10%) — haven't been matched before / past success
10
+ *
11
+ * runs as a callable function (triggered by cron or on profile update)
12
+ */
13
+ /**
14
+ * Run the match engine.
15
+ * Finds all opted-in, non-paused profiles, scores all pairs,
16
+ * and creates match records for scores above threshold.
17
+ */
18
+ export declare function runMatchEngine(options?: {
19
+ minScore?: number;
20
+ maxMatchesPerUser?: number;
21
+ useLLMPitches?: boolean;
22
+ }): Promise<{
23
+ matchesCreated: number;
24
+ profilesChecked: number;
25
+ errors: string[];
26
+ }>;
27
+ /**
28
+ * Expire old matches that haven't been responded to.
29
+ */
30
+ export declare function expireStaleMatches(): Promise<number>;
31
+ //# sourceMappingURL=match-engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"match-engine.d.ts","sourceRoot":"","sources":["../../src/lib/match-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAuOH;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,OAAO,CAAC,EAAE;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,GAAG,OAAO,CAAC;IAAE,cAAc,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CA+IjF;AAED;;GAEG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAS1D"}