synabun 1.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/CHANGELOG.md +665 -0
- package/LICENSE +191 -0
- package/README.md +853 -0
- package/hooks/claude-code/post-plan.mjs +391 -0
- package/hooks/claude-code/post-remember.mjs +239 -0
- package/hooks/claude-code/pre-compact.mjs +241 -0
- package/hooks/claude-code/pre-websearch.mjs +90 -0
- package/hooks/claude-code/prompt-submit.mjs +861 -0
- package/hooks/claude-code/session-start.mjs +494 -0
- package/hooks/claude-code/shared.mjs +368 -0
- package/hooks/claude-code/stop.mjs +646 -0
- package/hooks/claude-code/test-user-learning.mjs +531 -0
- package/mcp-server/package.json +38 -0
- package/mcp-server/run.mjs +10 -0
- package/mcp-server/src/config.ts +70 -0
- package/mcp-server/src/http.ts +93 -0
- package/mcp-server/src/index.ts +203 -0
- package/mcp-server/src/preload-http.ts +25 -0
- package/mcp-server/src/preload.ts +29 -0
- package/mcp-server/src/scripts/migrate-qdrant-to-sqlite.ts +506 -0
- package/mcp-server/src/services/categories.ts +429 -0
- package/mcp-server/src/services/discord.ts +702 -0
- package/mcp-server/src/services/file-checksums.ts +33 -0
- package/mcp-server/src/services/local-embeddings.ts +77 -0
- package/mcp-server/src/services/neural-interface.ts +344 -0
- package/mcp-server/src/services/sqlite.ts +823 -0
- package/mcp-server/src/tools/browser-advanced.ts +123 -0
- package/mcp-server/src/tools/browser-interact.ts +255 -0
- package/mcp-server/src/tools/browser-navigate.ts +77 -0
- package/mcp-server/src/tools/browser-observe.ts +1330 -0
- package/mcp-server/src/tools/browser.ts +107 -0
- package/mcp-server/src/tools/card-tools.ts +193 -0
- package/mcp-server/src/tools/card.ts +22 -0
- package/mcp-server/src/tools/category.ts +379 -0
- package/mcp-server/src/tools/discord-channel.ts +274 -0
- package/mcp-server/src/tools/discord-guild.ts +188 -0
- package/mcp-server/src/tools/discord-member.ts +158 -0
- package/mcp-server/src/tools/discord-message.ts +231 -0
- package/mcp-server/src/tools/discord-onboarding.ts +228 -0
- package/mcp-server/src/tools/discord-role.ts +209 -0
- package/mcp-server/src/tools/discord-thread.ts +110 -0
- package/mcp-server/src/tools/discord-webhook.ts +176 -0
- package/mcp-server/src/tools/discord.ts +40 -0
- package/mcp-server/src/tools/forget.ts +38 -0
- package/mcp-server/src/tools/git-tools.ts +141 -0
- package/mcp-server/src/tools/git.ts +13 -0
- package/mcp-server/src/tools/image-tools.ts +98 -0
- package/mcp-server/src/tools/image.ts +17 -0
- package/mcp-server/src/tools/leonardo-browser-tools.ts +281 -0
- package/mcp-server/src/tools/leonardo.ts +28 -0
- package/mcp-server/src/tools/loop.ts +348 -0
- package/mcp-server/src/tools/memories.ts +118 -0
- package/mcp-server/src/tools/recall.ts +323 -0
- package/mcp-server/src/tools/reflect.ts +145 -0
- package/mcp-server/src/tools/remember.ts +115 -0
- package/mcp-server/src/tools/response.ts +26 -0
- package/mcp-server/src/tools/restore.ts +34 -0
- package/mcp-server/src/tools/sync.ts +114 -0
- package/mcp-server/src/tools/tictactoe-tools.ts +98 -0
- package/mcp-server/src/tools/tictactoe.ts +13 -0
- package/mcp-server/src/tools/utils.ts +25 -0
- package/mcp-server/src/tools/whiteboard-tools.ts +260 -0
- package/mcp-server/src/tools/whiteboard.ts +22 -0
- package/mcp-server/src/tui.ts +940 -0
- package/mcp-server/src/types.ts +85 -0
- package/mcp-server/tsconfig.json +19 -0
- package/neural-interface/package.json +27 -0
- package/neural-interface/public/apple-touch-icon.png +0 -0
- package/neural-interface/public/category-logos/claude.svg +1 -0
- package/neural-interface/public/category-logos/gemini-color.svg +1 -0
- package/neural-interface/public/category-logos/openai.svg +1 -0
- package/neural-interface/public/category-logos/synabun.png +0 -0
- package/neural-interface/public/claude-chat.css +878 -0
- package/neural-interface/public/claude-chat.html +92 -0
- package/neural-interface/public/claude-chat.js +1168 -0
- package/neural-interface/public/favicon-16x16.png +0 -0
- package/neural-interface/public/favicon-32x32.png +0 -0
- package/neural-interface/public/gallery.svg +6 -0
- package/neural-interface/public/icon-192.png +0 -0
- package/neural-interface/public/icon-512.png +0 -0
- package/neural-interface/public/index-legacy.html +14677 -0
- package/neural-interface/public/index.html +212 -0
- package/neural-interface/public/index2d-legacy.html +8193 -0
- package/neural-interface/public/index2d.html +127 -0
- package/neural-interface/public/invite.html +351 -0
- package/neural-interface/public/leonardoai.svg +1 -0
- package/neural-interface/public/logo-synabun.png +0 -0
- package/neural-interface/public/logoHD.png +0 -0
- package/neural-interface/public/manifest.json +14 -0
- package/neural-interface/public/offline.html +299 -0
- package/neural-interface/public/onboarding.html +2335 -0
- package/neural-interface/public/openclaw-logo-text.png +0 -0
- package/neural-interface/public/pin-svgrepo-com.svg +25 -0
- package/neural-interface/public/sessionicon.svg +8 -0
- package/neural-interface/public/shared/ansi-parser.js +305 -0
- package/neural-interface/public/shared/api.js +1043 -0
- package/neural-interface/public/shared/colors.js +153 -0
- package/neural-interface/public/shared/constants.js +141 -0
- package/neural-interface/public/shared/html-shell.js +777 -0
- package/neural-interface/public/shared/html-term-renderer.js +1022 -0
- package/neural-interface/public/shared/i18n.js +147 -0
- package/neural-interface/public/shared/registry.js +82 -0
- package/neural-interface/public/shared/state.js +86 -0
- package/neural-interface/public/shared/storage.js +150 -0
- package/neural-interface/public/shared/styles.css +21212 -0
- package/neural-interface/public/shared/ui-automation-studio.js +3573 -0
- package/neural-interface/public/shared/ui-bookmarks.js +231 -0
- package/neural-interface/public/shared/ui-claude-panel.js +6333 -0
- package/neural-interface/public/shared/ui-command-runner.js +534 -0
- package/neural-interface/public/shared/ui-cost-widget.js +602 -0
- package/neural-interface/public/shared/ui-detail.js +1454 -0
- package/neural-interface/public/shared/ui-explorer.js +1356 -0
- package/neural-interface/public/shared/ui-file-explorer.js +2750 -0
- package/neural-interface/public/shared/ui-games.js +411 -0
- package/neural-interface/public/shared/ui-help.js +107 -0
- package/neural-interface/public/shared/ui-image-gallery.js +461 -0
- package/neural-interface/public/shared/ui-invite.js +519 -0
- package/neural-interface/public/shared/ui-keybinds.js +452 -0
- package/neural-interface/public/shared/ui-layouts.js +297 -0
- package/neural-interface/public/shared/ui-link.js +731 -0
- package/neural-interface/public/shared/ui-loading.js +255 -0
- package/neural-interface/public/shared/ui-menubar.js +499 -0
- package/neural-interface/public/shared/ui-multiselect.js +93 -0
- package/neural-interface/public/shared/ui-navbar.js +222 -0
- package/neural-interface/public/shared/ui-notifications.js +287 -0
- package/neural-interface/public/shared/ui-panels.js +309 -0
- package/neural-interface/public/shared/ui-resume.js +653 -0
- package/neural-interface/public/shared/ui-search.js +145 -0
- package/neural-interface/public/shared/ui-sessions.js +531 -0
- package/neural-interface/public/shared/ui-settings.js +5987 -0
- package/neural-interface/public/shared/ui-sidebar.js +1905 -0
- package/neural-interface/public/shared/ui-skills.js +2805 -0
- package/neural-interface/public/shared/ui-stats.js +38 -0
- package/neural-interface/public/shared/ui-sync.js +201 -0
- package/neural-interface/public/shared/ui-terminal.js +6385 -0
- package/neural-interface/public/shared/ui-tooltip.js +123 -0
- package/neural-interface/public/shared/ui-trash.js +439 -0
- package/neural-interface/public/shared/ui-tutorial-draw.js +875 -0
- package/neural-interface/public/shared/ui-tutorial-steps.js +2276 -0
- package/neural-interface/public/shared/ui-tutorial.js +1117 -0
- package/neural-interface/public/shared/ui-update.js +244 -0
- package/neural-interface/public/shared/ui-whiteboard.js +3262 -0
- package/neural-interface/public/shared/ui-workspaces.js +722 -0
- package/neural-interface/public/shared/utils.js +360 -0
- package/neural-interface/public/shared/vterm-buffer.js +1102 -0
- package/neural-interface/public/sw.js +52 -0
- package/neural-interface/public/synabun.png +0 -0
- package/neural-interface/public/toolbar-album.svg +8 -0
- package/neural-interface/public/toolbar-question.svg +6 -0
- package/neural-interface/public/toolbar-sessions.svg +6 -0
- package/neural-interface/public/variant/2d/background.js +113 -0
- package/neural-interface/public/variant/2d/context-menu.js +133 -0
- package/neural-interface/public/variant/2d/gfx.js +45 -0
- package/neural-interface/public/variant/2d/graph.js +1635 -0
- package/neural-interface/public/variant/2d/hulls.js +207 -0
- package/neural-interface/public/variant/2d/lasso.js +135 -0
- package/neural-interface/public/variant/2d/layout.js +594 -0
- package/neural-interface/public/variant/2d/main.js +521 -0
- package/neural-interface/public/variant/2d/minimap.js +312 -0
- package/neural-interface/public/variant/2d/settings-gfx.js +98 -0
- package/neural-interface/public/variant/3d/background.js +884 -0
- package/neural-interface/public/variant/3d/camera.js +661 -0
- package/neural-interface/public/variant/3d/gfx.js +116 -0
- package/neural-interface/public/variant/3d/graph.js +2399 -0
- package/neural-interface/public/variant/3d/main.js +500 -0
- package/neural-interface/public/variant/3d/settings-gfx.js +228 -0
- package/neural-interface/scripts/rebuild-pty.js +100 -0
- package/neural-interface/server.js +15000 -0
- package/neural-interface/templates/CLAUDE-template.md +211 -0
- package/package.json +53 -0
- package/postinstall.js +135 -0
- package/setup.js +260 -0
- package/skills/leonardo/SKILL.md +159 -0
- package/skills/leonardo/icon.svg +1 -0
- package/skills/leonardo/modules/browser-guide.md +378 -0
- package/skills/leonardo/modules/image-prompter.md +277 -0
- package/skills/leonardo/modules/model-advisor.md +100 -0
- package/skills/leonardo/modules/prompt-library.md +110 -0
- package/skills/leonardo/modules/style-guide.md +186 -0
- package/skills/leonardo/modules/video-prompter.md +311 -0
- package/skills/synabun/SKILL.md +185 -0
- package/skills/synabun/icon.png +0 -0
- package/skills/synabun/modules/audit.md +374 -0
- package/skills/synabun/modules/changelog.md +194 -0
- package/skills/synabun/modules/idea.md +144 -0
- package/skills/synabun/modules/memorize.md +188 -0
- package/skills/synabun/synabunicon.png +0 -0
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun Neural Interface — Invite / Share
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
// Toolbar dropdown for session invitation management.
|
|
5
|
+
// Follows the same pattern as ui-workspaces.js.
|
|
6
|
+
|
|
7
|
+
import { emit, on } from './state.js';
|
|
8
|
+
import {
|
|
9
|
+
fetchInviteStatus,
|
|
10
|
+
fetchTunnelStatus,
|
|
11
|
+
startTunnel,
|
|
12
|
+
stopTunnel,
|
|
13
|
+
generateInviteKey,
|
|
14
|
+
revokeInviteKey,
|
|
15
|
+
revokeAllInviteSessions,
|
|
16
|
+
saveInviteProxy,
|
|
17
|
+
fetchInvitePermissions,
|
|
18
|
+
saveInvitePermissions,
|
|
19
|
+
} from './api.js';
|
|
20
|
+
import { isGuest } from './ui-sync.js';
|
|
21
|
+
|
|
22
|
+
const $ = (id) => document.getElementById(id);
|
|
23
|
+
|
|
24
|
+
let _isOpen = false;
|
|
25
|
+
let _data = { hasKey: false, maskedKey: null, activeSessions: 0, proxyConfig: { useProxy: false, proxyUrl: '' } };
|
|
26
|
+
let _tunnelUrl = null;
|
|
27
|
+
let _tunnelAvailable = false;
|
|
28
|
+
let _tunnelStarting = false;
|
|
29
|
+
let _tunnelPollTimer = null;
|
|
30
|
+
let _keyMode = 'auto'; // 'auto' | 'custom'
|
|
31
|
+
let _revealedKey = null; // shown only right after generation
|
|
32
|
+
let _permissions = { terminal: false, whiteboard: false, memories: true, skills: false, cards: true, browser: false };
|
|
33
|
+
|
|
34
|
+
// ─── Dropdown open/close ────────────────
|
|
35
|
+
|
|
36
|
+
function openDropdown() {
|
|
37
|
+
const dd = $('invite-dropdown');
|
|
38
|
+
if (!dd) return;
|
|
39
|
+
_isOpen = true;
|
|
40
|
+
dd.style.display = '';
|
|
41
|
+
$('invite-btn')?.classList.add('active');
|
|
42
|
+
emit('panel:close-all-dropdowns-except', 'invite');
|
|
43
|
+
loadAndRender();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function closeDropdown() {
|
|
47
|
+
const dd = $('invite-dropdown');
|
|
48
|
+
if (!dd) return;
|
|
49
|
+
_isOpen = false;
|
|
50
|
+
dd.style.display = 'none';
|
|
51
|
+
$('invite-btn')?.classList.remove('active');
|
|
52
|
+
_revealedKey = null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function toggleDropdown() {
|
|
56
|
+
_isOpen ? closeDropdown() : openDropdown();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ─── Data fetching ──────────────────────
|
|
60
|
+
|
|
61
|
+
async function loadAndRender() {
|
|
62
|
+
try {
|
|
63
|
+
const [inviteRes, tunnelRes, permRes] = await Promise.all([
|
|
64
|
+
fetchInviteStatus(),
|
|
65
|
+
fetchTunnelStatus(),
|
|
66
|
+
fetchInvitePermissions(),
|
|
67
|
+
]);
|
|
68
|
+
_data = inviteRes;
|
|
69
|
+
_tunnelUrl = tunnelRes.url || null;
|
|
70
|
+
_tunnelAvailable = tunnelRes.available;
|
|
71
|
+
_tunnelStarting = tunnelRes.starting || false;
|
|
72
|
+
if (permRes?.permissions) _permissions = permRes.permissions;
|
|
73
|
+
} catch (err) {
|
|
74
|
+
console.warn('[invite] Failed to load status:', err);
|
|
75
|
+
}
|
|
76
|
+
render();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ─── Tunnel polling ─────────────────────
|
|
80
|
+
|
|
81
|
+
function pollForTunnelUrl() {
|
|
82
|
+
if (_tunnelPollTimer) clearInterval(_tunnelPollTimer);
|
|
83
|
+
let attempts = 0;
|
|
84
|
+
_tunnelPollTimer = setInterval(async () => {
|
|
85
|
+
attempts++;
|
|
86
|
+
try {
|
|
87
|
+
const res = await fetchTunnelStatus();
|
|
88
|
+
if (res.url) {
|
|
89
|
+
clearInterval(_tunnelPollTimer);
|
|
90
|
+
_tunnelPollTimer = null;
|
|
91
|
+
_tunnelUrl = res.url;
|
|
92
|
+
_tunnelStarting = false;
|
|
93
|
+
if (_isOpen) render();
|
|
94
|
+
} else if (attempts >= 30) {
|
|
95
|
+
clearInterval(_tunnelPollTimer);
|
|
96
|
+
_tunnelPollTimer = null;
|
|
97
|
+
_tunnelStarting = false;
|
|
98
|
+
if (_isOpen) render();
|
|
99
|
+
}
|
|
100
|
+
} catch {}
|
|
101
|
+
}, 1000);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ─── URL helpers ────────────────────────
|
|
105
|
+
|
|
106
|
+
function getBaseUrl() {
|
|
107
|
+
if (_data.proxyConfig?.useProxy && _data.proxyConfig?.proxyUrl) {
|
|
108
|
+
return _data.proxyConfig.proxyUrl.replace(/\/+$/, '');
|
|
109
|
+
}
|
|
110
|
+
return _tunnelUrl || null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function getInviteUrl() {
|
|
114
|
+
const base = getBaseUrl();
|
|
115
|
+
return base ? base + '/invite' : null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ─── Clipboard helper ───────────────────
|
|
119
|
+
|
|
120
|
+
function flashCopied(el) {
|
|
121
|
+
el.classList.add('show');
|
|
122
|
+
setTimeout(() => el.classList.remove('show'), 1500);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function copyText(text, feedbackId) {
|
|
126
|
+
try {
|
|
127
|
+
await navigator.clipboard.writeText(text);
|
|
128
|
+
const el = $(feedbackId);
|
|
129
|
+
if (el) flashCopied(el);
|
|
130
|
+
} catch {}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ─── Render ─────────────────────────────
|
|
134
|
+
|
|
135
|
+
function render() {
|
|
136
|
+
const dd = $('invite-dropdown');
|
|
137
|
+
if (!dd) return;
|
|
138
|
+
|
|
139
|
+
const baseUrl = getBaseUrl();
|
|
140
|
+
const inviteUrl = getInviteUrl();
|
|
141
|
+
const hasUrl = !!baseUrl;
|
|
142
|
+
const useProxy = _data.proxyConfig?.useProxy || false;
|
|
143
|
+
|
|
144
|
+
dd.innerHTML = `
|
|
145
|
+
<div class="invite-section">
|
|
146
|
+
<div class="invite-section-label">Connection</div>
|
|
147
|
+
${useProxy ? `
|
|
148
|
+
<input type="text" class="invite-proxy-input" id="invite-proxy-url"
|
|
149
|
+
placeholder="https://your-domain.com"
|
|
150
|
+
value="${escHtml(_data.proxyConfig?.proxyUrl || '')}">
|
|
151
|
+
<div class="invite-action-row" style="margin-top:6px">
|
|
152
|
+
<button class="invite-btn primary" id="invite-proxy-save">Save</button>
|
|
153
|
+
<button class="invite-alt-link" id="invite-mode-tunnel">Use Cloudflare tunnel</button>
|
|
154
|
+
</div>
|
|
155
|
+
` : `
|
|
156
|
+
${hasUrl ? `
|
|
157
|
+
<div class="invite-status-row">
|
|
158
|
+
<span class="invite-status-dot active"></span>
|
|
159
|
+
<span class="invite-url-text">${baseUrl}</span>
|
|
160
|
+
<button class="invite-copy-icon" id="invite-copy-base-url" data-tooltip="Copy"><svg viewBox="0 0 24 24"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>
|
|
161
|
+
<span class="invite-copied" id="invite-copied-url">Copied</span>
|
|
162
|
+
</div>
|
|
163
|
+
<div class="invite-action-row">
|
|
164
|
+
<button class="invite-btn" id="invite-tunnel-stop">Stop</button>
|
|
165
|
+
<button class="invite-alt-link" id="invite-mode-proxy">Use custom URL</button>
|
|
166
|
+
</div>
|
|
167
|
+
` : `
|
|
168
|
+
${_tunnelAvailable ? `
|
|
169
|
+
${_tunnelStarting ? `
|
|
170
|
+
<div class="invite-action-row">
|
|
171
|
+
<button class="invite-btn" disabled style="opacity:0.5;cursor:default">
|
|
172
|
+
<svg viewBox="0 0 24 24" class="invite-spin"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg>
|
|
173
|
+
Starting...
|
|
174
|
+
</button>
|
|
175
|
+
</div>
|
|
176
|
+
` : `
|
|
177
|
+
<div class="invite-action-row">
|
|
178
|
+
<button class="invite-btn primary" id="invite-tunnel-start">Start Tunnel</button>
|
|
179
|
+
<button class="invite-alt-link" id="invite-mode-proxy">Use custom URL</button>
|
|
180
|
+
</div>
|
|
181
|
+
`}
|
|
182
|
+
` : `
|
|
183
|
+
<div class="invite-action-row">
|
|
184
|
+
<span style="font-size:10px;color:var(--t-dimmed)">cloudflared not available</span>
|
|
185
|
+
<button class="invite-alt-link" id="invite-mode-proxy">Use custom URL</button>
|
|
186
|
+
</div>
|
|
187
|
+
`}
|
|
188
|
+
`}
|
|
189
|
+
`}
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
<div class="invite-section">
|
|
193
|
+
<div class="invite-section-label">Invite Key</div>
|
|
194
|
+
${_keyMode === 'custom' ? `
|
|
195
|
+
<input type="text" class="invite-custom-input" id="invite-custom-pw"
|
|
196
|
+
placeholder="Enter a password (min 6 chars)" autocomplete="off" spellcheck="false">
|
|
197
|
+
` : ''}
|
|
198
|
+
${_revealedKey ? `
|
|
199
|
+
<div class="invite-key-box">
|
|
200
|
+
${escHtml(_revealedKey)}
|
|
201
|
+
<button class="invite-copy-icon" id="invite-copy-key" data-tooltip="Copy key"><svg viewBox="0 0 24 24"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>
|
|
202
|
+
</div>
|
|
203
|
+
<div class="invite-key-meta">
|
|
204
|
+
<span class="invite-warn">Copy now — won't be shown again</span>
|
|
205
|
+
<span class="invite-copied" id="invite-copied-fb">Copied</span>
|
|
206
|
+
${inviteUrl ? `<button class="invite-btn primary" id="invite-copy-url" style="margin-left:auto">Copy Link</button>` : ''}
|
|
207
|
+
</div>
|
|
208
|
+
` : `
|
|
209
|
+
<div class="invite-action-row">
|
|
210
|
+
${_data.hasKey ? `
|
|
211
|
+
<span class="invite-key-status">${_data.maskedKey}</span>
|
|
212
|
+
<button class="invite-btn primary" id="invite-generate">Rotate</button>
|
|
213
|
+
<button class="invite-btn danger" id="invite-revoke-key">Revoke</button>
|
|
214
|
+
` : `
|
|
215
|
+
<button class="invite-btn primary" id="invite-generate">Generate</button>
|
|
216
|
+
`}
|
|
217
|
+
<button class="invite-alt-link" id="${_keyMode === 'custom' ? 'invite-km-auto' : 'invite-km-custom'}">${_keyMode === 'custom' ? 'Use auto key' : 'Custom password'}</button>
|
|
218
|
+
</div>
|
|
219
|
+
`}
|
|
220
|
+
</div>
|
|
221
|
+
|
|
222
|
+
${_data.activeSessions > 0 ? `
|
|
223
|
+
<div class="invite-section">
|
|
224
|
+
<div class="invite-sessions-row">
|
|
225
|
+
<span class="invite-sessions-dot"></span>
|
|
226
|
+
<span>${_data.activeSessions} active session${_data.activeSessions !== 1 ? 's' : ''}</span>
|
|
227
|
+
<button class="invite-btn danger" id="invite-revoke-sessions" style="margin-left:auto">Revoke</button>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
` : ''}
|
|
231
|
+
|
|
232
|
+
${_data.hasKey ? `
|
|
233
|
+
<div class="invite-section">
|
|
234
|
+
<div class="invite-section-label">Guest access</div>
|
|
235
|
+
${renderPermToggle('memories', 'Memories')}
|
|
236
|
+
${renderPermToggle('cards', 'Memory Cards')}
|
|
237
|
+
${renderPermToggle('whiteboard', 'Whiteboard')}
|
|
238
|
+
${renderPermToggle('terminal', 'Terminal')}
|
|
239
|
+
${renderPermToggle('skills', 'Skills Studio')}
|
|
240
|
+
${renderPermToggle('browser', 'Browser')}
|
|
241
|
+
</div>
|
|
242
|
+
` : ''}
|
|
243
|
+
`;
|
|
244
|
+
|
|
245
|
+
wireEvents();
|
|
246
|
+
updateBadge();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function escHtml(s) {
|
|
250
|
+
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const PERM_ICONS = {
|
|
254
|
+
memories: '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M12 8v8"/><path d="M8 12h8"/></svg>',
|
|
255
|
+
cards: '<svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M9 21V9"/></svg>',
|
|
256
|
+
whiteboard: '<svg viewBox="0 0 24 24"><path d="M12 19l7-7 3 3-7 7-3-3z"/><path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"/><path d="M2 2l7.586 7.586"/><circle cx="11" cy="11" r="2"/></svg>',
|
|
257
|
+
terminal: '<svg viewBox="0 0 24 24"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>',
|
|
258
|
+
skills: '<svg viewBox="0 0 24 24"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>',
|
|
259
|
+
browser: '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>',
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
function renderPermToggle(key, label) {
|
|
263
|
+
const on = !!_permissions[key];
|
|
264
|
+
return `<div class="invite-perm-row" data-perm="${key}">
|
|
265
|
+
<div class="invite-perm-left">
|
|
266
|
+
<span class="invite-perm-icon">${PERM_ICONS[key] || ''}</span>
|
|
267
|
+
<span class="invite-perm-label">${label}</span>
|
|
268
|
+
</div>
|
|
269
|
+
<div class="invite-perm-toggle ${on ? 'on' : ''}"><div class="invite-perm-knob"></div></div>
|
|
270
|
+
</div>`;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ─── Event wiring ───────────────────────
|
|
274
|
+
|
|
275
|
+
function wireEvents() {
|
|
276
|
+
// URL mode pills
|
|
277
|
+
$('invite-mode-tunnel')?.addEventListener('click', async (e) => {
|
|
278
|
+
e.stopPropagation();
|
|
279
|
+
await saveInviteProxy({ useProxy: false, proxyUrl: _data.proxyConfig?.proxyUrl || '' });
|
|
280
|
+
_data.proxyConfig = { useProxy: false, proxyUrl: _data.proxyConfig?.proxyUrl || '' };
|
|
281
|
+
_revealedKey = null;
|
|
282
|
+
render();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
$('invite-mode-proxy')?.addEventListener('click', async (e) => {
|
|
286
|
+
e.stopPropagation();
|
|
287
|
+
await saveInviteProxy({ useProxy: true, proxyUrl: _data.proxyConfig?.proxyUrl || '' });
|
|
288
|
+
_data.proxyConfig = { useProxy: true, proxyUrl: _data.proxyConfig?.proxyUrl || '' };
|
|
289
|
+
_revealedKey = null;
|
|
290
|
+
render();
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// Copy base URL
|
|
294
|
+
$('invite-copy-base-url')?.addEventListener('click', (e) => {
|
|
295
|
+
e.stopPropagation();
|
|
296
|
+
const url = getBaseUrl();
|
|
297
|
+
if (url) copyText(url, 'invite-copied-url');
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// Tunnel start/stop
|
|
301
|
+
$('invite-tunnel-start')?.addEventListener('click', async (e) => {
|
|
302
|
+
e.stopPropagation();
|
|
303
|
+
try {
|
|
304
|
+
_tunnelStarting = true;
|
|
305
|
+
render();
|
|
306
|
+
await startTunnel();
|
|
307
|
+
pollForTunnelUrl();
|
|
308
|
+
} catch (err) {
|
|
309
|
+
console.error('[invite] Tunnel start failed:', err);
|
|
310
|
+
_tunnelStarting = false;
|
|
311
|
+
render();
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
$('invite-tunnel-stop')?.addEventListener('click', async (e) => {
|
|
316
|
+
e.stopPropagation();
|
|
317
|
+
try {
|
|
318
|
+
await stopTunnel();
|
|
319
|
+
_tunnelUrl = null;
|
|
320
|
+
_tunnelStarting = false;
|
|
321
|
+
if (_tunnelPollTimer) { clearInterval(_tunnelPollTimer); _tunnelPollTimer = null; }
|
|
322
|
+
render();
|
|
323
|
+
} catch (err) {
|
|
324
|
+
console.error('[invite] Tunnel stop failed:', err);
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// Proxy save
|
|
329
|
+
$('invite-proxy-save')?.addEventListener('click', async (e) => {
|
|
330
|
+
e.stopPropagation();
|
|
331
|
+
const url = $('invite-proxy-url')?.value.trim() || '';
|
|
332
|
+
await saveInviteProxy({ useProxy: true, proxyUrl: url });
|
|
333
|
+
_data.proxyConfig = { useProxy: true, proxyUrl: url };
|
|
334
|
+
_revealedKey = null;
|
|
335
|
+
render();
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// Key mode pills
|
|
339
|
+
$('invite-km-auto')?.addEventListener('click', (e) => {
|
|
340
|
+
e.stopPropagation();
|
|
341
|
+
_keyMode = 'auto';
|
|
342
|
+
_revealedKey = null;
|
|
343
|
+
render();
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
$('invite-km-custom')?.addEventListener('click', (e) => {
|
|
347
|
+
e.stopPropagation();
|
|
348
|
+
_keyMode = 'custom';
|
|
349
|
+
_revealedKey = null;
|
|
350
|
+
render();
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// Generate / rotate
|
|
354
|
+
$('invite-generate')?.addEventListener('click', async (e) => {
|
|
355
|
+
e.stopPropagation();
|
|
356
|
+
try {
|
|
357
|
+
let res;
|
|
358
|
+
if (_keyMode === 'custom') {
|
|
359
|
+
const pw = $('invite-custom-pw')?.value.trim();
|
|
360
|
+
if (!pw || pw.length < 6) {
|
|
361
|
+
$('invite-custom-pw')?.focus();
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
res = await generateInviteKey(pw);
|
|
365
|
+
} else {
|
|
366
|
+
res = await generateInviteKey();
|
|
367
|
+
}
|
|
368
|
+
_revealedKey = res.key;
|
|
369
|
+
_data.hasKey = true;
|
|
370
|
+
_data.maskedKey = '***' + res.key.slice(-8);
|
|
371
|
+
_data.activeSessions = 0;
|
|
372
|
+
render();
|
|
373
|
+
} catch (err) {
|
|
374
|
+
console.error('[invite] Generate failed:', err);
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// Copy key
|
|
379
|
+
$('invite-copy-key')?.addEventListener('click', (e) => {
|
|
380
|
+
e.stopPropagation();
|
|
381
|
+
if (_revealedKey) copyText(_revealedKey, 'invite-copied-fb');
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// Copy URL (with key as fragment)
|
|
385
|
+
$('invite-copy-url')?.addEventListener('click', (e) => {
|
|
386
|
+
e.stopPropagation();
|
|
387
|
+
const url = getInviteUrl();
|
|
388
|
+
if (url && _revealedKey) {
|
|
389
|
+
copyText(url + '#' + encodeURIComponent(_revealedKey), 'invite-copied-fb');
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// Revoke key
|
|
394
|
+
$('invite-revoke-key')?.addEventListener('click', async (e) => {
|
|
395
|
+
e.stopPropagation();
|
|
396
|
+
try {
|
|
397
|
+
await revokeInviteKey();
|
|
398
|
+
_data.hasKey = false;
|
|
399
|
+
_data.maskedKey = null;
|
|
400
|
+
_data.activeSessions = 0;
|
|
401
|
+
_revealedKey = null;
|
|
402
|
+
render();
|
|
403
|
+
} catch (err) {
|
|
404
|
+
console.error('[invite] Revoke key failed:', err);
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
// Revoke sessions
|
|
409
|
+
$('invite-revoke-sessions')?.addEventListener('click', async (e) => {
|
|
410
|
+
e.stopPropagation();
|
|
411
|
+
try {
|
|
412
|
+
await revokeAllInviteSessions();
|
|
413
|
+
_data.activeSessions = 0;
|
|
414
|
+
render();
|
|
415
|
+
} catch (err) {
|
|
416
|
+
console.error('[invite] Revoke sessions failed:', err);
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
// Stop clicks inside dropdown from closing it
|
|
421
|
+
$('invite-dropdown')?.addEventListener('click', (e) => e.stopPropagation());
|
|
422
|
+
|
|
423
|
+
// Stop input/button focus from closing
|
|
424
|
+
const proxyInput = $('invite-proxy-url');
|
|
425
|
+
if (proxyInput) {
|
|
426
|
+
proxyInput.addEventListener('click', (e) => e.stopPropagation());
|
|
427
|
+
proxyInput.addEventListener('keydown', (e) => {
|
|
428
|
+
if (e.key === 'Enter') {
|
|
429
|
+
e.preventDefault();
|
|
430
|
+
$('invite-proxy-save')?.click();
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const customInput = $('invite-custom-pw');
|
|
436
|
+
if (customInput) {
|
|
437
|
+
customInput.addEventListener('click', (e) => e.stopPropagation());
|
|
438
|
+
customInput.addEventListener('keydown', (e) => {
|
|
439
|
+
if (e.key === 'Enter') {
|
|
440
|
+
e.preventDefault();
|
|
441
|
+
$('invite-generate')?.click();
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
// Auto-focus the custom password input
|
|
445
|
+
setTimeout(() => customInput.focus(), 50);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Permission toggles
|
|
449
|
+
$('invite-dropdown')?.querySelectorAll('.invite-perm-row').forEach((row) => {
|
|
450
|
+
row.addEventListener('click', async (e) => {
|
|
451
|
+
e.stopPropagation();
|
|
452
|
+
const key = row.dataset.perm;
|
|
453
|
+
if (!key) return;
|
|
454
|
+
const newVal = !_permissions[key];
|
|
455
|
+
_permissions[key] = newVal;
|
|
456
|
+
// Optimistic toggle
|
|
457
|
+
const toggle = row.querySelector('.invite-perm-toggle');
|
|
458
|
+
if (toggle) toggle.classList.toggle('on', newVal);
|
|
459
|
+
try {
|
|
460
|
+
await saveInvitePermissions({ [key]: newVal });
|
|
461
|
+
} catch (err) {
|
|
462
|
+
// Revert on failure
|
|
463
|
+
_permissions[key] = !newVal;
|
|
464
|
+
if (toggle) toggle.classList.toggle('on', !newVal);
|
|
465
|
+
console.error('[invite] Permission save failed:', err);
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// ─── Badge ──────────────────────────────
|
|
472
|
+
|
|
473
|
+
function updateBadge() {
|
|
474
|
+
const badge = $('invite-session-count');
|
|
475
|
+
if (!badge) return;
|
|
476
|
+
if (_data.activeSessions > 0) {
|
|
477
|
+
badge.textContent = _data.activeSessions;
|
|
478
|
+
badge.style.display = '';
|
|
479
|
+
} else {
|
|
480
|
+
badge.style.display = 'none';
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// ─── Init ───────────────────────────────
|
|
485
|
+
|
|
486
|
+
export function initInvite() {
|
|
487
|
+
const btn = $('invite-btn');
|
|
488
|
+
if (!btn) return;
|
|
489
|
+
|
|
490
|
+
btn.addEventListener('click', (e) => {
|
|
491
|
+
e.stopPropagation();
|
|
492
|
+
toggleDropdown();
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// Close on outside click
|
|
496
|
+
document.addEventListener('click', (e) => {
|
|
497
|
+
if (_isOpen && !e.target.closest('#invite-overlay')) {
|
|
498
|
+
closeDropdown();
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
// Close when other dropdowns request exclusivity
|
|
503
|
+
on('panel:close-all-dropdowns', () => closeDropdown());
|
|
504
|
+
on('panel:close-all-dropdowns-except', (name) => {
|
|
505
|
+
if (name !== 'invite') closeDropdown();
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
// Hide Share button for guests (admin only)
|
|
509
|
+
on('session:info', ({ isGuest: guest }) => {
|
|
510
|
+
const overlay = $('invite-overlay');
|
|
511
|
+
if (overlay) overlay.style.display = guest ? 'none' : '';
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
// Load initial badge count
|
|
515
|
+
fetchInviteStatus().then((res) => {
|
|
516
|
+
_data = res;
|
|
517
|
+
updateBadge();
|
|
518
|
+
}).catch(() => {});
|
|
519
|
+
}
|