create-claudeportal 0.3.9 → 0.4.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.
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-CjHE0n_q.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-Dorw4dQE.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/assets/index-DuJJJuzg.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.9",
3
+ "version": "0.4.0",
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"
@@ -15,6 +15,7 @@
15
15
  "test:watch": "vitest"
16
16
  },
17
17
  "dependencies": {
18
+ "@anthropic-ai/sdk": "^0.82.0",
18
19
  "@tailwindcss/typography": "^0.5.19",
19
20
  "@xterm/addon-fit": "^0.11.0",
20
21
  "@xterm/addon-web-links": "^0.12.0",
package/server/index.js CHANGED
@@ -20,6 +20,7 @@ const folderRoutes = require('./routes/folder')
20
20
  const { createDocEventsRouter } = require('./routes/doc-events')
21
21
  const { createPreviewProxy } = require('./routes/preview-proxy')
22
22
  const brainRoutes = require('./routes/brain')
23
+ const agentRoutes = require('./routes/agent')
23
24
 
24
25
  function startServer(port) {
25
26
  return new Promise((resolve) => {
@@ -114,6 +115,7 @@ function startServer(port) {
114
115
  app.use('/api', folderRoutes)
115
116
  app.use('/api', createDocEventsRouter(sseManager))
116
117
  app.use('/api', brainRoutes)
118
+ app.use('/api', agentRoutes)
117
119
 
118
120
  // Preview proxy — captures browser console errors and feeds them to terminal
119
121
  app.use('/api', createPreviewProxy(() => activePty))
@@ -0,0 +1,64 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const os = require('os')
4
+
5
+ const DEFAULT_BRAIN_DIR = path.join(os.homedir(), 'Claude', 'brain')
6
+ const MAX_HISTORY = 100
7
+
8
+ function getBrainDir() {
9
+ return module.exports.DEFAULT_BRAIN_DIR
10
+ }
11
+
12
+ function getHistory(agentId, brainDir = getBrainDir()) {
13
+ const filePath = path.join(brainDir, 'chats', `${agentId}.json`)
14
+ if (!fs.existsSync(filePath)) return []
15
+ try {
16
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'))
17
+ } catch {
18
+ return []
19
+ }
20
+ }
21
+
22
+ function saveHistory(agentId, messages, brainDir = getBrainDir()) {
23
+ const dir = path.join(brainDir, 'chats')
24
+ fs.mkdirSync(dir, { recursive: true })
25
+ const trimmed = messages.slice(-MAX_HISTORY)
26
+ fs.writeFileSync(path.join(dir, `${agentId}.json`), JSON.stringify(trimmed, null, 2))
27
+ }
28
+
29
+ function getTasks(agentId, brainDir = getBrainDir()) {
30
+ const filePath = path.join(brainDir, 'tasks', `${agentId}.json`)
31
+ if (!fs.existsSync(filePath)) return []
32
+ try {
33
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'))
34
+ } catch {
35
+ return []
36
+ }
37
+ }
38
+
39
+ function saveTasks(agentId, tasks, brainDir = getBrainDir()) {
40
+ const dir = path.join(brainDir, 'tasks')
41
+ fs.mkdirSync(dir, { recursive: true })
42
+ fs.writeFileSync(path.join(dir, `${agentId}.json`), JSON.stringify(tasks, null, 2))
43
+ }
44
+
45
+ function getApiKey(brainDir = getBrainDir()) {
46
+ // Check local .agent-env file first
47
+ const envPath = path.join(brainDir, '.agent-env')
48
+ if (fs.existsSync(envPath)) {
49
+ try {
50
+ const content = fs.readFileSync(envPath, 'utf8')
51
+ const match = content.match(/ANTHROPIC_API_KEY=(.+)/)
52
+ if (match && match[1].trim()) return match[1].trim()
53
+ } catch {}
54
+ }
55
+ // Fall back to process.env
56
+ return process.env.ANTHROPIC_API_KEY || null
57
+ }
58
+
59
+ function saveApiKey(apiKey, brainDir = getBrainDir()) {
60
+ fs.mkdirSync(brainDir, { recursive: true })
61
+ fs.writeFileSync(path.join(brainDir, '.agent-env'), `ANTHROPIC_API_KEY=${apiKey}\n`)
62
+ }
63
+
64
+ module.exports = { getHistory, saveHistory, getTasks, saveTasks, getApiKey, saveApiKey, DEFAULT_BRAIN_DIR }
@@ -0,0 +1,81 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+
4
+ const AGENTS = {
5
+ zarlo: {
6
+ id: 'zarlo',
7
+ name: 'Zarlo',
8
+ role: 'Strategy & Research',
9
+ description: 'Analyses your business, recommends approach, coordinates the team',
10
+ personality: 'Playful, direct, connector',
11
+ color: '#8b5cf6',
12
+ initial: 'Z',
13
+ brainReads: ['business', 'industry', 'voice', 'competitors'],
14
+ },
15
+ perry: {
16
+ id: 'perry',
17
+ name: 'Perry',
18
+ role: 'Offer Specialist',
19
+ description: 'Builds Grand Slam Offers — value stack, pricing, guarantees, bonuses',
20
+ personality: 'Structured, precise, builder',
21
+ color: '#3b82f6',
22
+ initial: 'P',
23
+ brainReads: ['business', 'industry'],
24
+ },
25
+ }
26
+
27
+ function getAgent(agentId) {
28
+ return AGENTS[agentId] || null
29
+ }
30
+
31
+ function buildAgentPrompt(agentId, brainDir) {
32
+ const agent = AGENTS[agentId]
33
+ if (!agent) return ''
34
+
35
+ let prompt = `You are ${agent.name}, the ${agent.role}.\n`
36
+ prompt += `Personality: ${agent.personality}.\n`
37
+ prompt += `Your job: ${agent.description}.\n\n`
38
+
39
+ let hasContent = false
40
+ for (const category of agent.brainReads) {
41
+ const filePath = path.join(brainDir, `${category}.md`)
42
+ if (fs.existsSync(filePath)) {
43
+ const content = fs.readFileSync(filePath, 'utf8')
44
+ if (content.trim().length > 20) {
45
+ prompt += content + '\n\n'
46
+ hasContent = true
47
+ }
48
+ }
49
+ }
50
+
51
+ if (!hasContent) {
52
+ prompt += 'No business context available yet. Ask about the business.\n\n'
53
+ }
54
+
55
+ prompt += `## Guidelines\n`
56
+ prompt += `- Be concise and actionable\n`
57
+ prompt += `- Reference Hormozi frameworks where relevant\n`
58
+ prompt += `- When you learn new business information, note it clearly\n`
59
+ prompt += `- When you commit to a deliverable, state it as a clear task\n`
60
+
61
+ return prompt
62
+ }
63
+
64
+ function trimHistoryToTokenBudget(history, tokenBudget = 8000) {
65
+ const charsPerToken = 4
66
+ const charBudget = tokenBudget * charsPerToken
67
+ let totalChars = 0
68
+ const result = []
69
+
70
+ // Walk backwards from newest, accumulate until budget reached
71
+ for (let i = history.length - 1; i >= 0; i--) {
72
+ const msgChars = history[i].content.length
73
+ if (totalChars + msgChars > charBudget && result.length > 0) break
74
+ result.unshift(history[i])
75
+ totalChars += msgChars
76
+ }
77
+
78
+ return result
79
+ }
80
+
81
+ module.exports = { AGENTS, getAgent, buildAgentPrompt, trimHistoryToTokenBudget }
@@ -0,0 +1,144 @@
1
+ const express = require('express')
2
+ const { getAgent, buildAgentPrompt, trimHistoryToTokenBudget } = require('../lib/agents')
3
+ const { getHistory, saveHistory, getTasks, saveTasks, getApiKey, saveApiKey } = require('../lib/agent-storage')
4
+
5
+ const router = express.Router()
6
+
7
+ // Check if API key is configured
8
+ router.get('/agent-config', (_req, res) => {
9
+ const key = getApiKey()
10
+ res.json({ hasApiKey: !!key })
11
+ })
12
+
13
+ // Save API key
14
+ router.post('/agent-config', (req, res) => {
15
+ const { apiKey } = req.body
16
+ if (!apiKey || typeof apiKey !== 'string' || !apiKey.startsWith('sk-')) {
17
+ return res.status(400).json({ error: 'Invalid API key — should start with sk-' })
18
+ }
19
+ try {
20
+ saveApiKey(apiKey)
21
+ res.json({ saved: true })
22
+ } catch (err) {
23
+ res.status(500).json({ error: err.message })
24
+ }
25
+ })
26
+
27
+ // Get chat history for an agent
28
+ router.get('/agent-history', (req, res) => {
29
+ const { agentId } = req.query
30
+ if (!agentId || !getAgent(agentId)) {
31
+ return res.status(400).json({ error: 'Invalid agentId' })
32
+ }
33
+ res.json({ messages: getHistory(agentId) })
34
+ })
35
+
36
+ // Save chat history for an agent
37
+ router.post('/agent-history', (req, res) => {
38
+ const { agentId, messages } = req.body
39
+ if (!agentId || !getAgent(agentId)) {
40
+ return res.status(400).json({ error: 'Invalid agentId' })
41
+ }
42
+ try {
43
+ saveHistory(agentId, messages || [])
44
+ res.json({ saved: true })
45
+ } catch (err) {
46
+ res.status(500).json({ error: err.message })
47
+ }
48
+ })
49
+
50
+ // Get tasks for an agent
51
+ router.get('/agent-tasks', (req, res) => {
52
+ const { agentId } = req.query
53
+ if (!agentId || !getAgent(agentId)) {
54
+ return res.status(400).json({ error: 'Invalid agentId' })
55
+ }
56
+ res.json({ tasks: getTasks(agentId) })
57
+ })
58
+
59
+ // Save tasks for an agent
60
+ router.post('/agent-tasks', (req, res) => {
61
+ const { agentId, tasks } = req.body
62
+ if (!agentId || !getAgent(agentId)) {
63
+ return res.status(400).json({ error: 'Invalid agentId' })
64
+ }
65
+ try {
66
+ saveTasks(agentId, tasks || [])
67
+ res.json({ saved: true })
68
+ } catch (err) {
69
+ res.status(500).json({ error: err.message })
70
+ }
71
+ })
72
+
73
+ // Chat with an agent — streams response via SSE
74
+ router.post('/agent-chat', async (req, res) => {
75
+ const { agentId, message, history } = req.body
76
+
77
+ const agent = getAgent(agentId)
78
+ if (!agent) {
79
+ return res.status(400).json({ error: 'Unknown agent' })
80
+ }
81
+
82
+ const apiKey = getApiKey()
83
+ if (!apiKey) {
84
+ return res.status(500).json({ error: 'ANTHROPIC_API_KEY not configured' })
85
+ }
86
+
87
+ // Set up SSE
88
+ res.setHeader('Content-Type', 'text/event-stream')
89
+ res.setHeader('Cache-Control', 'no-cache')
90
+ res.setHeader('Connection', 'keep-alive')
91
+ res.setHeader('X-Accel-Buffering', 'no')
92
+
93
+ const send = (data) => {
94
+ try { res.write(`data: ${JSON.stringify(data)}\n\n`) } catch {}
95
+ }
96
+
97
+ try {
98
+ const Anthropic = require('@anthropic-ai/sdk')
99
+ const client = new Anthropic({ apiKey })
100
+
101
+ const os = require('os')
102
+ const path = require('path')
103
+ const brainDir = path.join(os.homedir(), 'Claude', 'brain')
104
+ const systemPrompt = buildAgentPrompt(agentId, brainDir)
105
+
106
+ // Build messages array — trim history to token budget
107
+ const trimmedHistory = trimHistoryToTokenBudget(history || [], 8000)
108
+ const messages = [
109
+ ...trimmedHistory.map(m => ({ role: m.role, content: m.content })),
110
+ { role: 'user', content: message },
111
+ ]
112
+
113
+ const stream = client.messages.stream({
114
+ model: 'claude-sonnet-4-20250514',
115
+ max_tokens: 4096,
116
+ system: systemPrompt,
117
+ messages,
118
+ })
119
+
120
+ stream.on('text', (text) => {
121
+ send({ delta: text })
122
+ })
123
+
124
+ stream.on('end', () => {
125
+ send({ done: true })
126
+ res.end()
127
+ })
128
+
129
+ stream.on('error', (err) => {
130
+ send({ error: `Agent response failed: ${err.message}` })
131
+ res.end()
132
+ })
133
+
134
+ // Handle client disconnect
135
+ req.on('close', () => {
136
+ stream.abort()
137
+ })
138
+ } catch (err) {
139
+ send({ error: `Agent response failed: ${err.message}` })
140
+ res.end()
141
+ }
142
+ })
143
+
144
+ module.exports = router
@@ -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/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"}
1
+ {"root":["./src/app.tsx","./src/main.tsx","./src/components/agents/agentchat.tsx","./src/components/agents/agentslayout.tsx","./src/components/agents/agentssidebar.tsx","./src/components/agents/apikeysetup.tsx","./src/components/agents/messagebubble.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/agentscontext.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"}