@wipcomputer/wip-ldm-os 0.4.73-alpha.9 → 0.4.74
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/LICENSE +52 -0
- package/SKILL.md +8 -1
- package/bin/ldm.js +587 -82
- package/dist/bridge/chunk-3RG5ZIWI.js +10 -0
- package/dist/bridge/{chunk-LF7EMFBY.js → chunk-7NH6JBIO.js} +127 -49
- package/dist/bridge/cli.js +2 -1
- package/dist/bridge/core.d.ts +13 -1
- package/dist/bridge/core.js +4 -1
- package/dist/bridge/mcp-server.js +52 -7
- package/dist/bridge/openclaw.d.ts +5 -0
- package/dist/bridge/openclaw.js +11 -0
- package/docs/bridge/TECHNICAL.md +86 -0
- package/docs/doc-pipeline/README.md +74 -0
- package/docs/doc-pipeline/TECHNICAL.md +79 -0
- package/lib/deploy.mjs +175 -13
- package/lib/detect.mjs +20 -6
- package/package.json +2 -2
- package/shared/docs/README.md.tmpl +2 -2
- package/shared/docs/how-releases-work.md.tmpl +3 -1
- package/shared/docs/how-worktrees-work.md.tmpl +12 -7
- package/shared/rules/git-conventions.md +3 -3
- package/shared/rules/release-pipeline.md +1 -1
- package/shared/rules/security.md +1 -1
- package/shared/rules/workspace-boundaries.md +1 -1
- package/shared/rules/writing-style.md +1 -1
- package/shared/templates/claude-md-level1.md +7 -3
- package/src/bridge/core.ts +160 -56
- package/src/bridge/mcp-server.ts +93 -8
- package/src/bridge/openclaw.ts +14 -0
- package/src/hooks/inbox-check-hook.mjs +232 -0
- package/src/hooks/inbox-rewake-hook.mjs +388 -0
- package/src/hosted-mcp/.env.example +3 -0
- package/src/hosted-mcp/demo/agent.html +300 -0
- package/src/hosted-mcp/demo/agent.txt +84 -0
- package/src/hosted-mcp/demo/fallback.jpg +0 -0
- package/src/hosted-mcp/demo/footer.js +74 -0
- package/src/hosted-mcp/demo/index.html +1303 -0
- package/src/hosted-mcp/demo/login.html +548 -0
- package/src/hosted-mcp/demo/privacy.html +223 -0
- package/src/hosted-mcp/demo/sprites.jpg +0 -0
- package/src/hosted-mcp/demo/sprites.png +0 -0
- package/src/hosted-mcp/demo/tos.html +198 -0
- package/src/hosted-mcp/deploy.sh +70 -0
- package/src/hosted-mcp/ecosystem.config.cjs +14 -0
- package/src/hosted-mcp/inbox.mjs +64 -0
- package/src/hosted-mcp/legal/internet-services/terms/site.html +205 -0
- package/src/hosted-mcp/legal/privacy/en-ww/index.html +230 -0
- package/src/hosted-mcp/nginx/mcp-oauth.conf +98 -0
- package/src/hosted-mcp/nginx/mcp-server.conf +17 -0
- package/src/hosted-mcp/nginx/wip.computer.conf +45 -0
- package/src/hosted-mcp/package-lock.json +2092 -0
- package/src/hosted-mcp/package.json +23 -0
- package/src/hosted-mcp/prisma/migrations/20260406233014_init/migration.sql +68 -0
- package/src/hosted-mcp/prisma/migrations/migration_lock.toml +3 -0
- package/src/hosted-mcp/prisma/schema.prisma +57 -0
- package/src/hosted-mcp/prisma.config.ts +14 -0
- package/src/hosted-mcp/server.mjs +2093 -0
- package/src/hosted-mcp/shared/kaleidoscope.css +139 -0
- package/src/hosted-mcp/shared/kaleidoscope.js +192 -0
- package/src/hosted-mcp/tools.mjs +73 -0
- package/templates/hooks/pre-commit +5 -0
|
@@ -0,0 +1,223 @@
|
|
|
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, viewport-fit=cover">
|
|
6
|
+
<title>Privacy Policy - Kaleidoscope Demo</title>
|
|
7
|
+
<style>
|
|
8
|
+
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
|
9
|
+
|
|
10
|
+
body {
|
|
11
|
+
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, sans-serif;
|
|
12
|
+
background: #FFFDF5;
|
|
13
|
+
color: #1a1a1a;
|
|
14
|
+
-webkit-font-smoothing: antialiased;
|
|
15
|
+
-webkit-text-size-adjust: 100%;
|
|
16
|
+
line-height: 1.6;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.container {
|
|
20
|
+
max-width: 980px;
|
|
21
|
+
margin: 0 auto;
|
|
22
|
+
padding: 16px 24px 80px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.header {
|
|
26
|
+
position: sticky;
|
|
27
|
+
top: 0;
|
|
28
|
+
z-index: 100;
|
|
29
|
+
padding: calc(12px + env(safe-area-inset-top, 0px)) 20px 12px;
|
|
30
|
+
background: rgba(255, 253, 245, 0.8);
|
|
31
|
+
-webkit-backdrop-filter: saturate(180%) blur(20px);
|
|
32
|
+
backdrop-filter: saturate(180%) blur(20px);
|
|
33
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
|
34
|
+
display: flex;
|
|
35
|
+
align-items: center;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.header a {
|
|
39
|
+
display: flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
text-decoration: none;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
h1 {
|
|
45
|
+
font-size: 28px;
|
|
46
|
+
font-weight: 700;
|
|
47
|
+
letter-spacing: -0.02em;
|
|
48
|
+
margin-bottom: 4px;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.subtitle {
|
|
52
|
+
font-size: 17px;
|
|
53
|
+
color: #8a8580;
|
|
54
|
+
margin-bottom: 8px;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.updated {
|
|
58
|
+
font-size: 13px;
|
|
59
|
+
color: #b0aaa4;
|
|
60
|
+
margin-bottom: 40px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
section {
|
|
64
|
+
padding: 28px 0;
|
|
65
|
+
border-top: 1px solid #e8e5de;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
section:last-of-type {
|
|
69
|
+
border-bottom: 1px solid #e8e5de;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
h2 {
|
|
73
|
+
font-size: 16px;
|
|
74
|
+
font-weight: 600;
|
|
75
|
+
letter-spacing: -0.01em;
|
|
76
|
+
margin-bottom: 12px;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
p {
|
|
80
|
+
font-size: 15px;
|
|
81
|
+
line-height: 1.65;
|
|
82
|
+
color: #3a3a3a;
|
|
83
|
+
margin-bottom: 12px;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
p:last-child {
|
|
87
|
+
margin-bottom: 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
ul {
|
|
91
|
+
list-style: none;
|
|
92
|
+
padding: 0;
|
|
93
|
+
margin-bottom: 12px;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
ul li {
|
|
97
|
+
font-size: 15px;
|
|
98
|
+
line-height: 1.65;
|
|
99
|
+
color: #3a3a3a;
|
|
100
|
+
padding-left: 16px;
|
|
101
|
+
position: relative;
|
|
102
|
+
margin-bottom: 4px;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
ul li::before {
|
|
106
|
+
content: "\2022";
|
|
107
|
+
position: absolute;
|
|
108
|
+
left: 0;
|
|
109
|
+
color: #b0aaa4;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
a {
|
|
113
|
+
color: #1a1a1a;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
footer {
|
|
117
|
+
margin-top: 48px;
|
|
118
|
+
padding-bottom: 160px;
|
|
119
|
+
}
|
|
120
|
+
</style>
|
|
121
|
+
</head>
|
|
122
|
+
<body>
|
|
123
|
+
<div class="header">
|
|
124
|
+
<a id="navIcon" href="/demo/"></a>
|
|
125
|
+
</div>
|
|
126
|
+
<div class="container">
|
|
127
|
+
|
|
128
|
+
<h1>Privacy Policy</h1>
|
|
129
|
+
<p class="subtitle">Kaleidoscope Demo</p>
|
|
130
|
+
<p class="updated">Last updated: April 2, 2026</p>
|
|
131
|
+
|
|
132
|
+
<section>
|
|
133
|
+
<p>WIP Computer, Inc. ("we", "us") operates the Kaleidoscope demo at wip.computer/demo.</p>
|
|
134
|
+
</section>
|
|
135
|
+
|
|
136
|
+
<section>
|
|
137
|
+
<h2>What We Collect</h2>
|
|
138
|
+
<ul>
|
|
139
|
+
<li>Passkey credential ID and public key (for authentication)</li>
|
|
140
|
+
<li>Username (optional, if you provide one)</li>
|
|
141
|
+
<li>Wallet balance (simulated, for demo purposes)</li>
|
|
142
|
+
</ul>
|
|
143
|
+
</section>
|
|
144
|
+
|
|
145
|
+
<section>
|
|
146
|
+
<h2>What We Do NOT Collect</h2>
|
|
147
|
+
<ul>
|
|
148
|
+
<li>Email addresses (unless voluntarily provided later)</li>
|
|
149
|
+
<li>Passwords (passkeys only, no passwords exist)</li>
|
|
150
|
+
<li>Biometric data (Face ID / fingerprint verification happens on your device, we never receive biometric data)</li>
|
|
151
|
+
<li>Photos (camera captures are processed in your browser, never uploaded)</li>
|
|
152
|
+
<li>Browsing history or tracking data</li>
|
|
153
|
+
<li>Cookies (we use sessionStorage only, cleared when you close the browser)</li>
|
|
154
|
+
</ul>
|
|
155
|
+
</section>
|
|
156
|
+
|
|
157
|
+
<section>
|
|
158
|
+
<h2>How We Use Your Data</h2>
|
|
159
|
+
<ul>
|
|
160
|
+
<li>Authentication: verifying your passkey when you sign in</li>
|
|
161
|
+
<li>Demo functionality: tracking simulated wallet balance</li>
|
|
162
|
+
<li>Product improvement: aggregate usage patterns (no personal data)</li>
|
|
163
|
+
</ul>
|
|
164
|
+
</section>
|
|
165
|
+
|
|
166
|
+
<section>
|
|
167
|
+
<h2>Third-Party Services</h2>
|
|
168
|
+
<ul>
|
|
169
|
+
<li>xAI (Grok Imagine API): receives text prompts for image generation. Does not receive your photos or personal data.</li>
|
|
170
|
+
<li>No analytics services</li>
|
|
171
|
+
<li>No advertising networks</li>
|
|
172
|
+
<li>No data brokers</li>
|
|
173
|
+
</ul>
|
|
174
|
+
</section>
|
|
175
|
+
|
|
176
|
+
<section>
|
|
177
|
+
<h2>Data Storage</h2>
|
|
178
|
+
<p>Your passkey data is stored on our server at wip.computer. It is not encrypted at rest in this demo version. Production versions will implement end-to-end encryption where we architecturally cannot read your data.</p>
|
|
179
|
+
</section>
|
|
180
|
+
|
|
181
|
+
<section>
|
|
182
|
+
<h2>Data Deletion</h2>
|
|
183
|
+
<p>Contact <a href="mailto:hello@wip.computer">hello@wip.computer</a> to request deletion of your passkey data.</p>
|
|
184
|
+
</section>
|
|
185
|
+
|
|
186
|
+
<section>
|
|
187
|
+
<h2>Your Rights</h2>
|
|
188
|
+
<p>You can delete your passkey from your device at any time through your device settings (Settings > Passwords on iOS, chrome://settings/passwords on Chrome).</p>
|
|
189
|
+
</section>
|
|
190
|
+
|
|
191
|
+
<section>
|
|
192
|
+
<h2>Changes</h2>
|
|
193
|
+
<p>We may update this policy. Changes will be reflected in the "Last updated" date.</p>
|
|
194
|
+
</section>
|
|
195
|
+
|
|
196
|
+
<section>
|
|
197
|
+
<h2>Contact</h2>
|
|
198
|
+
<p><a href="mailto:hello@wip.computer">hello@wip.computer</a></p>
|
|
199
|
+
</section>
|
|
200
|
+
|
|
201
|
+
<footer>
|
|
202
|
+
<div id="kscope-footer"></div>
|
|
203
|
+
</footer>
|
|
204
|
+
<script src="/demo/footer.js"></script>
|
|
205
|
+
</div>
|
|
206
|
+
<script>
|
|
207
|
+
var SPRITE_COLS = 8, SPRITE_ROWS = 3, SPRITE_TOTAL = 24;
|
|
208
|
+
var navIdx = Math.floor(Math.random() * SPRITE_TOTAL);
|
|
209
|
+
function updateNavIcon() {
|
|
210
|
+
var el = document.getElementById('navIcon');
|
|
211
|
+
if (!el) return;
|
|
212
|
+
var col = navIdx % SPRITE_COLS;
|
|
213
|
+
var row = Math.floor(navIdx / SPRITE_COLS);
|
|
214
|
+
var bgPosX = (col / (SPRITE_COLS - 1)) * 100;
|
|
215
|
+
var bgPosY = (row / (SPRITE_ROWS - 1)) * 100;
|
|
216
|
+
el.innerHTML = '<div style="width:28px;height:28px;overflow:hidden;"><div style="width:100%;height:100%;background:url(/demo/sprites.png);background-size:' + (SPRITE_COLS * 100) + '% ' + (SPRITE_ROWS * 100) + '%;background-position:' + bgPosX + '% ' + bgPosY + '%;"></div></div>';
|
|
217
|
+
navIdx = (navIdx + 1) % SPRITE_TOTAL;
|
|
218
|
+
}
|
|
219
|
+
updateNavIcon();
|
|
220
|
+
setInterval(updateNavIcon, 6000);
|
|
221
|
+
</script>
|
|
222
|
+
</body>
|
|
223
|
+
</html>
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,198 @@
|
|
|
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, viewport-fit=cover">
|
|
6
|
+
<title>Terms of Service - Kaleidoscope Demo</title>
|
|
7
|
+
<style>
|
|
8
|
+
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
|
9
|
+
|
|
10
|
+
body {
|
|
11
|
+
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, sans-serif;
|
|
12
|
+
background: #FFFDF5;
|
|
13
|
+
color: #1a1a1a;
|
|
14
|
+
-webkit-font-smoothing: antialiased;
|
|
15
|
+
-webkit-text-size-adjust: 100%;
|
|
16
|
+
line-height: 1.6;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.container {
|
|
20
|
+
max-width: 980px;
|
|
21
|
+
margin: 0 auto;
|
|
22
|
+
padding: 16px 24px 80px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.header {
|
|
26
|
+
position: sticky;
|
|
27
|
+
top: 0;
|
|
28
|
+
z-index: 100;
|
|
29
|
+
padding: calc(12px + env(safe-area-inset-top, 0px)) 20px 12px;
|
|
30
|
+
background: rgba(255, 253, 245, 0.8);
|
|
31
|
+
-webkit-backdrop-filter: saturate(180%) blur(20px);
|
|
32
|
+
backdrop-filter: saturate(180%) blur(20px);
|
|
33
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
|
34
|
+
display: flex;
|
|
35
|
+
align-items: center;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.header a {
|
|
39
|
+
display: flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
text-decoration: none;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
h1 {
|
|
45
|
+
font-size: 28px;
|
|
46
|
+
font-weight: 700;
|
|
47
|
+
letter-spacing: -0.02em;
|
|
48
|
+
margin-bottom: 4px;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.subtitle {
|
|
52
|
+
font-size: 17px;
|
|
53
|
+
color: #8a8580;
|
|
54
|
+
margin-bottom: 8px;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.updated {
|
|
58
|
+
font-size: 13px;
|
|
59
|
+
color: #b0aaa4;
|
|
60
|
+
margin-bottom: 40px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
section {
|
|
64
|
+
padding: 28px 0;
|
|
65
|
+
border-top: 1px solid #e8e5de;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
section:last-of-type {
|
|
69
|
+
border-bottom: 1px solid #e8e5de;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
h2 {
|
|
73
|
+
font-size: 16px;
|
|
74
|
+
font-weight: 600;
|
|
75
|
+
letter-spacing: -0.01em;
|
|
76
|
+
margin-bottom: 12px;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
p {
|
|
80
|
+
font-size: 15px;
|
|
81
|
+
line-height: 1.65;
|
|
82
|
+
color: #3a3a3a;
|
|
83
|
+
margin-bottom: 12px;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
p:last-child {
|
|
87
|
+
margin-bottom: 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
ul {
|
|
91
|
+
list-style: none;
|
|
92
|
+
padding: 0;
|
|
93
|
+
margin-bottom: 12px;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
ul li {
|
|
97
|
+
font-size: 15px;
|
|
98
|
+
line-height: 1.65;
|
|
99
|
+
color: #3a3a3a;
|
|
100
|
+
padding-left: 16px;
|
|
101
|
+
position: relative;
|
|
102
|
+
margin-bottom: 4px;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
ul li::before {
|
|
106
|
+
content: "\2022";
|
|
107
|
+
position: absolute;
|
|
108
|
+
left: 0;
|
|
109
|
+
color: #b0aaa4;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
a {
|
|
113
|
+
color: #1a1a1a;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
footer {
|
|
117
|
+
margin-top: 48px;
|
|
118
|
+
padding-bottom: 160px;
|
|
119
|
+
}
|
|
120
|
+
</style>
|
|
121
|
+
</head>
|
|
122
|
+
<body>
|
|
123
|
+
<div class="header">
|
|
124
|
+
<a id="navIcon" href="/demo/"></a>
|
|
125
|
+
</div>
|
|
126
|
+
<div class="container">
|
|
127
|
+
|
|
128
|
+
<h1>Terms of Service</h1>
|
|
129
|
+
<p class="subtitle">Kaleidoscope Demo</p>
|
|
130
|
+
<p class="updated">Last updated: April 2, 2026</p>
|
|
131
|
+
|
|
132
|
+
<section>
|
|
133
|
+
<p>This is a technology demonstration by WIP Computer, Inc.</p>
|
|
134
|
+
</section>
|
|
135
|
+
|
|
136
|
+
<section>
|
|
137
|
+
<h2>The Demo</h2>
|
|
138
|
+
<p>This demo showcases passkey authentication, biometric permission, and AI image generation. It is not a production service.</p>
|
|
139
|
+
</section>
|
|
140
|
+
|
|
141
|
+
<section>
|
|
142
|
+
<h2>Your Account</h2>
|
|
143
|
+
<p>Your account is created with a passkey. No email or password is stored. Your passkey lives on your device.</p>
|
|
144
|
+
</section>
|
|
145
|
+
|
|
146
|
+
<section>
|
|
147
|
+
<h2>Your Wallet</h2>
|
|
148
|
+
<p>The demo wallet starts with $5.00 in simulated credits. No real money is charged. The wallet balance is for demonstration purposes only.</p>
|
|
149
|
+
</section>
|
|
150
|
+
|
|
151
|
+
<section>
|
|
152
|
+
<h2>Generated Content</h2>
|
|
153
|
+
<p>Images generated through this demo are created using third-party AI APIs (xAI Grok Imagine). Generated content is dual-licensed:</p>
|
|
154
|
+
<ul>
|
|
155
|
+
<li>MIT License for personal, non-commercial use</li>
|
|
156
|
+
<li>Apache 2.0 License for commercial use</li>
|
|
157
|
+
</ul>
|
|
158
|
+
<p>Both you and WIP Computer, Inc. retain rights to generated content. We may use generated images for product development, marketing, and research.</p>
|
|
159
|
+
</section>
|
|
160
|
+
|
|
161
|
+
<section>
|
|
162
|
+
<h2>Uploaded Media</h2>
|
|
163
|
+
<p>Photos taken through the camera feature are processed locally in your browser. They are not uploaded to or stored on our servers. Camera access is optional and can be denied.</p>
|
|
164
|
+
</section>
|
|
165
|
+
|
|
166
|
+
<section>
|
|
167
|
+
<h2>No Warranty</h2>
|
|
168
|
+
<p>This demo is provided "as is" without warranty of any kind. WIP Computer, Inc. is not liable for any damages arising from use of this demo.</p>
|
|
169
|
+
</section>
|
|
170
|
+
|
|
171
|
+
<section>
|
|
172
|
+
<h2>Contact</h2>
|
|
173
|
+
<p><a href="mailto:hello@wip.computer">hello@wip.computer</a></p>
|
|
174
|
+
</section>
|
|
175
|
+
|
|
176
|
+
<footer>
|
|
177
|
+
<div id="kscope-footer"></div>
|
|
178
|
+
</footer>
|
|
179
|
+
<script src="/demo/footer.js"></script>
|
|
180
|
+
</div>
|
|
181
|
+
<script>
|
|
182
|
+
var SPRITE_COLS = 8, SPRITE_ROWS = 3, SPRITE_TOTAL = 24;
|
|
183
|
+
var navIdx = Math.floor(Math.random() * SPRITE_TOTAL);
|
|
184
|
+
function updateNavIcon() {
|
|
185
|
+
var el = document.getElementById('navIcon');
|
|
186
|
+
if (!el) return;
|
|
187
|
+
var col = navIdx % SPRITE_COLS;
|
|
188
|
+
var row = Math.floor(navIdx / SPRITE_COLS);
|
|
189
|
+
var bgPosX = (col / (SPRITE_COLS - 1)) * 100;
|
|
190
|
+
var bgPosY = (row / (SPRITE_ROWS - 1)) * 100;
|
|
191
|
+
el.innerHTML = '<div style="width:28px;height:28px;overflow:hidden;"><div style="width:100%;height:100%;background:url(/demo/sprites.png);background-size:' + (SPRITE_COLS * 100) + '% ' + (SPRITE_ROWS * 100) + '%;background-position:' + bgPosX + '% ' + bgPosY + '%;"></div></div>';
|
|
192
|
+
navIdx = (navIdx + 1) % SPRITE_TOTAL;
|
|
193
|
+
}
|
|
194
|
+
updateNavIcon();
|
|
195
|
+
setInterval(updateNavIcon, 6000);
|
|
196
|
+
</script>
|
|
197
|
+
</body>
|
|
198
|
+
</html>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# deploy.sh: Deploy hosted MCP server to wip.computer
|
|
3
|
+
#
|
|
4
|
+
# Prerequisites:
|
|
5
|
+
# - SSH config has Host wip.computer
|
|
6
|
+
# - pm2 installed on the server
|
|
7
|
+
# - nginx configured on the server
|
|
8
|
+
#
|
|
9
|
+
# Usage: bash deploy.sh
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
REMOTE="wip.computer"
|
|
14
|
+
REMOTE_DIR="/var/www/wip.computer/app/mcp-server"
|
|
15
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
16
|
+
|
|
17
|
+
echo "Deploying hosted MCP server to ${REMOTE}..."
|
|
18
|
+
|
|
19
|
+
# 1. Create remote directory structure
|
|
20
|
+
echo "Creating remote directories..."
|
|
21
|
+
ssh "${REMOTE}" "mkdir -p ${REMOTE_DIR}/inbox"
|
|
22
|
+
|
|
23
|
+
# 2. Copy server files
|
|
24
|
+
echo "Copying files..."
|
|
25
|
+
scp "${SCRIPT_DIR}/server.mjs" "${REMOTE}:${REMOTE_DIR}/"
|
|
26
|
+
scp "${SCRIPT_DIR}/inbox.mjs" "${REMOTE}:${REMOTE_DIR}/"
|
|
27
|
+
scp "${SCRIPT_DIR}/tools.mjs" "${REMOTE}:${REMOTE_DIR}/"
|
|
28
|
+
scp "${SCRIPT_DIR}/package.json" "${REMOTE}:${REMOTE_DIR}/"
|
|
29
|
+
|
|
30
|
+
# 3. Install dependencies
|
|
31
|
+
echo "Installing dependencies..."
|
|
32
|
+
ssh "${REMOTE}" "cd ${REMOTE_DIR} && npm install --omit=dev"
|
|
33
|
+
|
|
34
|
+
# 4. Register with pm2 (restart if already running)
|
|
35
|
+
echo "Starting with pm2..."
|
|
36
|
+
ssh "${REMOTE}" "cd ${REMOTE_DIR} && pm2 delete mcp-server 2>/dev/null || true && pm2 start server.mjs --name mcp-server && pm2 save"
|
|
37
|
+
|
|
38
|
+
# 5. Configure nginx reverse proxy
|
|
39
|
+
echo "Configuring nginx..."
|
|
40
|
+
ssh "${REMOTE}" "cat > /tmp/mcp-server.conf << 'NGINX'
|
|
41
|
+
# MCP server reverse proxy
|
|
42
|
+
# Location block to add inside the wip.computer server block
|
|
43
|
+
location /mcp {
|
|
44
|
+
proxy_pass http://127.0.0.1:18800/mcp;
|
|
45
|
+
proxy_http_version 1.1;
|
|
46
|
+
proxy_set_header Host \$host;
|
|
47
|
+
proxy_set_header X-Real-IP \$remote_addr;
|
|
48
|
+
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
|
49
|
+
proxy_set_header X-Forwarded-Proto \$scheme;
|
|
50
|
+
|
|
51
|
+
# SSE support (for MCP Streamable HTTP GET streams)
|
|
52
|
+
proxy_set_header Connection '';
|
|
53
|
+
proxy_buffering off;
|
|
54
|
+
proxy_cache off;
|
|
55
|
+
proxy_read_timeout 86400;
|
|
56
|
+
chunked_transfer_encoding on;
|
|
57
|
+
}
|
|
58
|
+
NGINX
|
|
59
|
+
"
|
|
60
|
+
|
|
61
|
+
echo ""
|
|
62
|
+
echo "nginx config written to /tmp/mcp-server.conf on the server."
|
|
63
|
+
echo "To activate, add it to your server block and reload:"
|
|
64
|
+
echo " ssh ${REMOTE} 'sudo cp /tmp/mcp-server.conf /etc/nginx/snippets/mcp-server.conf'"
|
|
65
|
+
echo " # Then include it in your server block: include snippets/mcp-server.conf;"
|
|
66
|
+
echo " ssh ${REMOTE} 'sudo nginx -t && sudo systemctl reload nginx'"
|
|
67
|
+
echo ""
|
|
68
|
+
echo "Deployment complete."
|
|
69
|
+
echo "Health check: curl https://wip.computer/health"
|
|
70
|
+
echo "MCP endpoint: https://wip.computer/mcp"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// PM2 config for the hosted MCP server.
|
|
2
|
+
// API keys are resolved from 1Password at runtime via the op-secrets plugin.
|
|
3
|
+
// NEVER hardcode keys here. Use environment variables set by the deploy process,
|
|
4
|
+
// or read from 1Password at server startup.
|
|
5
|
+
module.exports = {
|
|
6
|
+
apps: [{
|
|
7
|
+
name: "mcp-server",
|
|
8
|
+
script: "server.mjs",
|
|
9
|
+
env: {
|
|
10
|
+
// XAI_API_KEY: resolved from 1Password at runtime (item: "x.ai - wip-computer-beta", field: "credential")
|
|
11
|
+
// DATABASE_URL: set in .env (gitignored)
|
|
12
|
+
}
|
|
13
|
+
}]
|
|
14
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// inbox.mjs: File-based message inbox for hosted MCP server.
|
|
2
|
+
// Each message is a JSON file. Same format as local bridge.
|
|
3
|
+
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { mkdirSync, readdirSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
|
|
8
|
+
const INBOX_DIR = process.env.MCP_INBOX_DIR || "/var/www/wip.computer/app/mcp-server/inbox";
|
|
9
|
+
mkdirSync(INBOX_DIR, { recursive: true });
|
|
10
|
+
|
|
11
|
+
/** Push a message into the inbox. Returns message ID. */
|
|
12
|
+
export function pushMessage({ from, to, body, type = "chat" }) {
|
|
13
|
+
const id = randomUUID();
|
|
14
|
+
const msg = { id, type, from, to, body, timestamp: new Date().toISOString(), read: false };
|
|
15
|
+
writeFileSync(join(INBOX_DIR, `${Date.now()}-${id}.json`), JSON.stringify(msg, null, 2));
|
|
16
|
+
return id;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Get messages for a recipient. If markRead, consumed messages are deleted. */
|
|
20
|
+
export function getMessages(to, markRead = false) {
|
|
21
|
+
const files = readdirSync(INBOX_DIR).filter((f) => f.endsWith(".json")).sort();
|
|
22
|
+
const matched = [];
|
|
23
|
+
for (const file of files) {
|
|
24
|
+
const fp = join(INBOX_DIR, file);
|
|
25
|
+
let msg;
|
|
26
|
+
try { msg = JSON.parse(readFileSync(fp, "utf-8")); } catch { continue; }
|
|
27
|
+
if (msg.read || !matches(msg.to, to)) continue;
|
|
28
|
+
matched.push(msg);
|
|
29
|
+
if (markRead) { try { unlinkSync(fp); } catch {} }
|
|
30
|
+
}
|
|
31
|
+
return matched;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Count pending (unread) messages for a recipient. */
|
|
35
|
+
export function countPending(to) {
|
|
36
|
+
const files = readdirSync(INBOX_DIR).filter((f) => f.endsWith(".json"));
|
|
37
|
+
let n = 0;
|
|
38
|
+
for (const file of files) {
|
|
39
|
+
try {
|
|
40
|
+
const msg = JSON.parse(readFileSync(join(INBOX_DIR, file), "utf-8"));
|
|
41
|
+
if (!msg.read && matches(msg.to, to)) n++;
|
|
42
|
+
} catch { continue; }
|
|
43
|
+
}
|
|
44
|
+
return n;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Recipient matching. Supports:
|
|
49
|
+
* "agent", "agent:session", "agent:*" (all sessions), "*" (broadcast)
|
|
50
|
+
*/
|
|
51
|
+
function matches(msgTo, query) {
|
|
52
|
+
if (msgTo === "*" || query === "*" || msgTo === query) return true;
|
|
53
|
+
if (query.endsWith(":*")) {
|
|
54
|
+
const p = query.slice(0, -2);
|
|
55
|
+
if (msgTo === p || msgTo.startsWith(p + ":")) return true;
|
|
56
|
+
}
|
|
57
|
+
if (msgTo.endsWith(":*")) {
|
|
58
|
+
const p = msgTo.slice(0, -2);
|
|
59
|
+
if (query === p || query.startsWith(p + ":")) return true;
|
|
60
|
+
}
|
|
61
|
+
if (!query.includes(":") && msgTo === query + ":default") return true;
|
|
62
|
+
if (!msgTo.includes(":") && query === msgTo + ":default") return true;
|
|
63
|
+
return false;
|
|
64
|
+
}
|