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/assets/index-Dorw4dQE.js +171 -0
- package/dist/index.html +1 -1
- package/package.json +2 -1
- package/server/index.js +2 -0
- package/server/lib/agent-storage.js +64 -0
- package/server/lib/agents.js +81 -0
- package/server/routes/agent.js +144 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/assets/index-CjHE0n_q.js +0 -161
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-
|
|
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
|
+
"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
|
package/tsconfig.tsbuildinfo
CHANGED
|
@@ -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"}
|