synabun 2026.3.316 → 2026.3.3102
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.
|
@@ -1,299 +1,335 @@
|
|
|
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>SynaBun - Offline</title>
|
|
7
|
-
<style>
|
|
8
|
-
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
9
|
-
|
|
10
|
-
body {
|
|
11
|
-
background: #030305;
|
|
12
|
-
color: rgba(255,255,255,0.7);
|
|
13
|
-
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
|
14
|
-
min-height: 100vh;
|
|
15
|
-
display: flex;
|
|
16
|
-
align-items: center;
|
|
17
|
-
justify-content: center;
|
|
18
|
-
overflow: hidden;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.card {
|
|
22
|
-
background: transparent;
|
|
23
|
-
border: none;
|
|
24
|
-
padding: 48px 40px 40px;
|
|
25
|
-
text-align: center;
|
|
26
|
-
max-width: 420px;
|
|
27
|
-
width: 90%;
|
|
28
|
-
position: relative;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/* --- Mascot --- */
|
|
32
|
-
.mascot-wrap {
|
|
33
|
-
display: inline-block;
|
|
34
|
-
position: relative;
|
|
35
|
-
animation: breathe 4s ease-in-out infinite;
|
|
36
|
-
margin-bottom: 28px;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
@keyframes breathe {
|
|
40
|
-
0%, 100% { transform: translateY(0); }
|
|
41
|
-
50% { transform: translateY(-5px); }
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/* --- Floating Z's --- */
|
|
45
|
-
.zzz {
|
|
46
|
-
position: absolute;
|
|
47
|
-
top: -4px;
|
|
48
|
-
right: -18px;
|
|
49
|
-
font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace;
|
|
50
|
-
font-weight: 700;
|
|
51
|
-
color: rgba(110,200,255,0.45);
|
|
52
|
-
pointer-events: none;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.zzz span {
|
|
56
|
-
position: absolute;
|
|
57
|
-
bottom: 0;
|
|
58
|
-
right: 0;
|
|
59
|
-
opacity: 0;
|
|
60
|
-
animation: floatZ 3s ease-out infinite;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
.zzz span:nth-child(1) { font-size: 14px; animation-delay: 0s; }
|
|
64
|
-
.zzz span:nth-child(2) { font-size: 18px; animation-delay: 1s; }
|
|
65
|
-
.zzz span:nth-child(3) { font-size: 22px; animation-delay: 2s; }
|
|
66
|
-
|
|
67
|
-
@keyframes floatZ {
|
|
68
|
-
0% { opacity: 0; transform: translate(0, 0) scale(0.7); }
|
|
69
|
-
12% { opacity: 0.7; }
|
|
70
|
-
100% { opacity: 0; transform: translate(28px, -55px) scale(1.3); }
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/* --- Text --- */
|
|
74
|
-
.heading {
|
|
75
|
-
font-size: 20px;
|
|
76
|
-
font-weight: 600;
|
|
77
|
-
color: rgba(255,255,255,0.8);
|
|
78
|
-
margin-bottom: 6px;
|
|
79
|
-
letter-spacing: -0.01em;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
.subtext {
|
|
83
|
-
font-size: 13px;
|
|
84
|
-
color: rgba(255,255,255,0.35);
|
|
85
|
-
margin-bottom: 28px;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/* --- Command
|
|
89
|
-
.cmd-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
color: rgba(255,255,255,0.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
<div class="
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
</div>
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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>SynaBun - Offline</title>
|
|
7
|
+
<style>
|
|
8
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
9
|
+
|
|
10
|
+
body {
|
|
11
|
+
background: #030305;
|
|
12
|
+
color: rgba(255,255,255,0.7);
|
|
13
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
|
14
|
+
min-height: 100vh;
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
justify-content: center;
|
|
18
|
+
overflow: hidden;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.card {
|
|
22
|
+
background: transparent;
|
|
23
|
+
border: none;
|
|
24
|
+
padding: 48px 40px 40px;
|
|
25
|
+
text-align: center;
|
|
26
|
+
max-width: 420px;
|
|
27
|
+
width: 90%;
|
|
28
|
+
position: relative;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* --- Mascot --- */
|
|
32
|
+
.mascot-wrap {
|
|
33
|
+
display: inline-block;
|
|
34
|
+
position: relative;
|
|
35
|
+
animation: breathe 4s ease-in-out infinite;
|
|
36
|
+
margin-bottom: 28px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@keyframes breathe {
|
|
40
|
+
0%, 100% { transform: translateY(0); }
|
|
41
|
+
50% { transform: translateY(-5px); }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* --- Floating Z's --- */
|
|
45
|
+
.zzz {
|
|
46
|
+
position: absolute;
|
|
47
|
+
top: -4px;
|
|
48
|
+
right: -18px;
|
|
49
|
+
font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace;
|
|
50
|
+
font-weight: 700;
|
|
51
|
+
color: rgba(110,200,255,0.45);
|
|
52
|
+
pointer-events: none;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.zzz span {
|
|
56
|
+
position: absolute;
|
|
57
|
+
bottom: 0;
|
|
58
|
+
right: 0;
|
|
59
|
+
opacity: 0;
|
|
60
|
+
animation: floatZ 3s ease-out infinite;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.zzz span:nth-child(1) { font-size: 14px; animation-delay: 0s; }
|
|
64
|
+
.zzz span:nth-child(2) { font-size: 18px; animation-delay: 1s; }
|
|
65
|
+
.zzz span:nth-child(3) { font-size: 22px; animation-delay: 2s; }
|
|
66
|
+
|
|
67
|
+
@keyframes floatZ {
|
|
68
|
+
0% { opacity: 0; transform: translate(0, 0) scale(0.7); }
|
|
69
|
+
12% { opacity: 0.7; }
|
|
70
|
+
100% { opacity: 0; transform: translate(28px, -55px) scale(1.3); }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* --- Text --- */
|
|
74
|
+
.heading {
|
|
75
|
+
font-size: 20px;
|
|
76
|
+
font-weight: 600;
|
|
77
|
+
color: rgba(255,255,255,0.8);
|
|
78
|
+
margin-bottom: 6px;
|
|
79
|
+
letter-spacing: -0.01em;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.subtext {
|
|
83
|
+
font-size: 13px;
|
|
84
|
+
color: rgba(255,255,255,0.35);
|
|
85
|
+
margin-bottom: 28px;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* --- Command labels --- */
|
|
89
|
+
.cmd-label {
|
|
90
|
+
font-size: 10px;
|
|
91
|
+
color: rgba(255,255,255,0.2);
|
|
92
|
+
margin-bottom: 6px;
|
|
93
|
+
text-transform: uppercase;
|
|
94
|
+
letter-spacing: 0.05em;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* --- Command block --- */
|
|
98
|
+
|
|
99
|
+
.cmd-block {
|
|
100
|
+
background: rgba(255,255,255,0.03);
|
|
101
|
+
border: 1px solid rgba(255,255,255,0.06);
|
|
102
|
+
border-radius: 10px;
|
|
103
|
+
padding: 14px 16px;
|
|
104
|
+
display: flex;
|
|
105
|
+
align-items: center;
|
|
106
|
+
justify-content: center;
|
|
107
|
+
gap: 12px;
|
|
108
|
+
margin-bottom: 8px;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.cmd-text {
|
|
112
|
+
flex: 1;
|
|
113
|
+
text-align: left;
|
|
114
|
+
font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace;
|
|
115
|
+
font-size: 11.5px;
|
|
116
|
+
color: rgba(255,255,255,0.55);
|
|
117
|
+
line-height: 1.5;
|
|
118
|
+
word-break: break-all;
|
|
119
|
+
user-select: all;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.cmd-text span {
|
|
123
|
+
color: rgba(110,200,255,0.6);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.cmd-or {
|
|
127
|
+
font-size: 10px;
|
|
128
|
+
color: rgba(255,255,255,0.15);
|
|
129
|
+
text-align: center;
|
|
130
|
+
margin-bottom: 8px;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.copy-btn {
|
|
134
|
+
flex-shrink: 0;
|
|
135
|
+
background: transparent;
|
|
136
|
+
border: 1px solid rgba(255,255,255,0.08);
|
|
137
|
+
border-radius: 6px;
|
|
138
|
+
color: rgba(255,255,255,0.35);
|
|
139
|
+
font-family: inherit;
|
|
140
|
+
font-size: 11px;
|
|
141
|
+
font-weight: 500;
|
|
142
|
+
padding: 6px 12px;
|
|
143
|
+
cursor: pointer;
|
|
144
|
+
transition: background 0.15s, color 0.15s, border-color 0.15s;
|
|
145
|
+
white-space: nowrap;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.copy-btn:hover {
|
|
149
|
+
background: rgba(255,255,255,0.06);
|
|
150
|
+
color: rgba(255,255,255,0.6);
|
|
151
|
+
border-color: rgba(255,255,255,0.12);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.copy-btn.copied {
|
|
155
|
+
background: rgba(110,200,255,0.12);
|
|
156
|
+
color: rgba(110,200,255,0.8);
|
|
157
|
+
border-color: rgba(110,200,255,0.2);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* --- Retry indicator --- */
|
|
161
|
+
.retry {
|
|
162
|
+
display: flex;
|
|
163
|
+
align-items: center;
|
|
164
|
+
justify-content: center;
|
|
165
|
+
gap: 8px;
|
|
166
|
+
font-size: 11px;
|
|
167
|
+
color: rgba(255,255,255,0.25);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.retry-dot {
|
|
171
|
+
width: 6px;
|
|
172
|
+
height: 6px;
|
|
173
|
+
border-radius: 50%;
|
|
174
|
+
background: rgba(110,200,255,0.4);
|
|
175
|
+
animation: pulse 2s ease-in-out infinite;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
@keyframes pulse {
|
|
179
|
+
0%, 100% { opacity: 0.4; transform: scale(1); }
|
|
180
|
+
50% { opacity: 1; transform: scale(1.3); }
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.retry.connected .retry-dot {
|
|
184
|
+
background: rgba(80,220,120,0.7);
|
|
185
|
+
animation: none;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.retry.connected {
|
|
189
|
+
color: rgba(80,220,120,0.6);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/* --- Ambient particles --- */
|
|
193
|
+
.particles {
|
|
194
|
+
position: fixed;
|
|
195
|
+
inset: 0;
|
|
196
|
+
pointer-events: none;
|
|
197
|
+
z-index: -1;
|
|
198
|
+
overflow: hidden;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.particles span {
|
|
202
|
+
position: absolute;
|
|
203
|
+
width: 2px;
|
|
204
|
+
height: 2px;
|
|
205
|
+
border-radius: 50%;
|
|
206
|
+
background: rgba(130,145,195,0.15);
|
|
207
|
+
animation: drift linear infinite;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
@keyframes drift {
|
|
211
|
+
0% { transform: translateY(100vh) translateX(0); opacity: 0; }
|
|
212
|
+
10% { opacity: 1; }
|
|
213
|
+
90% { opacity: 1; }
|
|
214
|
+
100% { transform: translateY(-10vh) translateX(30px); opacity: 0; }
|
|
215
|
+
}
|
|
216
|
+
</style>
|
|
217
|
+
</head>
|
|
218
|
+
<body>
|
|
219
|
+
|
|
220
|
+
<!-- Ambient particles -->
|
|
221
|
+
<div class="particles" id="particles"></div>
|
|
222
|
+
|
|
223
|
+
<div class="card">
|
|
224
|
+
<!-- Sleeping mascot -->
|
|
225
|
+
<div class="mascot-wrap">
|
|
226
|
+
<svg viewBox="0 0 280 140" width="180" height="90" style="overflow:visible">
|
|
227
|
+
<!-- Left eye — closed -->
|
|
228
|
+
<rect x="80" y="57" width="38" height="6" rx="3" fill="rgba(255,255,255,0.45)"/>
|
|
229
|
+
<!-- Right eye — closed -->
|
|
230
|
+
<rect x="162" y="57" width="38" height="6" rx="3" fill="rgba(255,255,255,0.45)"/>
|
|
231
|
+
<!-- Peaceful smile -->
|
|
232
|
+
<path d="M118,90 Q140,100 162,90" fill="none" stroke="rgba(255,255,255,0.15)" stroke-width="2.5" stroke-linecap="round"/>
|
|
233
|
+
</svg>
|
|
234
|
+
<div class="zzz">
|
|
235
|
+
<span>z</span>
|
|
236
|
+
<span>z</span>
|
|
237
|
+
<span>z</span>
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
<div class="heading">Server is sleeping</div>
|
|
242
|
+
<div class="subtext">The Neural Interface server isn't running.</div>
|
|
243
|
+
|
|
244
|
+
<div>
|
|
245
|
+
<div class="cmd-label">npm install</div>
|
|
246
|
+
<div class="cmd-block">
|
|
247
|
+
<div class="cmd-text" id="cmdText"><span>synabun</span></div>
|
|
248
|
+
<button class="copy-btn" id="copyBtn">Copy</button>
|
|
249
|
+
</div>
|
|
250
|
+
<div class="cmd-or" id="cmdOr" style="display:none">or</div>
|
|
251
|
+
<div class="cmd-label" id="cmdFallbackLabel" style="display:none">git / local install</div>
|
|
252
|
+
<div class="cmd-block" id="cmdFallback" style="display:none">
|
|
253
|
+
<div class="cmd-text" id="cmdFallbackText"></div>
|
|
254
|
+
<button class="copy-btn" id="copyFallbackBtn">Copy</button>
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
|
|
258
|
+
<div class="retry" id="retry">
|
|
259
|
+
<div class="retry-dot"></div>
|
|
260
|
+
<span id="retryText">Checking for server...</span>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
|
|
264
|
+
<script>
|
|
265
|
+
// --- Platform detection ---
|
|
266
|
+
const ua = navigator.userAgent;
|
|
267
|
+
const isWin = /Win/.test(navigator.platform) || /Windows/.test(ua);
|
|
268
|
+
const isMac = /Mac/.test(navigator.platform);
|
|
269
|
+
// Linux = everything else
|
|
270
|
+
|
|
271
|
+
const projectDir = localStorage.getItem('synabun-project-dir');
|
|
272
|
+
|
|
273
|
+
// --- Manual command fallback ---
|
|
274
|
+
if (projectDir) {
|
|
275
|
+
const sep = isWin ? ' & ' : ' ; ';
|
|
276
|
+
const fallbackCmd = `cd "${projectDir}"${sep}npm start`;
|
|
277
|
+
const cmdOr = document.getElementById('cmdOr');
|
|
278
|
+
const cmdFallback = document.getElementById('cmdFallback');
|
|
279
|
+
const cmdFallbackText = document.getElementById('cmdFallbackText');
|
|
280
|
+
cmdOr.style.display = '';
|
|
281
|
+
cmdFallback.style.display = '';
|
|
282
|
+
const cmdFallbackLabel = document.getElementById('cmdFallbackLabel');
|
|
283
|
+
if (cmdFallbackLabel) cmdFallbackLabel.style.display = '';
|
|
284
|
+
cmdFallbackText.innerHTML = `<span>cd</span> "${projectDir}"${sep}<span>npm</span> start`;
|
|
285
|
+
document.getElementById('copyFallbackBtn').addEventListener('click', function() { copyToClipboard(fallbackCmd, this); });
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// --- Copy helper ---
|
|
289
|
+
function copyToClipboard(text, btn) {
|
|
290
|
+
navigator.clipboard.writeText(text).catch(() => {
|
|
291
|
+
const ta = document.createElement('textarea');
|
|
292
|
+
ta.value = text;
|
|
293
|
+
ta.style.position = 'fixed';
|
|
294
|
+
ta.style.opacity = '0';
|
|
295
|
+
document.body.appendChild(ta);
|
|
296
|
+
ta.select();
|
|
297
|
+
document.execCommand('copy');
|
|
298
|
+
document.body.removeChild(ta);
|
|
299
|
+
});
|
|
300
|
+
btn.textContent = 'Copied!';
|
|
301
|
+
btn.classList.add('copied');
|
|
302
|
+
setTimeout(() => { btn.textContent = 'Copy'; btn.classList.remove('copied'); }, 1500);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
document.getElementById('copyBtn').addEventListener('click', function() { copyToClipboard('synabun', this); });
|
|
306
|
+
|
|
307
|
+
// --- Auto-retry ---
|
|
308
|
+
const retry = document.getElementById('retry');
|
|
309
|
+
const retryText = document.getElementById('retryText');
|
|
310
|
+
|
|
311
|
+
const interval = setInterval(async () => {
|
|
312
|
+
try {
|
|
313
|
+
const res = await fetch('/', { method: 'HEAD', cache: 'no-store' });
|
|
314
|
+
if (res.ok) {
|
|
315
|
+
clearInterval(interval);
|
|
316
|
+
retry.classList.add('connected');
|
|
317
|
+
retryText.textContent = 'Server is back! Reconnecting...';
|
|
318
|
+
setTimeout(() => location.reload(), 600);
|
|
319
|
+
}
|
|
320
|
+
} catch {}
|
|
321
|
+
}, 3000);
|
|
322
|
+
|
|
323
|
+
// --- Ambient particles ---
|
|
324
|
+
const container = document.getElementById('particles');
|
|
325
|
+
for (let i = 0; i < 20; i++) {
|
|
326
|
+
const p = document.createElement('span');
|
|
327
|
+
p.style.left = Math.random() * 100 + '%';
|
|
328
|
+
p.style.animationDuration = (12 + Math.random() * 18) + 's';
|
|
329
|
+
p.style.animationDelay = -(Math.random() * 20) + 's';
|
|
330
|
+
p.style.width = p.style.height = (1.5 + Math.random() * 1.5) + 'px';
|
|
331
|
+
container.appendChild(p);
|
|
332
|
+
}
|
|
333
|
+
</script>
|
|
334
|
+
</body>
|
|
335
|
+
</html>
|
|
@@ -165,6 +165,8 @@ async function handleStartAction() {
|
|
|
165
165
|
if ($label) $label.textContent = t('common.retry');
|
|
166
166
|
}
|
|
167
167
|
} catch (err) {
|
|
168
|
+
// Server is unreachable — try protocol handler as fallback
|
|
169
|
+
window.location.href = 'synabun://start';
|
|
168
170
|
if ($status) $status.textContent = t('loading.somethingWrong');
|
|
169
171
|
if (btn) btn.disabled = false;
|
|
170
172
|
if ($label) $label.textContent = t('common.retry');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const CACHE_NAME = 'synabun-offline-
|
|
1
|
+
const CACHE_NAME = 'synabun-offline-v9';
|
|
2
2
|
const OFFLINE_URL = '/offline.html';
|
|
3
3
|
|
|
4
4
|
self.addEventListener('install', (event) => {
|
|
@@ -20,7 +20,13 @@ self.addEventListener('activate', (event) => {
|
|
|
20
20
|
self.addEventListener('fetch', (event) => {
|
|
21
21
|
if (event.request.mode !== 'navigate') return;
|
|
22
22
|
event.respondWith(
|
|
23
|
-
fetch(event.request).
|
|
23
|
+
fetch(event.request).then((response) => {
|
|
24
|
+
// Server is up — silently refresh cached offline.html so it's always current
|
|
25
|
+
caches.open(CACHE_NAME).then((cache) =>
|
|
26
|
+
cache.add(new Request(OFFLINE_URL, { cache: 'reload' }))
|
|
27
|
+
);
|
|
28
|
+
return response;
|
|
29
|
+
}).catch(() => caches.match(OFFLINE_URL))
|
|
24
30
|
);
|
|
25
31
|
});
|
|
26
32
|
|
package/package.json
CHANGED
package/postinstall.js
CHANGED
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { execSync } from 'node:child_process';
|
|
12
|
-
import { existsSync } from 'node:fs';
|
|
12
|
+
import { existsSync, mkdirSync, writeFileSync, chmodSync } from 'node:fs';
|
|
13
13
|
import { resolve, dirname } from 'node:path';
|
|
14
14
|
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { homedir, platform } from 'node:os';
|
|
15
16
|
|
|
16
17
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
18
|
|
|
@@ -108,6 +109,90 @@ function buildMcpServer() {
|
|
|
108
109
|
}
|
|
109
110
|
}
|
|
110
111
|
|
|
112
|
+
// ── Protocol handler registration ──
|
|
113
|
+
|
|
114
|
+
function registerProtocolHandler() {
|
|
115
|
+
const nodeBin = process.execPath;
|
|
116
|
+
const setupJs = resolve(__dirname, 'setup.js');
|
|
117
|
+
const plat = platform();
|
|
118
|
+
|
|
119
|
+
if (plat === 'win32') {
|
|
120
|
+
// Windows — HKCU registry (no admin required)
|
|
121
|
+
try {
|
|
122
|
+
const cmd = `"${nodeBin}" "${setupJs}" "%1"`;
|
|
123
|
+
execSync(`reg add "HKCU\\Software\\Classes\\synabun" /ve /d "URL:SynaBun Protocol" /f`, { stdio: 'pipe' });
|
|
124
|
+
execSync(`reg add "HKCU\\Software\\Classes\\synabun" /v "URL Protocol" /d "" /f`, { stdio: 'pipe' });
|
|
125
|
+
execSync(`reg add "HKCU\\Software\\Classes\\synabun\\shell\\open\\command" /ve /d "${cmd}" /f`, { stdio: 'pipe' });
|
|
126
|
+
ok('Registered synabun:// protocol handler');
|
|
127
|
+
} catch {
|
|
128
|
+
warn('Could not register synabun:// protocol handler');
|
|
129
|
+
}
|
|
130
|
+
} else if (plat === 'darwin') {
|
|
131
|
+
// macOS — minimal .app bundle in ~/.synabun/
|
|
132
|
+
try {
|
|
133
|
+
const appDir = resolve(homedir(), '.synabun', 'SynaBun.app', 'Contents');
|
|
134
|
+
const macosDir = resolve(appDir, 'MacOS');
|
|
135
|
+
mkdirSync(macosDir, { recursive: true });
|
|
136
|
+
|
|
137
|
+
writeFileSync(resolve(appDir, 'Info.plist'), [
|
|
138
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
139
|
+
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
140
|
+
'<plist version="1.0">',
|
|
141
|
+
'<dict>',
|
|
142
|
+
' <key>CFBundleIdentifier</key><string>ai.synabun.launcher</string>',
|
|
143
|
+
' <key>CFBundleName</key><string>SynaBun</string>',
|
|
144
|
+
' <key>CFBundleExecutable</key><string>synabun-launcher</string>',
|
|
145
|
+
' <key>CFBundleVersion</key><string>1.0</string>',
|
|
146
|
+
' <key>CFBundleURLTypes</key>',
|
|
147
|
+
' <array><dict>',
|
|
148
|
+
' <key>CFBundleURLName</key><string>SynaBun Protocol</string>',
|
|
149
|
+
' <key>CFBundleURLSchemes</key><array><string>synabun</string></array>',
|
|
150
|
+
' </dict></array>',
|
|
151
|
+
'</dict>',
|
|
152
|
+
'</plist>',
|
|
153
|
+
].join('\n'));
|
|
154
|
+
|
|
155
|
+
const launcherPath = resolve(macosDir, 'synabun-launcher');
|
|
156
|
+
writeFileSync(launcherPath, `#!/bin/sh\nexec "${nodeBin}" "${setupJs}" "$@"\n`);
|
|
157
|
+
chmodSync(launcherPath, 0o755);
|
|
158
|
+
|
|
159
|
+
// Register with LaunchServices
|
|
160
|
+
try {
|
|
161
|
+
execSync(`/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -R "${resolve(homedir(), '.synabun', 'SynaBun.app')}"`, { stdio: 'pipe' });
|
|
162
|
+
} catch {
|
|
163
|
+
try { execSync(`open "${resolve(homedir(), '.synabun', 'SynaBun.app')}"`, { stdio: 'pipe' }); } catch {}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
ok('Registered synabun:// protocol handler');
|
|
167
|
+
} catch {
|
|
168
|
+
warn('Could not register synabun:// protocol handler');
|
|
169
|
+
}
|
|
170
|
+
} else {
|
|
171
|
+
// Linux — .desktop file
|
|
172
|
+
try {
|
|
173
|
+
const appsDir = resolve(homedir(), '.local', 'share', 'applications');
|
|
174
|
+
mkdirSync(appsDir, { recursive: true });
|
|
175
|
+
|
|
176
|
+
writeFileSync(resolve(appsDir, 'synabun.desktop'), [
|
|
177
|
+
'[Desktop Entry]',
|
|
178
|
+
'Type=Application',
|
|
179
|
+
'Name=SynaBun',
|
|
180
|
+
`Exec="${nodeBin}" "${setupJs}" %u`,
|
|
181
|
+
'MimeType=x-scheme-handler/synabun;',
|
|
182
|
+
'NoDisplay=true',
|
|
183
|
+
'Terminal=true',
|
|
184
|
+
'',
|
|
185
|
+
].join('\n'));
|
|
186
|
+
|
|
187
|
+
try { execSync('xdg-mime default synabun.desktop x-scheme-handler/synabun', { stdio: 'pipe' }); } catch {}
|
|
188
|
+
|
|
189
|
+
ok('Registered synabun:// protocol handler');
|
|
190
|
+
} catch {
|
|
191
|
+
warn('Could not register synabun:// protocol handler');
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
111
196
|
// ── Main ──
|
|
112
197
|
|
|
113
198
|
function main() {
|
|
@@ -117,6 +202,7 @@ function main() {
|
|
|
117
202
|
installDeps('MCP Server', resolve(__dirname, 'mcp-server'), { includeDev: needsBuild() });
|
|
118
203
|
rebuildPty();
|
|
119
204
|
buildMcpServer();
|
|
205
|
+
registerProtocolHandler();
|
|
120
206
|
|
|
121
207
|
console.log(`\n ${c.green}\u2713${c.reset} Setup complete. Run ${c.cyan}synabun${c.reset} to start.\n`);
|
|
122
208
|
}
|
package/preuninstall.js
CHANGED
|
@@ -5,15 +5,20 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Runs before `npm uninstall synabun`.
|
|
7
7
|
* Removes nested node_modules that cause EPERM/deep-path errors on Windows.
|
|
8
|
+
* Removes the synabun:// protocol handler registration.
|
|
8
9
|
* Graceful — if cleanup fails, don't block the uninstall.
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
|
-
import { rmSync } from 'node:fs';
|
|
12
|
+
import { rmSync, existsSync } from 'node:fs';
|
|
12
13
|
import { resolve, dirname } from 'node:path';
|
|
13
14
|
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { execSync } from 'node:child_process';
|
|
16
|
+
import { homedir, platform } from 'node:os';
|
|
14
17
|
|
|
15
18
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
19
|
|
|
20
|
+
// ── Remove nested node_modules ──
|
|
21
|
+
|
|
17
22
|
const dirs = [
|
|
18
23
|
resolve(__dirname, 'neural-interface', 'node_modules'),
|
|
19
24
|
resolve(__dirname, 'mcp-server', 'node_modules'),
|
|
@@ -26,3 +31,23 @@ for (const dir of dirs) {
|
|
|
26
31
|
// Non-fatal — npm will handle remaining cleanup
|
|
27
32
|
}
|
|
28
33
|
}
|
|
34
|
+
|
|
35
|
+
// ── Remove protocol handler ──
|
|
36
|
+
|
|
37
|
+
const plat = platform();
|
|
38
|
+
|
|
39
|
+
if (plat === 'win32') {
|
|
40
|
+
try {
|
|
41
|
+
execSync('reg delete "HKCU\\Software\\Classes\\synabun" /f', { stdio: 'pipe' });
|
|
42
|
+
} catch {}
|
|
43
|
+
} else if (plat === 'darwin') {
|
|
44
|
+
const appPath = resolve(homedir(), '.synabun', 'SynaBun.app');
|
|
45
|
+
try {
|
|
46
|
+
rmSync(appPath, { recursive: true, force: true });
|
|
47
|
+
} catch {}
|
|
48
|
+
} else {
|
|
49
|
+
const desktopFile = resolve(homedir(), '.local', 'share', 'applications', 'synabun.desktop');
|
|
50
|
+
try {
|
|
51
|
+
if (existsSync(desktopFile)) rmSync(desktopFile);
|
|
52
|
+
} catch {}
|
|
53
|
+
}
|