create-claudeportal 0.1.0 → 0.2.1
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-BG0yZd9Y.css +1 -0
- package/dist/assets/index-DT9wvgYq.js +132 -0
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/server/index.js +4 -0
- package/server/lib/cli-checker.js +61 -0
- package/server/lib/install-tools.js +49 -1
- package/server/lib/mcp-checker.js +217 -0
- package/server/routes/health.js +19 -0
- package/server/routes/install.js +44 -8
- package/server/routes/preview-proxy.js +136 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/assets/index-BBU5K5iA.js +0 -132
- package/dist/assets/index-fNmv07eE.css +0 -1
package/dist/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
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-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-DT9wvgYq.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BG0yZd9Y.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -18,6 +18,7 @@ const { createEventsRouter } = require('./routes/events')
|
|
|
18
18
|
const { validateProjectPath } = require('./lib/validate-path')
|
|
19
19
|
const folderRoutes = require('./routes/folder')
|
|
20
20
|
const { createDocEventsRouter } = require('./routes/doc-events')
|
|
21
|
+
const { createPreviewProxy } = require('./routes/preview-proxy')
|
|
21
22
|
|
|
22
23
|
function startServer(port) {
|
|
23
24
|
return new Promise((resolve) => {
|
|
@@ -112,6 +113,9 @@ function startServer(port) {
|
|
|
112
113
|
app.use('/api', folderRoutes)
|
|
113
114
|
app.use('/api', createDocEventsRouter(sseManager))
|
|
114
115
|
|
|
116
|
+
// Preview proxy — captures browser console errors and feeds them to terminal
|
|
117
|
+
app.use('/api', createPreviewProxy(() => activePty))
|
|
118
|
+
|
|
115
119
|
// Serve project files for live preview
|
|
116
120
|
app.use('/preview', (req, res) => {
|
|
117
121
|
const projectPath = validateProjectPath(req.query.projectPath) || path.join(os.homedir(), 'Claude')
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const { execSync } = require('child_process')
|
|
2
|
+
|
|
3
|
+
function checkCli(command, parseOutput) {
|
|
4
|
+
try {
|
|
5
|
+
const output = execSync(command, { timeout: 10000, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim()
|
|
6
|
+
return parseOutput(output)
|
|
7
|
+
} catch {
|
|
8
|
+
return { connected: false, detail: 'Not installed' }
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function checkCLIs() {
|
|
13
|
+
const results = []
|
|
14
|
+
|
|
15
|
+
// GitHub CLI
|
|
16
|
+
results.push({
|
|
17
|
+
id: 'github',
|
|
18
|
+
label: 'GitHub',
|
|
19
|
+
...checkCli('gh auth status 2>&1', (output) => {
|
|
20
|
+
if (output.includes('Logged in to')) {
|
|
21
|
+
const match = output.match(/Logged in to ([^\s]+) as ([^\s]+)/)
|
|
22
|
+
return {
|
|
23
|
+
connected: true,
|
|
24
|
+
detail: match ? `${match[2]}` : 'Connected',
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return { connected: false, detail: 'Not authenticated — run: gh auth login' }
|
|
28
|
+
}),
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
// Vercel CLI
|
|
32
|
+
results.push({
|
|
33
|
+
id: 'vercel',
|
|
34
|
+
label: 'Vercel',
|
|
35
|
+
...checkCli('vercel whoami 2>&1', (output) => {
|
|
36
|
+
if (output && !output.includes('Error') && !output.includes('not logged in')) {
|
|
37
|
+
return { connected: true, detail: output.split('\n').pop().trim() }
|
|
38
|
+
}
|
|
39
|
+
return { connected: false, detail: 'Not authenticated — run: vercel login' }
|
|
40
|
+
}),
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// Supabase CLI
|
|
44
|
+
results.push({
|
|
45
|
+
id: 'supabase',
|
|
46
|
+
label: 'Supabase',
|
|
47
|
+
...checkCli('supabase projects list 2>&1', (output) => {
|
|
48
|
+
if (output.includes('ID') || output.includes('│')) {
|
|
49
|
+
return { connected: true, detail: 'Connected' }
|
|
50
|
+
}
|
|
51
|
+
if (output.includes('not logged in') || output.includes('access token')) {
|
|
52
|
+
return { connected: false, detail: 'Not authenticated — run: supabase login' }
|
|
53
|
+
}
|
|
54
|
+
return { connected: false, detail: 'Not installed — run: brew install supabase/tap/supabase' }
|
|
55
|
+
}),
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
return results
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = { checkCLIs }
|
|
@@ -44,10 +44,58 @@ async function installTool(toolId, onProgress) {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
async function installViaNpm(packageName, onProgress) {
|
|
47
|
+
const os = require('os')
|
|
48
|
+
const path = require('path')
|
|
49
|
+
const fs = require('fs')
|
|
50
|
+
|
|
51
|
+
// Check if npm prefix needs fixing (avoid /usr/local which requires sudo)
|
|
52
|
+
let npmArgs = ['install', '-g', packageName]
|
|
53
|
+
try {
|
|
54
|
+
const { execSync } = require('child_process')
|
|
55
|
+
const currentPrefix = execSync('npm config get prefix', {
|
|
56
|
+
encoding: 'utf8',
|
|
57
|
+
timeout: 5000,
|
|
58
|
+
env: { ...process.env, PATH: getExpandedPath() },
|
|
59
|
+
}).trim()
|
|
60
|
+
|
|
61
|
+
if (currentPrefix === '/usr/local') {
|
|
62
|
+
// Use a user-owned prefix instead
|
|
63
|
+
const userPrefix = path.join(os.homedir(), '.npm-global')
|
|
64
|
+
fs.mkdirSync(path.join(userPrefix, 'bin'), { recursive: true })
|
|
65
|
+
fs.mkdirSync(path.join(userPrefix, 'lib'), { recursive: true })
|
|
66
|
+
npmArgs = ['install', '-g', '--prefix', userPrefix, packageName]
|
|
67
|
+
onProgress(`Using ${userPrefix} to avoid permission issues...`)
|
|
68
|
+
|
|
69
|
+
// Also persist the config for future installs
|
|
70
|
+
try {
|
|
71
|
+
execSync(`npm config set prefix "${userPrefix}"`, {
|
|
72
|
+
timeout: 5000,
|
|
73
|
+
env: { ...process.env, PATH: getExpandedPath() },
|
|
74
|
+
})
|
|
75
|
+
} catch {}
|
|
76
|
+
|
|
77
|
+
// Add to PATH for this process
|
|
78
|
+
const binDir = path.join(userPrefix, 'bin')
|
|
79
|
+
if (!process.env.PATH.includes(binDir)) {
|
|
80
|
+
process.env.PATH = `${binDir}:${process.env.PATH}`
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Add to .zshrc if not already there
|
|
84
|
+
const zshrc = path.join(os.homedir(), '.zshrc')
|
|
85
|
+
const pathLine = `export PATH="$HOME/.npm-global/bin:$PATH"`
|
|
86
|
+
try {
|
|
87
|
+
const existing = fs.existsSync(zshrc) ? fs.readFileSync(zshrc, 'utf8') : ''
|
|
88
|
+
if (!existing.includes('.npm-global')) {
|
|
89
|
+
fs.appendFileSync(zshrc, `\n# Added by Claude Portal\n${pathLine}\n`)
|
|
90
|
+
}
|
|
91
|
+
} catch {}
|
|
92
|
+
}
|
|
93
|
+
} catch {}
|
|
94
|
+
|
|
47
95
|
onProgress(`Installing ${packageName} via npm...`)
|
|
48
96
|
|
|
49
97
|
return new Promise((resolve) => {
|
|
50
|
-
const child = spawn('npm',
|
|
98
|
+
const child = spawn('npm', npmArgs, {
|
|
51
99
|
env: { ...process.env, PATH: getExpandedPath() },
|
|
52
100
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
53
101
|
timeout: 120000,
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const os = require('os')
|
|
4
|
+
|
|
5
|
+
// MCP servers recommended for document mode / work automation
|
|
6
|
+
const RECOMMENDED_MCPS = [
|
|
7
|
+
// Research & Data
|
|
8
|
+
{
|
|
9
|
+
id: 'brave-search',
|
|
10
|
+
name: 'Brave Search',
|
|
11
|
+
description: 'Web search with 2,000 free queries/month',
|
|
12
|
+
installHint: 'claude mcp add brave-search -- npx -y @modelcontextprotocol/server-brave-search',
|
|
13
|
+
usefulFor: ['draft', 'analyse', 'review', 'summarize', 'compare'],
|
|
14
|
+
category: 'research',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: 'serper',
|
|
18
|
+
name: 'Serper (Google Search)',
|
|
19
|
+
description: 'Google search results for research and fact-checking',
|
|
20
|
+
installHint: 'claude mcp add serper -- npx -y mcp-server-serper',
|
|
21
|
+
usefulFor: ['draft', 'analyse', 'review', 'summarize', 'extract', 'compare'],
|
|
22
|
+
category: 'research',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: 'jina',
|
|
26
|
+
name: 'Jina (Web Reader)',
|
|
27
|
+
description: 'Read web pages, PDFs, and extract content',
|
|
28
|
+
installHint: 'Add via Claude settings → MCP',
|
|
29
|
+
usefulFor: ['draft', 'analyse', 'review', 'summarize', 'extract', 'compare', 'transform'],
|
|
30
|
+
category: 'research',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'firecrawl',
|
|
34
|
+
name: 'Firecrawl',
|
|
35
|
+
description: 'Turn any website into clean markdown data',
|
|
36
|
+
installHint: 'claude mcp add firecrawl -- npx -y firecrawl-mcp',
|
|
37
|
+
usefulFor: ['extract', 'analyse', 'summarize', 'transform'],
|
|
38
|
+
category: 'research',
|
|
39
|
+
},
|
|
40
|
+
// Design & Build
|
|
41
|
+
{
|
|
42
|
+
id: 'stitch',
|
|
43
|
+
name: 'Google Stitch',
|
|
44
|
+
description: 'AI design-to-code — generate UI from text prompts',
|
|
45
|
+
installHint: 'claude mcp add stitch -- npx -y stitch-mcp',
|
|
46
|
+
usefulFor: ['draft', 'transform'],
|
|
47
|
+
category: 'design',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'figma',
|
|
51
|
+
name: 'Figma',
|
|
52
|
+
description: 'Read designs, extract specs, generate layers',
|
|
53
|
+
installHint: 'Enable in Figma Dev Mode → MCP settings',
|
|
54
|
+
usefulFor: ['draft', 'extract', 'transform'],
|
|
55
|
+
category: 'design',
|
|
56
|
+
},
|
|
57
|
+
// Productivity & Workspace
|
|
58
|
+
{
|
|
59
|
+
id: 'google-workspace',
|
|
60
|
+
name: 'Google Workspace',
|
|
61
|
+
description: 'Gmail, Calendar, Docs, Sheets, Drive — 100+ tools',
|
|
62
|
+
installHint: 'claude mcp add google-workspace -- npx -y @alanxchen/google-workspace-mcp',
|
|
63
|
+
usefulFor: ['draft', 'summarize', 'extract', 'analyse', 'transform'],
|
|
64
|
+
category: 'workspace',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'notion',
|
|
68
|
+
name: 'Notion',
|
|
69
|
+
description: 'Read/write pages and query databases',
|
|
70
|
+
installHint: 'Connect via claude.ai settings → MCP',
|
|
71
|
+
usefulFor: ['draft', 'summarize', 'extract', 'transform'],
|
|
72
|
+
category: 'workspace',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 'slack',
|
|
76
|
+
name: 'Slack',
|
|
77
|
+
description: 'Search messages, draft replies, channel summaries',
|
|
78
|
+
installHint: 'claude mcp add slack -- npx -y @anthropic/slack-mcp',
|
|
79
|
+
usefulFor: ['summarize', 'extract', 'draft'],
|
|
80
|
+
category: 'workspace',
|
|
81
|
+
},
|
|
82
|
+
// Automation
|
|
83
|
+
{
|
|
84
|
+
id: 'zapier',
|
|
85
|
+
name: 'Zapier',
|
|
86
|
+
description: 'Connect to 7,000+ apps — automate anything',
|
|
87
|
+
installHint: 'Visit zapier.com/mcp to connect',
|
|
88
|
+
usefulFor: ['draft', 'extract', 'transform'],
|
|
89
|
+
category: 'automation',
|
|
90
|
+
},
|
|
91
|
+
// Meetings & Notes
|
|
92
|
+
{
|
|
93
|
+
id: 'fireflies',
|
|
94
|
+
name: 'Fireflies',
|
|
95
|
+
description: 'Access meeting transcripts and AI summaries',
|
|
96
|
+
installHint: 'claude mcp add fireflies -- npx -y @fireflies-ai/mcp-server',
|
|
97
|
+
usefulFor: ['summarize', 'extract', 'draft', 'analyse'],
|
|
98
|
+
category: 'meetings',
|
|
99
|
+
},
|
|
100
|
+
// Project Management
|
|
101
|
+
{
|
|
102
|
+
id: 'todoist',
|
|
103
|
+
name: 'Todoist',
|
|
104
|
+
description: 'Manage tasks, projects, and deadlines',
|
|
105
|
+
installHint: 'claude mcp add todoist (see pulsemcp.com/servers/todoist)',
|
|
106
|
+
usefulFor: ['extract', 'draft'],
|
|
107
|
+
category: 'projects',
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: 'linear',
|
|
111
|
+
name: 'Linear',
|
|
112
|
+
description: 'Manage issues, sprints, and backlogs',
|
|
113
|
+
installHint: 'claude mcp add linear -- npx -y @mkusaka/linear-mcp',
|
|
114
|
+
usefulFor: ['extract', 'analyse', 'summarize'],
|
|
115
|
+
category: 'projects',
|
|
116
|
+
},
|
|
117
|
+
// Data & Database
|
|
118
|
+
{
|
|
119
|
+
id: 'supabase',
|
|
120
|
+
name: 'Supabase',
|
|
121
|
+
description: 'Query databases, manage tables, run migrations',
|
|
122
|
+
installHint: 'Add via Claude settings → MCP',
|
|
123
|
+
usefulFor: ['extract', 'analyse', 'transform'],
|
|
124
|
+
category: 'data',
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: 'postgres',
|
|
128
|
+
name: 'PostgreSQL',
|
|
129
|
+
description: 'Query any Postgres database with natural language',
|
|
130
|
+
installHint: 'claude mcp add postgres -- npx -y @crystaldba/postgres-mcp',
|
|
131
|
+
usefulFor: ['extract', 'analyse'],
|
|
132
|
+
category: 'data',
|
|
133
|
+
},
|
|
134
|
+
// Memory & Thinking
|
|
135
|
+
{
|
|
136
|
+
id: 'memory-keeper',
|
|
137
|
+
name: 'Memory Keeper',
|
|
138
|
+
description: 'Remember context across conversations',
|
|
139
|
+
installHint: 'claude mcp add memory-keeper -- npx -y mcp-memory-keeper',
|
|
140
|
+
usefulFor: ['draft', 'analyse', 'review'],
|
|
141
|
+
category: 'utility',
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
id: 'sequential-thinking',
|
|
145
|
+
name: 'Sequential Thinking',
|
|
146
|
+
description: 'Break down complex problems step by step',
|
|
147
|
+
installHint: 'claude mcp add sequential-thinking -- npx -y @modelcontextprotocol/server-sequential-thinking',
|
|
148
|
+
usefulFor: ['analyse', 'review', 'compare'],
|
|
149
|
+
category: 'utility',
|
|
150
|
+
},
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
const CATEGORY_LABELS = {
|
|
154
|
+
research: 'Research',
|
|
155
|
+
design: 'Design & Build',
|
|
156
|
+
workspace: 'Workspace',
|
|
157
|
+
automation: 'Automation',
|
|
158
|
+
meetings: 'Meetings',
|
|
159
|
+
projects: 'Project Management',
|
|
160
|
+
data: 'Data',
|
|
161
|
+
utility: 'Utilities',
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function getInstalledMCPs() {
|
|
165
|
+
const configPaths = [
|
|
166
|
+
path.join(os.homedir(), '.claude.json'),
|
|
167
|
+
path.join(os.homedir(), '.claude', 'settings.json'),
|
|
168
|
+
path.join(os.homedir(), '.claude', 'settings.local.json'),
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
const installed = new Set()
|
|
172
|
+
|
|
173
|
+
for (const configPath of configPaths) {
|
|
174
|
+
try {
|
|
175
|
+
if (!fs.existsSync(configPath)) continue
|
|
176
|
+
const raw = fs.readFileSync(configPath, 'utf8')
|
|
177
|
+
const config = JSON.parse(raw)
|
|
178
|
+
const servers = config.mcpServers || {}
|
|
179
|
+
for (const key of Object.keys(servers)) {
|
|
180
|
+
installed.add(key.toLowerCase())
|
|
181
|
+
}
|
|
182
|
+
// Also check claudeAiMcpEverConnected for claude.ai-connected MCPs
|
|
183
|
+
const connected = config.claudeAiMcpEverConnected || []
|
|
184
|
+
for (const name of connected) {
|
|
185
|
+
installed.add(name.toLowerCase().replace(/^claude\.ai\s+/i, ''))
|
|
186
|
+
}
|
|
187
|
+
} catch {}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return installed
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function checkMCPs(outcomeKey) {
|
|
194
|
+
const installed = getInstalledMCPs()
|
|
195
|
+
|
|
196
|
+
const mcps = RECOMMENDED_MCPS.map(mcp => {
|
|
197
|
+
const isInstalled = installed.has(mcp.id.toLowerCase()) ||
|
|
198
|
+
[...installed].some(k => k.includes(mcp.id.toLowerCase()) || mcp.id.toLowerCase().includes(k))
|
|
199
|
+
|
|
200
|
+
const isRelevant = !outcomeKey || mcp.usefulFor.includes(outcomeKey)
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
id: mcp.id,
|
|
204
|
+
name: mcp.name,
|
|
205
|
+
description: mcp.description,
|
|
206
|
+
installed: isInstalled,
|
|
207
|
+
relevant: isRelevant,
|
|
208
|
+
category: mcp.category,
|
|
209
|
+
categoryLabel: CATEGORY_LABELS[mcp.category] || mcp.category,
|
|
210
|
+
installHint: isInstalled ? undefined : mcp.installHint,
|
|
211
|
+
}
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
return mcps
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
module.exports = { checkMCPs, CATEGORY_LABELS }
|
package/server/routes/health.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
const express = require('express')
|
|
2
2
|
const { scanProject } = require('../lib/file-scanner')
|
|
3
3
|
const { validateProjectPath } = require('../lib/validate-path')
|
|
4
|
+
const { checkCLIs } = require('../lib/cli-checker')
|
|
5
|
+
const { checkMCPs } = require('../lib/mcp-checker')
|
|
4
6
|
const router = express.Router()
|
|
5
7
|
|
|
6
8
|
router.get('/health-scan', (req, res) => {
|
|
@@ -13,4 +15,21 @@ router.get('/health-scan', (req, res) => {
|
|
|
13
15
|
}
|
|
14
16
|
})
|
|
15
17
|
|
|
18
|
+
router.get('/cli-status', (_req, res) => {
|
|
19
|
+
try {
|
|
20
|
+
res.json({ clis: checkCLIs() })
|
|
21
|
+
} catch (err) {
|
|
22
|
+
res.status(500).json({ error: err.message })
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
router.get('/mcp-status', (req, res) => {
|
|
27
|
+
try {
|
|
28
|
+
const outcome = req.query.outcome || null
|
|
29
|
+
res.json({ mcps: checkMCPs(outcome) })
|
|
30
|
+
} catch (err) {
|
|
31
|
+
res.status(500).json({ error: err.message })
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
|
|
16
35
|
module.exports = router
|
package/server/routes/install.js
CHANGED
|
@@ -18,16 +18,30 @@ router.post('/install/:toolId', async (req, res) => {
|
|
|
18
18
|
res.write(`data: ${JSON.stringify(data)}\n\n`)
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
try {
|
|
22
|
+
// Fix npm prefix on Mac to avoid sudo for global installs
|
|
23
|
+
try {
|
|
24
|
+
const prefixResult = fixNpmPrefix()
|
|
25
|
+
if (prefixResult.fixed) {
|
|
26
|
+
send({ tool: toolId, status: 'progress', message: 'npm configured for global installs (no sudo needed)' })
|
|
27
|
+
}
|
|
28
|
+
} catch (prefixErr) {
|
|
29
|
+
send({ tool: toolId, status: 'progress', message: `npm prefix check skipped: ${prefixErr.message}` })
|
|
30
|
+
}
|
|
22
31
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
32
|
+
send({ tool: toolId, status: 'installing', message: `Installing ${toolId}...` })
|
|
33
|
+
|
|
34
|
+
const result = await installTool(toolId, (message) => {
|
|
35
|
+
send({ tool: toolId, status: 'progress', message })
|
|
36
|
+
})
|
|
26
37
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
38
|
+
if (result.success) {
|
|
39
|
+
send({ tool: toolId, status: 'done' })
|
|
40
|
+
} else {
|
|
41
|
+
send({ tool: toolId, status: 'error', error: result.error })
|
|
42
|
+
}
|
|
43
|
+
} catch (err) {
|
|
44
|
+
send({ tool: toolId, status: 'error', error: err.message || 'Unknown error during install' })
|
|
31
45
|
}
|
|
32
46
|
|
|
33
47
|
send({ status: 'complete' })
|
|
@@ -99,4 +113,26 @@ router.post('/install-all', async (req, res) => {
|
|
|
99
113
|
res.end()
|
|
100
114
|
})
|
|
101
115
|
|
|
116
|
+
// Launch Claude Code authentication (opens browser)
|
|
117
|
+
router.post('/launch-auth', (req, res) => {
|
|
118
|
+
const { spawn } = require('child_process')
|
|
119
|
+
const { getExpandedPath } = require('../lib/detect-tools')
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const child = spawn('claude', ['login'], {
|
|
123
|
+
env: { ...process.env, PATH: getExpandedPath() },
|
|
124
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
125
|
+
timeout: 60000,
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
// Claude login opens a browser — just let it run
|
|
129
|
+
child.on('error', () => {})
|
|
130
|
+
child.unref()
|
|
131
|
+
|
|
132
|
+
res.json({ launched: true })
|
|
133
|
+
} catch {
|
|
134
|
+
res.status(500).json({ error: 'Failed to launch claude login' })
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
|
|
102
138
|
module.exports = router
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
const express = require('express')
|
|
2
|
+
const http = require('http')
|
|
3
|
+
|
|
4
|
+
const router = express.Router()
|
|
5
|
+
|
|
6
|
+
// Script injected into the user's dev server HTML to capture console errors
|
|
7
|
+
const ERROR_CATCHER_SCRIPT = `
|
|
8
|
+
<script data-claude-portal-injected>
|
|
9
|
+
(function() {
|
|
10
|
+
var endpoint = window.location.protocol + '//' + window.location.hostname + ':3456/api/console-errors';
|
|
11
|
+
|
|
12
|
+
function send(payload) {
|
|
13
|
+
try {
|
|
14
|
+
navigator.sendBeacon(endpoint, JSON.stringify(payload));
|
|
15
|
+
} catch(e) {}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Capture console.error
|
|
19
|
+
var origError = console.error;
|
|
20
|
+
console.error = function() {
|
|
21
|
+
var args = Array.from(arguments).map(function(a) {
|
|
22
|
+
try { return typeof a === 'object' ? JSON.stringify(a) : String(a); }
|
|
23
|
+
catch(e) { return String(a); }
|
|
24
|
+
});
|
|
25
|
+
send({ type: 'console.error', message: args.join(' ') });
|
|
26
|
+
origError.apply(console, arguments);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Capture unhandled errors
|
|
30
|
+
window.addEventListener('error', function(e) {
|
|
31
|
+
send({
|
|
32
|
+
type: 'error',
|
|
33
|
+
message: e.message,
|
|
34
|
+
source: e.filename,
|
|
35
|
+
line: e.lineno,
|
|
36
|
+
col: e.colno,
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Capture unhandled promise rejections
|
|
41
|
+
window.addEventListener('unhandledrejection', function(e) {
|
|
42
|
+
send({
|
|
43
|
+
type: 'unhandledrejection',
|
|
44
|
+
message: e.reason ? (e.reason.message || String(e.reason)) : 'Unknown rejection',
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
})();
|
|
48
|
+
</script>
|
|
49
|
+
`
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Creates the preview proxy routes.
|
|
53
|
+
* @param {Function} getActivePty - returns the active PTY process (or null)
|
|
54
|
+
*/
|
|
55
|
+
function createPreviewProxy(getActivePty) {
|
|
56
|
+
// Receive console errors from the injected script and write to terminal
|
|
57
|
+
router.post('/console-errors', express.json(), (req, res) => {
|
|
58
|
+
const { type, message, source, line } = req.body || {}
|
|
59
|
+
if (!message) return res.status(400).end()
|
|
60
|
+
|
|
61
|
+
const pty = getActivePty()
|
|
62
|
+
const location = source ? ` (${source}${line ? ':' + line : ''})` : ''
|
|
63
|
+
const formatted = `\r\n⚠️ Browser ${type}: ${message}${location}\r\n`
|
|
64
|
+
|
|
65
|
+
// Write to terminal so Claude Code can see it
|
|
66
|
+
if (pty) {
|
|
67
|
+
try { pty.write(formatted) } catch {}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
res.status(204).end()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
// Proxy dev server and inject error catcher into HTML responses
|
|
74
|
+
router.get('/dev-preview/*', (req, res) => {
|
|
75
|
+
const devPort = req.query.port || '5173'
|
|
76
|
+
const urlPath = req.params[0] || ''
|
|
77
|
+
|
|
78
|
+
const proxyReq = http.request(
|
|
79
|
+
{
|
|
80
|
+
hostname: 'localhost',
|
|
81
|
+
port: parseInt(devPort),
|
|
82
|
+
path: '/' + urlPath,
|
|
83
|
+
method: 'GET',
|
|
84
|
+
headers: {
|
|
85
|
+
...req.headers,
|
|
86
|
+
host: `localhost:${devPort}`,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
(proxyRes) => {
|
|
90
|
+
const contentType = proxyRes.headers['content-type'] || ''
|
|
91
|
+
const isHtml = contentType.includes('text/html')
|
|
92
|
+
|
|
93
|
+
// Forward non-HTML responses as-is
|
|
94
|
+
if (!isHtml) {
|
|
95
|
+
res.writeHead(proxyRes.statusCode, proxyRes.headers)
|
|
96
|
+
proxyRes.pipe(res)
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Buffer HTML response to inject our script
|
|
101
|
+
const chunks = []
|
|
102
|
+
proxyRes.on('data', (chunk) => chunks.push(chunk))
|
|
103
|
+
proxyRes.on('end', () => {
|
|
104
|
+
let html = Buffer.concat(chunks).toString()
|
|
105
|
+
|
|
106
|
+
// Inject before </head> or at start of <body>
|
|
107
|
+
if (html.includes('</head>')) {
|
|
108
|
+
html = html.replace('</head>', ERROR_CATCHER_SCRIPT + '</head>')
|
|
109
|
+
} else if (html.includes('<body')) {
|
|
110
|
+
html = html.replace(/<body([^>]*)>/, `<body$1>${ERROR_CATCHER_SCRIPT}`)
|
|
111
|
+
} else {
|
|
112
|
+
html = ERROR_CATCHER_SCRIPT + html
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Update content-length
|
|
116
|
+
const headers = { ...proxyRes.headers }
|
|
117
|
+
delete headers['content-length']
|
|
118
|
+
headers['transfer-encoding'] = 'chunked'
|
|
119
|
+
|
|
120
|
+
res.writeHead(proxyRes.statusCode, headers)
|
|
121
|
+
res.end(html)
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
proxyReq.on('error', () => {
|
|
127
|
+
res.status(502).json({ error: 'Dev server not running' })
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
proxyReq.end()
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
return router
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
module.exports = { createPreviewProxy }
|
package/tsconfig.tsbuildinfo
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/app.tsx","./src/main.tsx","./src/components/chat/buildsidebar.tsx","./src/components/checklist/featurebuilder.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/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/chat/buildsidebar.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"}
|