create-claudeportal 0.3.5 → 0.3.7

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.
package/dist/index.html CHANGED
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Claude Portal</title>
7
- <script type="module" crossorigin src="/assets/index-CSb4VBNF.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-Bc6yvEXv.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/assets/index-Bps4hEp0.css">
9
9
  </head>
10
10
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-claudeportal",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "Get from npx to a working app in under 5 minutes — Claude Code setup wizard",
5
5
  "bin": {
6
6
  "create-claudeportal": "bin/cli.js"
@@ -208,14 +208,75 @@ function getInstalledMCPs() {
208
208
  return installed
209
209
  }
210
210
 
211
+ // Map business tools to MCPs that connect to them
212
+ const TOOL_TO_MCP = {
213
+ 'hubspot': ['composio'],
214
+ 'salesforce': ['composio'],
215
+ 'pipedrive': ['composio'],
216
+ 'zoho crm': ['composio'],
217
+ 'monday crm': ['composio'],
218
+ 'close': ['composio'],
219
+ 'xero': ['composio'],
220
+ 'quickbooks': ['composio'],
221
+ 'myob': ['composio'],
222
+ 'freshbooks': ['composio'],
223
+ 'mailchimp': ['composio'],
224
+ 'convertkit': ['composio'],
225
+ 'activecampaign': ['composio'],
226
+ 'klaviyo': ['composio'],
227
+ 'mailerlite': ['composio'],
228
+ 'calendly': ['composio'],
229
+ 'cal.com': ['composio'],
230
+ 'later': ['postiz', 'composio'],
231
+ 'buffer': ['postiz', 'composio'],
232
+ 'postiz': ['postiz'],
233
+ 'hootsuite': ['postiz', 'composio'],
234
+ 'metricool': ['postiz'],
235
+ 'notion': ['notion'],
236
+ 'asana': ['composio'],
237
+ 'trello': ['composio'],
238
+ 'clickup': ['composio'],
239
+ 'monday': ['composio'],
240
+ 'linear': ['linear'],
241
+ 'slack': ['slack'],
242
+ 'microsoft teams': ['composio'],
243
+ 'discord': ['composio'],
244
+ 'google chat': ['google-workspace'],
245
+ 'google drive': ['google-workspace'],
246
+ 'stripe': ['composio'],
247
+ 'wordpress': ['firecrawl'],
248
+ 'shopify': ['composio'],
249
+ }
250
+
251
+ function getUserTools() {
252
+ const brainDir = path.join(os.homedir(), 'Claude', 'brain')
253
+ const businessPath = path.join(brainDir, 'business.md')
254
+ if (!fs.existsSync(businessPath)) return []
255
+
256
+ const content = fs.readFileSync(businessPath, 'utf8')
257
+ const match = content.match(/^- \*\*Tools:\*\*\s*(.+)$/m)
258
+ if (!match) return []
259
+
260
+ return match[1].split(',').map(t => t.trim().toLowerCase()).filter(Boolean)
261
+ }
262
+
211
263
  function checkMCPs(outcomeKey) {
212
264
  const installed = getInstalledMCPs()
265
+ const userTools = getUserTools()
266
+
267
+ // Find which MCPs are recommended based on user's business tools
268
+ const recommendedByTools = new Set()
269
+ for (const tool of userTools) {
270
+ const mcpIds = TOOL_TO_MCP[tool] || []
271
+ for (const id of mcpIds) recommendedByTools.add(id)
272
+ }
213
273
 
214
274
  const mcps = RECOMMENDED_MCPS.map(mcp => {
215
275
  const isInstalled = installed.has(mcp.id.toLowerCase()) ||
216
276
  [...installed].some(k => k.includes(mcp.id.toLowerCase()) || mcp.id.toLowerCase().includes(k))
217
277
 
218
278
  const isRelevant = !outcomeKey || mcp.usefulFor.includes(outcomeKey)
279
+ const isRecommended = recommendedByTools.has(mcp.id)
219
280
 
220
281
  return {
221
282
  id: mcp.id,
@@ -223,6 +284,7 @@ function checkMCPs(outcomeKey) {
223
284
  description: mcp.description,
224
285
  installed: isInstalled,
225
286
  relevant: isRelevant,
287
+ recommended: isRecommended,
226
288
  category: mcp.category,
227
289
  categoryLabel: CATEGORY_LABELS[mcp.category] || mcp.category,
228
290
  installHint: isInstalled ? undefined : mcp.installHint,
@@ -92,13 +92,21 @@ function detectSocialLinks(links, html) {
92
92
  twitter: /(twitter\.com|x\.com)\/([a-zA-Z0-9_]+)/i,
93
93
  }
94
94
 
95
- const allText = links.join(' ') + ' ' + html
95
+ // Only search href links (not the full HTML) to avoid matching share buttons, SDKs, and tracking pixels
96
+ const linkText = links.join(' ')
97
+
98
+ // Patterns that indicate a link is NOT a real profile (share buttons, SDKs, login, etc.)
99
+ const falsePositives = /sharer|share\?|dialog\/|connect\.facebook|platform\.twitter|apis\.google|intent\/tweet|oauth|login|signup|widget/i
96
100
 
97
101
  for (const [platform, regex] of Object.entries(patterns)) {
98
- const match = allText.match(regex)
102
+ const match = linkText.match(regex)
99
103
  if (match) {
100
- const fullUrlMatch = allText.match(new RegExp(`https?://[^"'\\s]*${regex.source}`, 'i'))
101
- profiles[platform] = { url: fullUrlMatch ? fullUrlMatch[0] : match[0] }
104
+ const fullUrlMatch = linkText.match(new RegExp(`https?://[^"'\\s]*${regex.source}`, 'i'))
105
+ const url = fullUrlMatch ? fullUrlMatch[0] : match[0]
106
+ // Skip if it looks like a share button or SDK, not a real profile
107
+ if (!falsePositives.test(url)) {
108
+ profiles[platform] = { url }
109
+ }
102
110
  }
103
111
  }
104
112
 
@@ -42,6 +42,92 @@ router.post('/brain/save', (req, res) => {
42
42
  }
43
43
  })
44
44
 
45
+ // Map ChatGPT natural language labels to Brain field names
46
+ const IMPORT_FIELD_MAP = {
47
+ business: {
48
+ 'business name': 'Name',
49
+ 'name': 'Name',
50
+ 'what i sell': 'Description',
51
+ 'products': 'Description',
52
+ 'services': 'Description',
53
+ 'products/services': 'Description',
54
+ 'pricing': 'Pricing',
55
+ 'my pricing': 'Pricing',
56
+ 'price': 'Pricing',
57
+ 'target audience': 'Target Audience',
58
+ 'my target audience': 'Target Audience',
59
+ 'location': 'Location',
60
+ 'my location': 'Location',
61
+ 'team size': 'Team Size',
62
+ 'my team size': 'Team Size',
63
+ 'revenue': 'Revenue Baseline',
64
+ 'revenue range': 'Revenue Baseline',
65
+ 'my revenue range': 'Revenue Baseline',
66
+ 'dream outcome': 'Dream Outcome',
67
+ 'unique offer': 'Dream Outcome',
68
+ 'what makes my offer unique': 'Dream Outcome',
69
+ 'guarantee': 'Guarantee',
70
+ 'guarantees': 'Guarantee',
71
+ 'bonuses': 'Bonuses',
72
+ 'delivery model': 'Value Stack',
73
+ 'my delivery model': 'Value Stack',
74
+ },
75
+ industry: {
76
+ 'industry': 'Sector',
77
+ 'niche': 'Sector',
78
+ 'my industry': 'Sector',
79
+ 'my industry/niche': 'Sector',
80
+ 'seasonal patterns': 'Seasonal Patterns',
81
+ 'common objections': 'Common Objections',
82
+ 'customer objections': 'Common Objections',
83
+ 'common customer objections': 'Common Objections',
84
+ 'buying triggers': 'Buying Triggers',
85
+ 'what triggers people to buy': 'Buying Triggers',
86
+ 'customer lifetime value': 'Avg Customer Value',
87
+ 'average customer lifetime value': 'Avg Customer Value',
88
+ },
89
+ voice: {
90
+ 'communication style': 'Tone',
91
+ 'how i like to communicate': 'Tone',
92
+ 'tone': 'Tone',
93
+ 'words or phrases i use often': 'Tone',
94
+ 'things i\'d never say': 'Words To Avoid',
95
+ 'words to avoid': 'Words To Avoid',
96
+ 'preferred length': 'Preferred Length',
97
+ 'my preferred email/content length': 'Preferred Length',
98
+ 'brand personality': 'Template',
99
+ 'my brand personality': 'Template',
100
+ },
101
+ competitors: {
102
+ 'competitors': 'Identified',
103
+ 'competitors i\'ve mentioned': 'Identified',
104
+ 'how i position against them': 'Differentiator',
105
+ 'positioning': 'Differentiator',
106
+ 'what they do well': 'Their Strengths',
107
+ 'where they fall short': 'Their Weaknesses',
108
+ },
109
+ }
110
+
111
+ function extractFieldsFromText(category, text) {
112
+ const fieldMap = IMPORT_FIELD_MAP[category] || {}
113
+ const fields = {}
114
+
115
+ // Try to match "- Label: value" or "- **Label:** value" patterns from ChatGPT output
116
+ const lines = text.split('\n')
117
+ for (const line of lines) {
118
+ const match = line.match(/^[-•*]\s*\*{0,2}([^:*]+?)\*{0,2}\s*[:—–-]\s*(.+)$/i)
119
+ if (match) {
120
+ const label = match[1].trim().toLowerCase()
121
+ const value = match[2].trim()
122
+ if (value && fieldMap[label]) {
123
+ fields[fieldMap[label]] = value
124
+ }
125
+ }
126
+ }
127
+
128
+ return fields
129
+ }
130
+
45
131
  // Import ChatGPT export text
46
132
  router.post('/brain/import', (req, res) => {
47
133
  const { text } = req.body
@@ -53,6 +139,12 @@ router.post('/brain/import', (req, res) => {
53
139
  const imported = []
54
140
  for (const [category, content] of Object.entries(sections)) {
55
141
  if (content.trim()) {
142
+ // Try to extract structured fields first
143
+ const fields = extractFieldsFromText(category, content)
144
+ if (Object.keys(fields).length > 0) {
145
+ saveBrainCategory(category, fields)
146
+ }
147
+ // Also save raw context as backup
56
148
  appendRawContext(category, content)
57
149
  imported.push(category)
58
150
  }
@@ -87,12 +179,19 @@ router.post('/brain/documents', express.raw({ type: '*/*', limit: '50mb' }), (re
87
179
  fs.writeFileSync(filePath, req.body)
88
180
 
89
181
  const textExts = ['.txt', '.md', '.csv']
182
+ let extracted = false
90
183
  if (textExts.includes(ext)) {
91
184
  const content = req.body.toString('utf8')
92
185
  const sections = parseImport(content)
93
186
  let hadSections = false
94
187
  for (const [category, text] of Object.entries(sections)) {
95
188
  if (text.trim()) {
189
+ // Extract structured fields where possible
190
+ const fields = extractFieldsFromText(category, text)
191
+ if (Object.keys(fields).length > 0) {
192
+ saveBrainCategory(category, fields)
193
+ extracted = true
194
+ }
96
195
  appendRawContext(category, text)
97
196
  hadSections = true
98
197
  }
@@ -101,11 +200,21 @@ router.post('/brain/documents', express.raw({ type: '*/*', limit: '50mb' }), (re
101
200
  const guessed = guessCategoryFromFilename(filename)
102
201
  appendRawContext(guessed, content.trim())
103
202
  }
203
+ // hadSections means raw context was saved, extracted means structured fields were found
104
204
  }
105
205
 
106
206
  ensureClaudeMdReference()
107
207
  const status = getBrainStatus()
108
- res.json({ saved: true, filename, ...status })
208
+ const binaryFile = !textExts.includes(ext)
209
+ let message = 'File saved'
210
+ if (binaryFile) {
211
+ message = `${ext.toUpperCase().slice(1)} saved — Claude will read this when you start working`
212
+ } else if (extracted) {
213
+ message = 'Content extracted and added to Brain'
214
+ } else if (hadSections) {
215
+ message = 'Content saved as context'
216
+ }
217
+ res.json({ saved: true, filename, extracted, message, ...status })
109
218
  } catch (err) {
110
219
  res.status(500).json({ error: err.message })
111
220
  }
@@ -1 +1 @@
1
- {"root":["./src/app.tsx","./src/main.tsx","./src/components/brain/brainonboarding.tsx","./src/components/brain/brainstepdocuments.tsx","./src/components/brain/brainstepimport.tsx","./src/components/brain/brainstepwebsite.tsx","./src/components/chat/buildsidebar.tsx","./src/components/checklist/brainstatussection.tsx","./src/components/checklist/clistatussection.tsx","./src/components/checklist/featurebuilder.tsx","./src/components/checklist/futurebuildsection.tsx","./src/components/checklist/healthcheckitem.tsx","./src/components/checklist/healthsection.tsx","./src/components/checklist/prepushsection.tsx","./src/components/checklist/testingsection.tsx","./src/components/document/contextsection.tsx","./src/components/document/docsidebar.tsx","./src/components/document/foldersection.tsx","./src/components/document/mcpsection.tsx","./src/components/document/outcomesection.tsx","./src/components/document/outputpanel.tsx","./src/components/document/stylereferencesection.tsx","./src/components/layout/checklistdrawer.tsx","./src/components/layout/errorbanner.tsx","./src/components/layout/iconrail.tsx","./src/components/layout/topbar.tsx","./src/components/preview/previewpanel.tsx","./src/components/setup/howitworks.tsx","./src/components/setup/projectselector.tsx","./src/components/setup/setupwizard.tsx","./src/components/setup/tooldetection.tsx","./src/components/setup/toolinstaller.tsx","./src/components/terminal/terminalpanel.tsx","./src/context/docmodecontext.tsx","./src/context/projectcontext.tsx","./src/context/terminalcontext.tsx","./src/hooks/usedocevents.ts","./src/hooks/usehealthscan.ts","./src/hooks/useprojectinfo.ts","./src/hooks/usesse.ts","./src/lib/api.ts","./src/lib/doc-prompts.ts","./src/lib/storage.ts","./src/lib/style-templates.ts","./src/types/doc.ts","./src/types/index.ts"],"version":"5.9.3"}
1
+ {"root":["./src/app.tsx","./src/main.tsx","./src/components/brain/brainonboarding.tsx","./src/components/brain/brainstepdocuments.tsx","./src/components/brain/brainstepimport.tsx","./src/components/brain/brainsteptools.tsx","./src/components/brain/brainstepwebsite.tsx","./src/components/chat/buildsidebar.tsx","./src/components/checklist/brainstatussection.tsx","./src/components/checklist/clistatussection.tsx","./src/components/checklist/featurebuilder.tsx","./src/components/checklist/futurebuildsection.tsx","./src/components/checklist/healthcheckitem.tsx","./src/components/checklist/healthsection.tsx","./src/components/checklist/prepushsection.tsx","./src/components/checklist/testingsection.tsx","./src/components/document/contextsection.tsx","./src/components/document/docsidebar.tsx","./src/components/document/foldersection.tsx","./src/components/document/mcpsection.tsx","./src/components/document/outcomesection.tsx","./src/components/document/outputpanel.tsx","./src/components/document/stylereferencesection.tsx","./src/components/layout/checklistdrawer.tsx","./src/components/layout/errorbanner.tsx","./src/components/layout/iconrail.tsx","./src/components/layout/topbar.tsx","./src/components/preview/previewpanel.tsx","./src/components/setup/howitworks.tsx","./src/components/setup/projectselector.tsx","./src/components/setup/setupwizard.tsx","./src/components/setup/tooldetection.tsx","./src/components/setup/toolinstaller.tsx","./src/components/terminal/terminalpanel.tsx","./src/context/docmodecontext.tsx","./src/context/projectcontext.tsx","./src/context/terminalcontext.tsx","./src/hooks/usedocevents.ts","./src/hooks/usehealthscan.ts","./src/hooks/useprojectinfo.ts","./src/hooks/usesse.ts","./src/lib/api.ts","./src/lib/doc-prompts.ts","./src/lib/storage.ts","./src/lib/style-templates.ts","./src/types/doc.ts","./src/types/index.ts"],"version":"5.9.3"}