@shaykec/bridge 0.1.0 → 0.3.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/README.md +6 -0
- package/package.json +1 -1
- package/src/server.js +55 -1
- package/templates/welcome.html +208 -0
package/README.md
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
# @shaykec/bridge
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@shaykec/bridge)
|
|
4
|
+
|
|
3
5
|
Communication hub for ClaudeTeach -- HTTP server with WebSocket, SSE, and REST endpoints. Routes visual commands from the Claude Code plugin to browser clients (canvas app and Chrome extension) and user events back.
|
|
4
6
|
|
|
7
|
+
```bash
|
|
8
|
+
npm install @shaykec/bridge
|
|
9
|
+
```
|
|
10
|
+
|
|
5
11
|
## Architecture
|
|
6
12
|
|
|
7
13
|
```
|
package/package.json
CHANGED
package/src/server.js
CHANGED
|
@@ -22,8 +22,9 @@
|
|
|
22
22
|
|
|
23
23
|
import { createServer } from 'http';
|
|
24
24
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync } from 'fs';
|
|
25
|
-
import { join, dirname, extname } from 'path';
|
|
25
|
+
import { join, dirname, extname, resolve } from 'path';
|
|
26
26
|
import { fileURLToPath } from 'url';
|
|
27
|
+
import { exec } from 'child_process';
|
|
27
28
|
import { WebSocketServer } from 'ws';
|
|
28
29
|
|
|
29
30
|
import {
|
|
@@ -253,6 +254,11 @@ function handleRequest(req, res, tierManager, router, options) {
|
|
|
253
254
|
return;
|
|
254
255
|
}
|
|
255
256
|
|
|
257
|
+
if (req.method === 'POST' && pathname === '/api/open-extension') {
|
|
258
|
+
handleApiOpenExtension(res);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
256
262
|
// --- Health check ---
|
|
257
263
|
if (req.method === 'GET' && pathname === '/health') {
|
|
258
264
|
sendJson(res, 200, {
|
|
@@ -269,6 +275,11 @@ function handleRequest(req, res, tierManager, router, options) {
|
|
|
269
275
|
if (serveStatic(res, pathname)) return;
|
|
270
276
|
}
|
|
271
277
|
|
|
278
|
+
// --- Welcome page fallback (when canvas dist is not built) ---
|
|
279
|
+
if (req.method === 'GET' && (pathname === '/' || url.searchParams.get('mode') === 'welcome')) {
|
|
280
|
+
if (serveWelcomeFallback(res)) return;
|
|
281
|
+
}
|
|
282
|
+
|
|
272
283
|
// 404
|
|
273
284
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
274
285
|
res.end(JSON.stringify({ error: 'Not found' }));
|
|
@@ -458,6 +469,49 @@ function serveStatic(res, pathname) {
|
|
|
458
469
|
}
|
|
459
470
|
}
|
|
460
471
|
|
|
472
|
+
/**
|
|
473
|
+
* Serve the standalone welcome page when the canvas dist is not available.
|
|
474
|
+
* This provides a working welcome page for npx users who don't have the canvas built.
|
|
475
|
+
*/
|
|
476
|
+
function serveWelcomeFallback(res) {
|
|
477
|
+
const welcomePath = join(__dirname, '..', 'templates', 'welcome.html');
|
|
478
|
+
if (!existsSync(welcomePath)) return false;
|
|
479
|
+
try {
|
|
480
|
+
const content = readFileSync(welcomePath, 'utf-8');
|
|
481
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
482
|
+
res.end(content);
|
|
483
|
+
return true;
|
|
484
|
+
} catch {
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* POST /api/open-extension — open extension folder + chrome://extensions in the OS.
|
|
491
|
+
* Triggered by the "Install Extension" button on the welcome page.
|
|
492
|
+
*/
|
|
493
|
+
function handleApiOpenExtension(res) {
|
|
494
|
+
const extensionDir = resolve(__dirname, '..', '..', 'extension');
|
|
495
|
+
if (!existsSync(join(extensionDir, 'manifest.json'))) {
|
|
496
|
+
sendJson(res, 404, { error: 'Extension directory not found' });
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const platform = process.platform;
|
|
501
|
+
if (platform === 'darwin') {
|
|
502
|
+
exec(`open "${extensionDir}"`);
|
|
503
|
+
exec('open -a "Google Chrome" "chrome://extensions"');
|
|
504
|
+
} else if (platform === 'linux') {
|
|
505
|
+
exec(`xdg-open "${extensionDir}"`);
|
|
506
|
+
exec('google-chrome "chrome://extensions" 2>/dev/null || chromium-browser "chrome://extensions" 2>/dev/null');
|
|
507
|
+
} else if (platform === 'win32') {
|
|
508
|
+
exec(`explorer "${extensionDir}"`);
|
|
509
|
+
exec('start chrome "chrome://extensions"');
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
sendJson(res, 200, { ok: true, path: extensionDir });
|
|
513
|
+
}
|
|
514
|
+
|
|
461
515
|
// --- Helpers ---
|
|
462
516
|
|
|
463
517
|
function readBody(req, callback) {
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>ClaudeTeach — Welcome</title>
|
|
7
|
+
<style>
|
|
8
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
9
|
+
body {
|
|
10
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
|
11
|
+
background: #0d1117; color: #c9d1d9; line-height: 1.6;
|
|
12
|
+
}
|
|
13
|
+
.container { max-width: 720px; margin: 0 auto; padding: 20px 16px 60px; }
|
|
14
|
+
.hero { text-align: center; margin-bottom: 40px; padding: 32px 0; }
|
|
15
|
+
.hero h1 { font-size: 32px; font-weight: 700; color: #58a6ff; letter-spacing: -0.02em; }
|
|
16
|
+
.hero p { font-size: 16px; color: #8b949e; margin-top: 12px; }
|
|
17
|
+
.section {
|
|
18
|
+
margin-bottom: 32px; background: #161b22; border-radius: 12px;
|
|
19
|
+
border: 1px solid #30363d; overflow: hidden;
|
|
20
|
+
}
|
|
21
|
+
.section-header {
|
|
22
|
+
display: flex; align-items: center; gap: 12px;
|
|
23
|
+
padding: 16px 20px; border-bottom: 1px solid #30363d;
|
|
24
|
+
}
|
|
25
|
+
.section-icon {
|
|
26
|
+
width: 28px; height: 28px; border-radius: 50%; background: #58a6ff;
|
|
27
|
+
color: #0d1117; display: flex; align-items: center; justify-content: center;
|
|
28
|
+
font-size: 14px; font-weight: 700; flex-shrink: 0;
|
|
29
|
+
}
|
|
30
|
+
.section-title { font-size: 18px; font-weight: 600; color: #c9d1d9; }
|
|
31
|
+
.section-body { padding: 16px 20px; }
|
|
32
|
+
.text { font-size: 14px; color: #c9d1d9; margin: 0 0 12px; }
|
|
33
|
+
.text-small { font-size: 13px; color: #8b949e; margin: 8px 0 0; }
|
|
34
|
+
pre {
|
|
35
|
+
background: #0d1117; border: 1px solid #30363d; border-radius: 8px;
|
|
36
|
+
padding: 10px 14px; font-size: 13px;
|
|
37
|
+
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
|
|
38
|
+
color: #3fb950; margin: 8px 0 12px; overflow: auto;
|
|
39
|
+
}
|
|
40
|
+
.skill-grid {
|
|
41
|
+
display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
42
|
+
gap: 8px; margin-top: 8px;
|
|
43
|
+
}
|
|
44
|
+
.skill-card {
|
|
45
|
+
display: flex; flex-direction: column; gap: 4px; padding: 10px 12px;
|
|
46
|
+
background: #0d1117; border-radius: 8px; border: 1px solid #30363d;
|
|
47
|
+
}
|
|
48
|
+
.skill-name {
|
|
49
|
+
font-size: 13px; font-weight: 600; color: #58a6ff;
|
|
50
|
+
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
|
|
51
|
+
}
|
|
52
|
+
.skill-desc { font-size: 12px; color: #8b949e; line-height: 1.4; }
|
|
53
|
+
.install-btn {
|
|
54
|
+
padding: 10px 24px; font-size: 14px; font-weight: 600; color: #fff;
|
|
55
|
+
background: #238636; border: 1px solid #2ea043; border-radius: 8px;
|
|
56
|
+
cursor: pointer; transition: background 0.15s ease; margin-top: 4px;
|
|
57
|
+
}
|
|
58
|
+
.install-btn:hover { background: #2ea043; }
|
|
59
|
+
.install-btn:disabled { opacity: 0.7; cursor: default; }
|
|
60
|
+
.install-result {
|
|
61
|
+
margin-top: 16px; padding: 12px 16px; background: #0d1117;
|
|
62
|
+
border-radius: 8px; border: 1px solid #30363d;
|
|
63
|
+
}
|
|
64
|
+
.install-result .title { font-size: 14px; font-weight: 600; color: #3fb950; margin: 0 0 8px; }
|
|
65
|
+
.install-result ol { margin: 0; padding-left: 20px; font-size: 13px; color: #c9d1d9; line-height: 1.8; }
|
|
66
|
+
.install-result .path { font-size: 13px; color: #8b949e; margin-top: 8px; }
|
|
67
|
+
.error { font-size: 13px; color: #f85149; margin-top: 8px; }
|
|
68
|
+
.feature-list { margin: 8px 0 0; padding-left: 20px; font-size: 14px; color: #c9d1d9; line-height: 1.8; }
|
|
69
|
+
.quick-steps { display: flex; flex-direction: column; gap: 16px; }
|
|
70
|
+
.step { display: flex; gap: 12px; align-items: flex-start; }
|
|
71
|
+
.step-num {
|
|
72
|
+
width: 24px; height: 24px; border-radius: 50%; background: #30363d;
|
|
73
|
+
color: #58a6ff; display: flex; align-items: center; justify-content: center;
|
|
74
|
+
font-size: 13px; font-weight: 700; flex-shrink: 0; margin-top: 2px;
|
|
75
|
+
}
|
|
76
|
+
</style>
|
|
77
|
+
</head>
|
|
78
|
+
<body>
|
|
79
|
+
<div class="container">
|
|
80
|
+
<!-- Hero -->
|
|
81
|
+
<div class="hero">
|
|
82
|
+
<h1>Welcome to ClaudeTeach</h1>
|
|
83
|
+
<p>A Socratic AI teaching platform with gamification and visual learning.<br>
|
|
84
|
+
Learn through guided dialogue — the AI asks YOU questions.</p>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<!-- Getting Started -->
|
|
88
|
+
<div class="section">
|
|
89
|
+
<div class="section-header">
|
|
90
|
+
<span class="section-icon">1</span>
|
|
91
|
+
<h2 class="section-title">Getting Started</h2>
|
|
92
|
+
</div>
|
|
93
|
+
<div class="section-body">
|
|
94
|
+
<p class="text">Launch Claude Code with teaching skills in one command:</p>
|
|
95
|
+
<pre><code>claude-teach start</code></pre>
|
|
96
|
+
<p class="text-small">This starts the bridge server (for visuals) and launches Claude Code with all
|
|
97
|
+
teaching skills loaded. You can also pass flags through:</p>
|
|
98
|
+
<pre><code>claude-teach start -p "teach me git"</code></pre>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<!-- Teaching Skills -->
|
|
103
|
+
<div class="section">
|
|
104
|
+
<div class="section-header">
|
|
105
|
+
<span class="section-icon">2</span>
|
|
106
|
+
<h2 class="section-title">Teaching Skills</h2>
|
|
107
|
+
</div>
|
|
108
|
+
<div class="section-body">
|
|
109
|
+
<p class="text">Once Claude Code is running with ClaudeTeach, these skills are available:</p>
|
|
110
|
+
<div class="skill-grid">
|
|
111
|
+
<div class="skill-card"><span class="skill-name">/teach</span><span class="skill-desc">Socratic teaching — learn any topic through guided dialogue</span></div>
|
|
112
|
+
<div class="skill-card"><span class="skill-name">/teach:ask</span><span class="skill-desc">Ask questions about any topic covered by learning modules</span></div>
|
|
113
|
+
<div class="skill-card"><span class="skill-name">/teach:stats</span><span class="skill-desc">View your XP, belt rank, and module progress</span></div>
|
|
114
|
+
<div class="skill-card"><span class="skill-name">/teach:level-up</span><span class="skill-desc">See your belt roadmap and what to learn next</span></div>
|
|
115
|
+
<div class="skill-card"><span class="skill-name">/teach:canvas</span><span class="skill-desc">Open the visual canvas — diagrams, quizzes, dashboards</span></div>
|
|
116
|
+
<div class="skill-card"><span class="skill-name">/teach:author</span><span class="skill-desc">Create a learning module with guided scaffolding</span></div>
|
|
117
|
+
<div class="skill-card"><span class="skill-name">/teach:author-pack</span><span class="skill-desc">Create a complete module pack with multiple modules</span></div>
|
|
118
|
+
<div class="skill-card"><span class="skill-name">/teach:dev</span><span class="skill-desc">Developer workflow — run tests, validate, build</span></div>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<!-- Chrome Extension -->
|
|
124
|
+
<div class="section">
|
|
125
|
+
<div class="section-header">
|
|
126
|
+
<span class="section-icon">3</span>
|
|
127
|
+
<h2 class="section-title">Chrome Extension</h2>
|
|
128
|
+
</div>
|
|
129
|
+
<div class="section-body">
|
|
130
|
+
<p class="text">The ClaudeTeach extension adds context-aware learning suggestions, "Teach me this"
|
|
131
|
+
right-click menus, and real-time connection to the visual canvas.</p>
|
|
132
|
+
<button class="install-btn" id="installBtn" onclick="installExtension()">Install Extension</button>
|
|
133
|
+
<div id="installResult"></div>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<!-- Visual Canvas -->
|
|
138
|
+
<div class="section">
|
|
139
|
+
<div class="section-header">
|
|
140
|
+
<span class="section-icon">4</span>
|
|
141
|
+
<h2 class="section-title">Visual Canvas</h2>
|
|
142
|
+
</div>
|
|
143
|
+
<div class="section-body">
|
|
144
|
+
<p class="text">The bridge server powers browser-based visuals during lessons:</p>
|
|
145
|
+
<ul class="feature-list">
|
|
146
|
+
<li>Mermaid diagrams and flowcharts</li>
|
|
147
|
+
<li>Interactive drag-and-drop quizzes</li>
|
|
148
|
+
<li>Timed multiple-choice challenges</li>
|
|
149
|
+
<li>Live code playgrounds</li>
|
|
150
|
+
<li>XP dashboard with belt progression</li>
|
|
151
|
+
</ul>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<!-- Quick Start -->
|
|
156
|
+
<div class="section">
|
|
157
|
+
<div class="section-header">
|
|
158
|
+
<span class="section-icon">5</span>
|
|
159
|
+
<h2 class="section-title">Quick Start</h2>
|
|
160
|
+
</div>
|
|
161
|
+
<div class="section-body">
|
|
162
|
+
<div class="quick-steps">
|
|
163
|
+
<div class="step">
|
|
164
|
+
<span class="step-num">1</span>
|
|
165
|
+
<div><span class="text">Run:</span><pre><code>claude-teach start</code></pre></div>
|
|
166
|
+
</div>
|
|
167
|
+
<div class="step">
|
|
168
|
+
<span class="step-num">2</span>
|
|
169
|
+
<div><span class="text">In Claude Code, type:</span><pre><code>/teach git</code></pre></div>
|
|
170
|
+
</div>
|
|
171
|
+
<div class="step">
|
|
172
|
+
<span class="step-num">3</span>
|
|
173
|
+
<div><span class="text">Follow the Socratic dialogue — you write the code, the AI guides you.</span></div>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
<script>
|
|
181
|
+
async function installExtension() {
|
|
182
|
+
const btn = document.getElementById('installBtn');
|
|
183
|
+
const result = document.getElementById('installResult');
|
|
184
|
+
btn.disabled = true;
|
|
185
|
+
btn.textContent = 'Opening...';
|
|
186
|
+
result.innerHTML = '';
|
|
187
|
+
try {
|
|
188
|
+
const res = await fetch('/api/open-extension', { method: 'POST' });
|
|
189
|
+
const data = await res.json();
|
|
190
|
+
if (data.ok) {
|
|
191
|
+
result.innerHTML = '<div class="install-result">' +
|
|
192
|
+
'<p class="title">Extension folder opened. Now:</p>' +
|
|
193
|
+
'<ol><li>Enable <strong>Developer mode</strong> (toggle, top-right of Chrome Extensions)</li>' +
|
|
194
|
+
'<li>Click <strong>Load unpacked</strong></li>' +
|
|
195
|
+
'<li>Select the folder that just opened</li></ol>' +
|
|
196
|
+
'<p class="path">Path: ' + data.path + '</p></div>';
|
|
197
|
+
} else {
|
|
198
|
+
result.innerHTML = '<p class="error">' + (data.error || 'Unknown error') + '</p>';
|
|
199
|
+
}
|
|
200
|
+
} catch (err) {
|
|
201
|
+
result.innerHTML = '<p class="error">' + err.message + '</p>';
|
|
202
|
+
}
|
|
203
|
+
btn.disabled = false;
|
|
204
|
+
btn.textContent = 'Install Extension';
|
|
205
|
+
}
|
|
206
|
+
</script>
|
|
207
|
+
</body>
|
|
208
|
+
</html>
|