@lifeaitools/clauth 2.1.1 → 2.10.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/.clauth-skill/references/keys-guide.md +270 -270
- package/cli/api.js +238 -238
- package/cli/commands/install.js +413 -396
- package/cli/commands/login.test.js +49 -1
- package/cli/commands/ops-install.js +11 -10
- package/cli/commands/ops.test.js +270 -0
- package/cli/commands/scrub.test.js +70 -0
- package/cli/commands/serve.js +2613 -11219
- package/cli/commands/uninstall.js +164 -164
- package/cli/dashboard/dashboard.js +592 -0
- package/cli/dashboard/global.css +65 -0
- package/cli/dashboard/panel-element.js +79 -0
- package/cli/dashboard/panels/build-status-panel.css +12 -0
- package/cli/dashboard/panels/build-status-panel.js +98 -0
- package/cli/dashboard/panels/panel-registry.js +58 -0
- package/cli/dashboard/panels/services-panel.css +95 -0
- package/cli/dashboard/panels/services-panel.js +872 -0
- package/cli/dashboard/panels/surfaces-panel.css +59 -0
- package/cli/dashboard/panels/surfaces-panel.js +474 -0
- package/cli/dashboard/panels/toolbar-panel.css +42 -0
- package/cli/dashboard/panels/toolbar-panel.js +374 -0
- package/cli/dashboard/panels/tunnel-panel.css +61 -0
- package/cli/dashboard/panels/tunnel-panel.js +688 -0
- package/cli/dashboard/panels/webdav-panel.css +38 -0
- package/cli/dashboard/panels/webdav-panel.js +166 -0
- package/cli/fingerprint.test.js +95 -0
- package/cli/http/components/callagent-terminal-component.js +310 -0
- package/cli/http/components/dashboard-component.js +123 -0
- package/cli/http/components/inbox-component.js +41 -0
- package/cli/http/components/mcp-transport-component.js +275 -0
- package/cli/http/components/oauth-component.js +207 -0
- package/cli/http/components/service-crud-component.js +288 -0
- package/cli/http/components/supervisor-component.js +104 -0
- package/cli/http/components/system-component.js +184 -0
- package/cli/http/components/tunnel-component.js +508 -0
- package/cli/http/components/vault-auth-component.js +264 -0
- package/cli/http/components/watchdog-component.js +28 -0
- package/cli/http/request-utils.js +21 -0
- package/cli/http/servers/ops-server.js +335 -0
- package/cli/index.js +5 -91
- package/cli/lib/fs-git.test.js +467 -0
- package/cli/mcp/providers/call-agent-provider.js +30 -0
- package/cli/mcp/providers/clauth-core-provider.js +461 -0
- package/cli/mcp/providers/ops-provider.js +42 -0
- package/cli/mcp/providers/terminal-provider.js +81 -0
- package/cli/mcp/tool-registry.js +48 -0
- package/cli/mcp/tool-schemas.js +822 -0
- package/cli/mcp/tool-schemas.test.js +49 -0
- package/cli/ops/pm2-preflight.js +54 -0
- package/cli/recovery.js +6 -0
- package/cli/recovery.test.js +179 -0
- package/cli/services/agent-pool-instance.js +38 -0
- package/cli/services/claude-binary.js +29 -0
- package/cli/services/delegation-lane-instance.js +35 -0
- package/cli/services/delegation-lane-instance.test.js +37 -0
- package/cli/services/file-io.js +38 -0
- package/cli/services/file-io.test.js +10 -0
- package/cli/services/fs-mount.js +66 -0
- package/cli/services/fs-mount.test.js +14 -0
- package/cli/services/fs-upload-sessions.js +20 -0
- package/cli/services/fs-upload-sessions.test.js +14 -0
- package/cli/services/git-exec.js +37 -0
- package/cli/services/git-exec.test.js +41 -0
- package/cli/services/git-import-guard.js +28 -0
- package/cli/services/git-import-guard.test.js +18 -0
- package/cli/services/service-status.js +191 -0
- package/cli/services/service-status.test.js +39 -0
- package/cli/services/terminal-dispatch.js +135 -0
- package/cli/services/terminal-dispatch.test.js +9 -0
- package/cli/services/terminal-registry.js +39 -0
- package/cli/services/terminal-registry.test.js +31 -0
- package/cli/services/terminal-sessions.js +100 -0
- package/cli/services/terminal-sessions.test.js +26 -0
- package/cli/services/terminal-window.js +106 -0
- package/cli/services/vault-read.js +14 -0
- package/cli/services/vault-read.test.js +35 -0
- package/cli/supervisor-registry.js +116 -0
- package/cli/supervisor-registry.test.js +165 -0
- package/cli/supervisor-ui.test.js +230 -30
- package/cli/webdav-config.js +84 -0
- package/cli/webdav-config.test.js +79 -0
- package/cli/webdav-obscure.js +23 -0
- package/cli/webdav-obscure.test.js +41 -0
- package/cli/webdav-rclone-discovery.js +44 -0
- package/cli/webdav-service.js +30 -118
- package/cli/webdav-service.test.js +107 -0
- package/install.ps1 +102 -102
- package/install.sh +49 -49
- package/package.json +2 -2
- package/scripts/bootstrap.cjs +121 -121
- package/supabase/functions/auth-vault/index.ts +350 -350
- package/supabase/migrations/001_clauth_schema.sql +94 -94
- package/supabase/migrations/002_vault_helpers.sql +90 -90
- package/supabase/migrations/20260317_lockout.sql +26 -26
- package/cli/assets/codevelop/launcher-active.cmd.template +0 -20
- package/cli/assets/codevelop/launcher-static.cmd.template +0 -7
- package/cli/assets/codevelop/windows-terminal.profiles.json +0 -48
- package/cli/commands/codevelop.js +0 -1190
|
@@ -0,0 +1,688 @@
|
|
|
1
|
+
// cli/dashboard/panels/tunnel-panel.js
|
|
2
|
+
// The Tunnel panel, as a real Web Component: <clauth-tunnel-panel>. Owns its
|
|
3
|
+
// own Shadow DOM (tunnel bar + the full Cloudflare tunnel-setup wizard, both
|
|
4
|
+
// rendered inside this element -- the wizard used to be a separate top-level
|
|
5
|
+
// overlay in the served HTML; it's genuinely part of this panel's concern,
|
|
6
|
+
// so it now lives in this panel's own shadow tree). No other panel can see
|
|
7
|
+
// or be affected by anything in here, and this panel can't see anything
|
|
8
|
+
// outside itself except through the shared apiFetch/writeHeaders/BASE
|
|
9
|
+
// globals dashboard.js provides (a cross-cutting API client, not panel
|
|
10
|
+
// state -- sharing that is normal for any component library).
|
|
11
|
+
//
|
|
12
|
+
// State that used to be module-level `let` (tunnelPollTimer, wizStep,
|
|
13
|
+
// wizData, window._wizSelectedTunnel) is now instance state on `this` --
|
|
14
|
+
// the correct home for one panel instance's own data.
|
|
15
|
+
//
|
|
16
|
+
// Every onclick="globalFn()" became data-action="methodName" (or
|
|
17
|
+
// data-keydown for Enter-key submits), dispatched by the shared listener in
|
|
18
|
+
// ../panel-element.js. Per-item dynamic args (which tunnel was clicked,
|
|
19
|
+
// which copy target) move from inline call arguments to data-* attributes
|
|
20
|
+
// read off the clicked element -- the standard delegated-event pattern.
|
|
21
|
+
class ClauthTunnelPanel extends window.ClauthPanelElement {
|
|
22
|
+
static tagName = "clauth-tunnel-panel";
|
|
23
|
+
static cssHref = "/dashboard/panels/tunnel-panel.css";
|
|
24
|
+
|
|
25
|
+
template() {
|
|
26
|
+
return `
|
|
27
|
+
<section class="tunnel-panel">
|
|
28
|
+
<!-- not_configured -->
|
|
29
|
+
<div class="tunnel-state not-configured" style="display:none;align-items:center;gap:10px;width:100%;flex-wrap:wrap">
|
|
30
|
+
<div class="tunnel-dot off"></div>
|
|
31
|
+
<div class="tunnel-label"><strong>claude.ai MCP</strong> — No tunnel configured</div>
|
|
32
|
+
<button class="btn-tunnel-stop" data-action="runTunnelSetup">Setup Tunnel</button>
|
|
33
|
+
</div>
|
|
34
|
+
<!-- missing_cloudflared -->
|
|
35
|
+
<div class="tunnel-state missing-cloudflared" style="display:none;align-items:center;gap:10px;width:100%;flex-wrap:wrap">
|
|
36
|
+
<div class="tunnel-dot err"></div>
|
|
37
|
+
<div class="tunnel-label"><strong>claude.ai MCP</strong> — cloudflared not installed</div>
|
|
38
|
+
<a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/" target="_blank" style="color:#60a5fa;font-size:.8rem">Download cloudflared ↗</a>
|
|
39
|
+
<span class="tunnel-err" style="display:inline;font-size:.78rem;color:#94a3b8">Then run: clauth tunnel setup</span>
|
|
40
|
+
</div>
|
|
41
|
+
<!-- starting -->
|
|
42
|
+
<div class="tunnel-state starting" style="display:none;align-items:center;gap:10px;width:100%;flex-wrap:wrap">
|
|
43
|
+
<div class="tunnel-dot starting"></div>
|
|
44
|
+
<div class="tunnel-label"><strong>claude.ai MCP</strong> — Tunnel starting…</div>
|
|
45
|
+
</div>
|
|
46
|
+
<!-- live -->
|
|
47
|
+
<div class="tunnel-state live" style="display:none;align-items:center;gap:10px;width:100%;flex-wrap:wrap">
|
|
48
|
+
<div class="tunnel-dot on"></div>
|
|
49
|
+
<div class="tunnel-label"><strong>claude.ai MCP</strong> — <span class="tunnel-url"><a href="" target="_blank" id="tunnel-live-url"></a></span></div>
|
|
50
|
+
<button class="btn-check" style="padding:6px 12px;font-size:.8rem" data-action="testTunnel">Test</button>
|
|
51
|
+
<button class="btn-claude" data-action="openClaude">Connect claude.ai</button>
|
|
52
|
+
<button class="btn-tunnel-stop" data-action="stopTunnel">Stop</button>
|
|
53
|
+
</div>
|
|
54
|
+
<!-- error -->
|
|
55
|
+
<div class="tunnel-state error" style="display:none;align-items:center;gap:10px;width:100%;flex-wrap:wrap">
|
|
56
|
+
<div class="tunnel-dot err"></div>
|
|
57
|
+
<div class="tunnel-label"><strong>claude.ai MCP</strong> — Tunnel error — check cloudflared config</div>
|
|
58
|
+
<button class="btn-tunnel-stop" data-action="startTunnel">Retry</button>
|
|
59
|
+
</div>
|
|
60
|
+
<!-- not_started -->
|
|
61
|
+
<div class="tunnel-state not-started" style="display:flex;align-items:center;gap:10px;width:100%;flex-wrap:wrap">
|
|
62
|
+
<div class="tunnel-dot off"></div>
|
|
63
|
+
<div class="tunnel-label"><strong>claude.ai MCP</strong> — checking tunnel…</div>
|
|
64
|
+
<button class="btn-tunnel-stop" data-action="startTunnel">Start Tunnel</button>
|
|
65
|
+
</div>
|
|
66
|
+
</section>
|
|
67
|
+
|
|
68
|
+
<div class="wizard-panel" id="wizard-panel">
|
|
69
|
+
<div class="wizard-header">
|
|
70
|
+
<div class="tunnel-dot off" id="wiz-dot" style="width:10px;height:10px;border-radius:50%"></div>
|
|
71
|
+
<div class="wizard-title">claude.ai Tunnel Setup</div>
|
|
72
|
+
<button class="btn-cancel" data-action="closeSetupWizard">✕ Dismiss</button>
|
|
73
|
+
</div>
|
|
74
|
+
<div class="wizard-steps" id="wizard-steps"></div>
|
|
75
|
+
<div class="wizard-body" id="wizard-body"><p class="loading">Checking setup state…</p></div>
|
|
76
|
+
<div class="wizard-foot" id="wizard-foot"></div>
|
|
77
|
+
</div>
|
|
78
|
+
`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
mount() {
|
|
82
|
+
this.tunnelPollTimer = null;
|
|
83
|
+
this.wizStep = null;
|
|
84
|
+
this.wizData = {};
|
|
85
|
+
this.wizSelectedTunnel = null;
|
|
86
|
+
this.pollTunnel();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── Tunnel management ───────────────────────
|
|
90
|
+
async pollTunnel() {
|
|
91
|
+
if (this.tunnelPollTimer) clearInterval(this.tunnelPollTimer);
|
|
92
|
+
const r = await apiFetch("/tunnel");
|
|
93
|
+
if (r) this.renderTunnelPanel(r.status, r.url, r.error);
|
|
94
|
+
// Poll every 3s; slow to 10s once live
|
|
95
|
+
this.tunnelPollTimer = setInterval(async () => {
|
|
96
|
+
const r2 = await apiFetch("/tunnel");
|
|
97
|
+
if (!r2) return;
|
|
98
|
+
this.renderTunnelPanel(r2.status, r2.url, r2.error);
|
|
99
|
+
if (r2.status === "live") {
|
|
100
|
+
clearInterval(this.tunnelPollTimer);
|
|
101
|
+
this.tunnelPollTimer = setInterval(async () => {
|
|
102
|
+
const r3 = await apiFetch("/tunnel");
|
|
103
|
+
if (r3) this.renderTunnelPanel(r3.status, r3.url, r3.error);
|
|
104
|
+
}, 10000);
|
|
105
|
+
}
|
|
106
|
+
}, 3000);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
renderTunnelPanel(status, url, error) {
|
|
110
|
+
const panel = this.$(".tunnel-panel");
|
|
111
|
+
if (!panel) return;
|
|
112
|
+
panel.querySelectorAll(".tunnel-state").forEach(el => el.style.display = "none");
|
|
113
|
+
const cls = (status || "not-started").replace(/_/g, "-");
|
|
114
|
+
const active = panel.querySelector(".tunnel-state." + cls);
|
|
115
|
+
if (active) active.style.display = "flex";
|
|
116
|
+
if (status === "live" && url) {
|
|
117
|
+
const sseUrl = url.startsWith("http") ? url + "/sse" : url;
|
|
118
|
+
const link = panel.querySelector(".tunnel-state.live a");
|
|
119
|
+
if (link) { link.href = sseUrl; link.textContent = sseUrl; }
|
|
120
|
+
const liveUrlEl = this.$("#tunnel-live-url");
|
|
121
|
+
if (liveUrlEl) { liveUrlEl.href = sseUrl; liveUrlEl.textContent = sseUrl; }
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async toggleTunnel(action) {
|
|
126
|
+
try {
|
|
127
|
+
await fetch(BASE + "/tunnel", {
|
|
128
|
+
method: "POST",
|
|
129
|
+
headers: { "Content-Type": "application/json" },
|
|
130
|
+
body: JSON.stringify({ action })
|
|
131
|
+
});
|
|
132
|
+
setTimeout(() => this.pollTunnel(), action === "start" ? 2000 : 500);
|
|
133
|
+
} catch {}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async testTunnel() {
|
|
137
|
+
const liveState = this.$(".tunnel-panel .tunnel-state.live");
|
|
138
|
+
const btn = liveState ? liveState.querySelector(".btn-check") : null;
|
|
139
|
+
const labelEl = liveState ? liveState.querySelector(".tunnel-label") : null;
|
|
140
|
+
if (btn) { btn.disabled = true; btn.textContent = "Testing…"; }
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const r = await fetch(BASE + "/tunnel/test").then(r => r.json());
|
|
144
|
+
if (labelEl) {
|
|
145
|
+
if (r.ok) {
|
|
146
|
+
labelEl.innerHTML = '<strong>claude.ai MCP</strong> — <span style="color:#4ade80">PASS</span> ' +
|
|
147
|
+
r.latencyMs + 'ms · SSE ' + (r.sseReachable ? 'reachable' : 'unreachable') +
|
|
148
|
+
' · <span class="tunnel-url"><a href="' + r.sseUrl + '" target="_blank">' + r.sseUrl + '</a></span>';
|
|
149
|
+
} else {
|
|
150
|
+
labelEl.innerHTML = '<strong>claude.ai MCP</strong> — <span style="color:#f87171">FAIL</span> ' + (r.reason || "unknown error");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} catch (e) {
|
|
154
|
+
if (labelEl) labelEl.innerHTML = '<strong>claude.ai MCP</strong> — <span style="color:#f87171">FAIL</span> ' + e.message;
|
|
155
|
+
} finally {
|
|
156
|
+
if (btn) { btn.disabled = false; btn.textContent = "Test"; }
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Public method other panels call for cross-panel communication -- the
|
|
161
|
+
// correct boundary now that this panel is a real component: toolbar-
|
|
162
|
+
// panel.js's checkAll() calls this instead of reaching into DOM by id
|
|
163
|
+
// (the id it used to reach for, "tunnel-dot"/"tunnel-label", never
|
|
164
|
+
// existed even before this panel had its own shadow root -- only the
|
|
165
|
+
// CLASS names tunnel-dot/tunnel-label did, so this call site was already
|
|
166
|
+
// dead/broken, just never exercised in a way that surfaced the
|
|
167
|
+
// TypeError). Fixed here as the correct component-boundary call, not a
|
|
168
|
+
// DOM-id lookup into another panel's internals.
|
|
169
|
+
updateHealthCheckResult(result) {
|
|
170
|
+
const liveState = this.$(".tunnel-panel .tunnel-state.live");
|
|
171
|
+
const tDot = liveState ? liveState.querySelector(".tunnel-dot") : null;
|
|
172
|
+
const tLabel = liveState ? liveState.querySelector(".tunnel-label") : null;
|
|
173
|
+
if (!tDot || !tLabel) return;
|
|
174
|
+
if (result.ok) {
|
|
175
|
+
tDot.className = "tunnel-dot on";
|
|
176
|
+
tLabel.innerHTML = '<strong>claude.ai MCP</strong> — tunnel healthy';
|
|
177
|
+
} else if (result.running) {
|
|
178
|
+
tDot.className = "tunnel-dot starting";
|
|
179
|
+
tLabel.innerHTML = '<strong>claude.ai MCP</strong> — ' + (result.reason || "starting…");
|
|
180
|
+
} else {
|
|
181
|
+
tDot.className = "tunnel-dot err";
|
|
182
|
+
tLabel.innerHTML = '<strong>claude.ai MCP</strong> — ' + (result.reason || "not running");
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
openClaude() {
|
|
187
|
+
fetch(BASE + "/tunnel").then(r => r.json()).then(t => {
|
|
188
|
+
const sseUrl = t.sseUrl || (t.url && t.url.startsWith("http") ? t.url + "/sse" : null);
|
|
189
|
+
if (sseUrl) {
|
|
190
|
+
navigator.clipboard.writeText(sseUrl).then(() => {
|
|
191
|
+
const btn = this.$(".tunnel-panel .tunnel-state.live .btn-claude");
|
|
192
|
+
if (btn) {
|
|
193
|
+
btn.textContent = "SSE URL copied!";
|
|
194
|
+
setTimeout(() => { btn.textContent = "Connect claude.ai"; }, 2000);
|
|
195
|
+
}
|
|
196
|
+
}).catch(() => {});
|
|
197
|
+
window.open("https://claude.ai/settings/integrations", "_blank");
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ── Tunnel Setup Wizard ─────────────────────
|
|
203
|
+
async openSetupWizard() {
|
|
204
|
+
const panel = this.$("#wizard-panel");
|
|
205
|
+
panel.classList.add("open");
|
|
206
|
+
panel.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
207
|
+
this.wizStep = "loading";
|
|
208
|
+
this.renderWizBody('<p class="loading">Checking setup state…</p>', [], []);
|
|
209
|
+
|
|
210
|
+
const state = await apiFetch("/tunnel/setup-state");
|
|
211
|
+
if (!state) {
|
|
212
|
+
this.renderWizBody('<p class="wiz-desc" style="color:#f87171">Could not reach daemon.</p>', [], []);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (state.step === "ready") {
|
|
217
|
+
await apiFetch("/tunnel/start", { method: "POST" });
|
|
218
|
+
this.closeSetupWizard();
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
this.wizData = state;
|
|
223
|
+
|
|
224
|
+
if (state.step === "need_cf_token") this.wizShowCfToken();
|
|
225
|
+
else if (state.step === "pick_tunnel") this.wizShowPickTunnel(state.tunnels);
|
|
226
|
+
else if (state.step === "no_tunnels") this.wizShowCreateViaCfApi(state.accountId);
|
|
227
|
+
else this.wizShowInstallCheck(); // no CF token — needs cloudflared login
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
closeSetupWizard() {
|
|
231
|
+
this.$("#wizard-panel").classList.remove("open");
|
|
232
|
+
this.wizStep = null; this.wizData = {};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
renderWizBody(html, steps, footHtml) {
|
|
236
|
+
this.$("#wizard-body").innerHTML = html;
|
|
237
|
+
|
|
238
|
+
const stepLabels = ["CF Token", "Tunnel", "cloudflared", "MCP Setup", "Test"];
|
|
239
|
+
const stepsEl = this.$("#wizard-steps");
|
|
240
|
+
stepsEl.innerHTML = stepLabels.map((label, i) => {
|
|
241
|
+
const stepKeys = ["need_cf_token", "pick_tunnel", "check_cf", "mcp_setup", "test"];
|
|
242
|
+
const currentIdx = stepKeys.indexOf(this.wizStep);
|
|
243
|
+
let cls = "wstep";
|
|
244
|
+
if (i < currentIdx) cls += " done";
|
|
245
|
+
else if (i === currentIdx) cls += " active";
|
|
246
|
+
return `<div class="${cls}">${i < currentIdx ? "✓ " : ""}${label}</div>`;
|
|
247
|
+
}).join("");
|
|
248
|
+
|
|
249
|
+
this.$("#wizard-foot").innerHTML = footHtml.join("");
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Step: Enter CF API token
|
|
253
|
+
wizShowCfToken() {
|
|
254
|
+
this.wizStep = "need_cf_token";
|
|
255
|
+
this.renderWizBody(`
|
|
256
|
+
<div class="wiz-desc">A Cloudflare API token is needed to check for existing tunnels and configure new ones.</div>
|
|
257
|
+
<div class="wiz-label">Cloudflare API Token</div>
|
|
258
|
+
<input class="wiz-input" id="wiz-cf-token-input" type="password" placeholder="Paste your CF API token…" autocomplete="off" spellcheck="false" data-keydown="onCfTokenKeydown">
|
|
259
|
+
<div style="margin-top:8px">
|
|
260
|
+
<a class="wiz-link" href="https://dash.cloudflare.com/profile/api-tokens" target="_blank">↗ Create token at Cloudflare Dashboard</a>
|
|
261
|
+
<span style="color:#475569;font-size:.78rem;margin-left:8px">(needs Cloudflare Tunnel: Edit permission)</span>
|
|
262
|
+
</div>
|
|
263
|
+
<div class="wiz-msg fail" id="wiz-cf-err" style="display:none;margin-top:8px"></div>
|
|
264
|
+
`, [], [
|
|
265
|
+
`<button class="btn-wiz-primary" id="wiz-cf-btn" data-action="wizSubmitCfToken">Verify & Save Token</button>`,
|
|
266
|
+
`<button class="btn-wiz-secondary" data-action="closeSetupWizard">Postpone</button>`,
|
|
267
|
+
`<span class="wiz-msg" id="wiz-cf-msg"></span>`
|
|
268
|
+
]);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
onCfTokenKeydown(e) {
|
|
272
|
+
if (e.key === "Enter") this.wizSubmitCfToken();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async wizSubmitCfToken() {
|
|
276
|
+
const input = this.$("#wiz-cf-token-input");
|
|
277
|
+
const btn = this.$("#wiz-cf-btn");
|
|
278
|
+
const errEl = this.$("#wiz-cf-err");
|
|
279
|
+
const token = input.value.trim();
|
|
280
|
+
if (!token) return;
|
|
281
|
+
|
|
282
|
+
btn.disabled = true; btn.textContent = "Verifying…";
|
|
283
|
+
if (errEl) errEl.style.display = "none";
|
|
284
|
+
|
|
285
|
+
const r = await fetch(BASE + "/tunnel/setup/cf-token", {
|
|
286
|
+
method: "POST",
|
|
287
|
+
headers: writeHeaders({ "Content-Type": "application/json" }),
|
|
288
|
+
body: JSON.stringify({ token }),
|
|
289
|
+
}).then(r => r.json()).catch(() => null);
|
|
290
|
+
|
|
291
|
+
btn.disabled = false; btn.textContent = "Verify & Save Token";
|
|
292
|
+
|
|
293
|
+
if (!r || r.error) {
|
|
294
|
+
if (errEl) { errEl.textContent = r?.error || "Request failed"; errEl.style.display = "block"; }
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
this.wizData.accountId = r.accountId;
|
|
299
|
+
const tunnelState = await apiFetch("/tunnel/setup-state");
|
|
300
|
+
if (tunnelState?.step === "pick_tunnel") this.wizShowPickTunnel(tunnelState.tunnels);
|
|
301
|
+
else this.wizShowInstallCheck();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Step: Pick existing tunnel
|
|
305
|
+
wizShowPickTunnel(tunnels) {
|
|
306
|
+
this.wizStep = "pick_tunnel";
|
|
307
|
+
|
|
308
|
+
const listHtml = tunnels.map(t => `
|
|
309
|
+
<div class="wiz-tunnel-item" id="wti-${t.id}" data-action="wizSelectTunnel" data-tunnel-id="${t.id}" data-tunnel-name="${t.name}">
|
|
310
|
+
<div style="flex:1">
|
|
311
|
+
<div class="wiz-tunnel-name">${t.name}</div>
|
|
312
|
+
<div class="wiz-tunnel-id">${t.id}</div>
|
|
313
|
+
</div>
|
|
314
|
+
<div class="wiz-tunnel-status">${t.status || "active"}</div>
|
|
315
|
+
</div>
|
|
316
|
+
`).join("");
|
|
317
|
+
|
|
318
|
+
this.renderWizBody(`
|
|
319
|
+
<div class="wiz-desc">Found ${tunnels.length} existing Cloudflare tunnel${tunnels.length !== 1 ? "s" : ""}. Select one to use, or create a new one.</div>
|
|
320
|
+
<div class="wiz-tunnel-list">${listHtml}</div>
|
|
321
|
+
<div style="margin-top:12px">
|
|
322
|
+
<div class="wiz-label">Public hostname for selected tunnel (e.g. clauth.yourdomain.com)</div>
|
|
323
|
+
<input class="wiz-input" id="wiz-hostname-input" type="text" placeholder="clauth.yourdomain.com" spellcheck="false" autocomplete="off">
|
|
324
|
+
</div>
|
|
325
|
+
<div class="wiz-msg fail" id="wiz-pick-err" style="display:none;margin-top:8px"></div>
|
|
326
|
+
`, [], [
|
|
327
|
+
`<button class="btn-wiz-primary" id="wiz-pick-btn" data-action="wizSaveTunnel">Use Selected Tunnel</button>`,
|
|
328
|
+
`<button class="btn-wiz-secondary" data-action="wizShowInstallCheck">Create New Tunnel Instead</button>`,
|
|
329
|
+
`<button class="btn-wiz-secondary" data-action="closeSetupWizard">Postpone</button>`
|
|
330
|
+
]);
|
|
331
|
+
|
|
332
|
+
this.wizSelectedTunnel = null;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
wizSelectTunnel(e, el) {
|
|
336
|
+
this.$$(".wiz-tunnel-item").forEach(item => item.classList.remove("selected"));
|
|
337
|
+
el.classList.add("selected");
|
|
338
|
+
this.wizSelectedTunnel = { id: el.dataset.tunnelId, name: el.dataset.tunnelName };
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async wizSaveTunnel() {
|
|
342
|
+
const sel = this.wizSelectedTunnel;
|
|
343
|
+
const hostname = this.$("#wiz-hostname-input")?.value.trim();
|
|
344
|
+
const errEl = this.$("#wiz-pick-err");
|
|
345
|
+
const btn = this.$("#wiz-pick-btn");
|
|
346
|
+
|
|
347
|
+
if (!sel) { if (errEl) { errEl.textContent = "Select a tunnel first"; errEl.style.display = "block"; } return; }
|
|
348
|
+
if (!hostname) { if (errEl) { errEl.textContent = "Enter a public hostname"; errEl.style.display = "block"; } return; }
|
|
349
|
+
|
|
350
|
+
btn.disabled = true; btn.textContent = "Saving…";
|
|
351
|
+
|
|
352
|
+
const r = await fetch(BASE + "/tunnel/setup/cf-save", {
|
|
353
|
+
method: "POST",
|
|
354
|
+
headers: { "Content-Type": "application/json" },
|
|
355
|
+
body: JSON.stringify({ tunnelId: sel.id, tunnelName: sel.name, hostname }),
|
|
356
|
+
}).then(r => r.json()).catch(() => null);
|
|
357
|
+
|
|
358
|
+
btn.disabled = false; btn.textContent = "Use Selected Tunnel";
|
|
359
|
+
|
|
360
|
+
if (!r?.ok) {
|
|
361
|
+
if (errEl) { errEl.textContent = r?.error || "Save failed"; errEl.style.display = "block"; }
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
await apiFetch("/tunnel/start", { method: "POST" });
|
|
366
|
+
this.wizShowMcpSetup(hostname);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Step: Create tunnel via CF API (CF token already in vault — no cloudflared login needed)
|
|
370
|
+
wizShowCreateViaCfApi(accountId) {
|
|
371
|
+
this.wizStep = "pick_tunnel";
|
|
372
|
+
this.wizData.accountId = accountId;
|
|
373
|
+
this.renderWizBody(`
|
|
374
|
+
<div class="wiz-desc">No existing tunnels found. Create a new one using your Cloudflare API token.</div>
|
|
375
|
+
<div class="wiz-label">Tunnel name</div>
|
|
376
|
+
<input class="wiz-input" id="wiz-api-tname" type="text" value="clauth" spellcheck="false">
|
|
377
|
+
<div class="wiz-label" style="margin-top:10px">Public hostname (e.g. clauth.yourdomain.com)</div>
|
|
378
|
+
<input class="wiz-input" id="wiz-api-hostname" type="text" placeholder="clauth.yourdomain.com" spellcheck="false">
|
|
379
|
+
<div class="wiz-msg fail" id="wiz-api-err" style="display:none;margin-top:8px"></div>
|
|
380
|
+
`, [], [
|
|
381
|
+
`<button class="btn-wiz-primary" id="wiz-api-btn" data-action="wizRunCreateViaCfApi">Create Tunnel</button>`,
|
|
382
|
+
`<button class="btn-wiz-secondary" data-action="closeSetupWizard">Postpone</button>`,
|
|
383
|
+
`<span class="wiz-msg" id="wiz-api-msg"></span>`
|
|
384
|
+
]);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
async wizRunCreateViaCfApi() {
|
|
388
|
+
const name = this.$("#wiz-api-tname")?.value.trim();
|
|
389
|
+
const hostname = this.$("#wiz-api-hostname")?.value.trim();
|
|
390
|
+
const btn = this.$("#wiz-api-btn");
|
|
391
|
+
const err = this.$("#wiz-api-err");
|
|
392
|
+
const msg = this.$("#wiz-api-msg");
|
|
393
|
+
if (!name || !hostname) { if (err) { err.textContent = "Name and hostname required"; err.style.display = "block"; } return; }
|
|
394
|
+
btn.disabled = true; btn.textContent = "Creating…"; if (err) err.style.display = "none";
|
|
395
|
+
if (msg) msg.textContent = "Calling Cloudflare API…";
|
|
396
|
+
const r = await fetch(BASE + "/tunnel/setup/cf-create-api", {
|
|
397
|
+
method: "POST",
|
|
398
|
+
headers: { "Content-Type": "application/json" },
|
|
399
|
+
body: JSON.stringify({ name, hostname, accountId: this.wizData.accountId }),
|
|
400
|
+
}).then(r => r.json()).catch(() => null);
|
|
401
|
+
btn.disabled = false; btn.textContent = "Create Tunnel";
|
|
402
|
+
if (!r?.ok) {
|
|
403
|
+
if (err) { err.textContent = r?.error || "Creation failed"; err.style.display = "block"; }
|
|
404
|
+
if (msg) msg.textContent = "";
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
await apiFetch("/tunnel/start", { method: "POST" });
|
|
408
|
+
this.wizShowMcpSetup(hostname);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Step: cloudflared install check (for new tunnel creation flow)
|
|
412
|
+
async wizShowInstallCheck() {
|
|
413
|
+
this.wizStep = "check_cf";
|
|
414
|
+
this.renderWizBody('<p class="loading">Checking cloudflared…</p>', [], []);
|
|
415
|
+
|
|
416
|
+
const r = await apiFetch("/tunnel/setup/check-cloudflared");
|
|
417
|
+
|
|
418
|
+
if (r?.installed) {
|
|
419
|
+
this.wizShowCfLogin();
|
|
420
|
+
} else {
|
|
421
|
+
this.renderWizBody(`
|
|
422
|
+
<div class="wiz-desc">cloudflared is required to create and run Cloudflare tunnels. It is not currently installed.</div>
|
|
423
|
+
<div style="display:flex;gap:12px;margin-top:12px;flex-wrap:wrap">
|
|
424
|
+
<a class="btn-wiz-secondary" href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/" target="_blank" style="text-decoration:none">↗ Download cloudflared</a>
|
|
425
|
+
<span style="color:#475569;font-size:.8rem;align-self:center">or: <code style="color:#60a5fa">winget install Cloudflare.cloudflared</code></span>
|
|
426
|
+
</div>
|
|
427
|
+
<div class="wiz-desc" style="margin-top:12px;font-size:.78rem;color:#475569">After installing, click "Check Again".</div>
|
|
428
|
+
`, [], [
|
|
429
|
+
`<button class="btn-wiz-primary" data-action="wizShowInstallCheck">Check Again</button>`,
|
|
430
|
+
`<button class="btn-wiz-secondary" data-action="closeSetupWizard">Postpone</button>`
|
|
431
|
+
]);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Step: cloudflared login (Cloudflare auth via browser)
|
|
436
|
+
wizShowCfLogin() {
|
|
437
|
+
this.wizStep = "check_cf";
|
|
438
|
+
this.renderWizBody(`
|
|
439
|
+
<div class="wiz-desc">Authenticate cloudflared with your Cloudflare account. This opens a browser window.</div>
|
|
440
|
+
<div class="wiz-log" id="wiz-login-log" style="display:none"></div>
|
|
441
|
+
`, [], [
|
|
442
|
+
`<button class="btn-wiz-primary" id="wiz-login-btn" data-action="wizRunCfLogin">Open Browser to Authenticate</button>`,
|
|
443
|
+
`<button class="btn-wiz-secondary" data-action="closeSetupWizard">Postpone</button>`
|
|
444
|
+
]);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
async wizRunCfLogin() {
|
|
448
|
+
const btn = this.$("#wiz-login-btn");
|
|
449
|
+
const log = this.$("#wiz-login-log");
|
|
450
|
+
btn.disabled = true; btn.textContent = "Authenticating…";
|
|
451
|
+
log.style.display = "block";
|
|
452
|
+
|
|
453
|
+
try {
|
|
454
|
+
const resp = await fetch(BASE + "/tunnel/setup/cf-login", { method: "POST" });
|
|
455
|
+
const reader = resp.body.getReader();
|
|
456
|
+
const dec = new TextDecoder();
|
|
457
|
+
let buf = "";
|
|
458
|
+
while (true) {
|
|
459
|
+
const { done, value } = await reader.read();
|
|
460
|
+
if (done) break;
|
|
461
|
+
buf += dec.decode(value, { stream: true });
|
|
462
|
+
const lines = buf.split("\n");
|
|
463
|
+
buf = lines.pop();
|
|
464
|
+
for (const line of lines) {
|
|
465
|
+
if (!line.startsWith("data:")) continue;
|
|
466
|
+
try {
|
|
467
|
+
const d = JSON.parse(line.slice(5).trim());
|
|
468
|
+
if (d.line !== undefined) { log.textContent += d.line + "\n"; log.scrollTop = log.scrollHeight; }
|
|
469
|
+
if (d.done) {
|
|
470
|
+
if (d.code === 0) this.wizShowCreateTunnel();
|
|
471
|
+
else { btn.disabled = false; btn.textContent = "Retry"; }
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
} catch {}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
} catch (e) {
|
|
478
|
+
log.textContent += "Error: " + e.message;
|
|
479
|
+
btn.disabled = false; btn.textContent = "Retry";
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Step: Create new tunnel
|
|
484
|
+
wizShowCreateTunnel() {
|
|
485
|
+
this.wizStep = "check_cf";
|
|
486
|
+
this.renderWizBody(`
|
|
487
|
+
<div class="wiz-desc">Create a new named Cloudflare tunnel. Give it a name and a public hostname.</div>
|
|
488
|
+
<div class="wiz-label">Tunnel name (e.g. clauth)</div>
|
|
489
|
+
<input class="wiz-input" id="wiz-tname" type="text" value="clauth" spellcheck="false">
|
|
490
|
+
<div class="wiz-label" style="margin-top:10px">Public hostname (e.g. clauth.yourdomain.com)</div>
|
|
491
|
+
<input class="wiz-input" id="wiz-thostname" type="text" placeholder="clauth.yourdomain.com" spellcheck="false">
|
|
492
|
+
<div class="wiz-log" id="wiz-create-log" style="display:none;margin-top:10px"></div>
|
|
493
|
+
<div class="wiz-msg fail" id="wiz-create-err" style="display:none;margin-top:8px"></div>
|
|
494
|
+
`, [], [
|
|
495
|
+
`<button class="btn-wiz-primary" id="wiz-create-btn" data-action="wizRunCreateTunnel">Create Tunnel</button>`,
|
|
496
|
+
`<button class="btn-wiz-secondary" data-action="closeSetupWizard">Postpone</button>`
|
|
497
|
+
]);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
async wizRunCreateTunnel() {
|
|
501
|
+
const name = this.$("#wiz-tname")?.value.trim();
|
|
502
|
+
const hostname = this.$("#wiz-thostname")?.value.trim();
|
|
503
|
+
const btn = this.$("#wiz-create-btn");
|
|
504
|
+
const log = this.$("#wiz-create-log");
|
|
505
|
+
const err = this.$("#wiz-create-err");
|
|
506
|
+
|
|
507
|
+
if (!name || !hostname) { if (err) { err.textContent = "Name and hostname required"; err.style.display = "block"; } return; }
|
|
508
|
+
|
|
509
|
+
btn.disabled = true; btn.textContent = "Creating…";
|
|
510
|
+
log.style.display = "block"; err.style.display = "none";
|
|
511
|
+
|
|
512
|
+
try {
|
|
513
|
+
const resp = await fetch(BASE + "/tunnel/setup/cf-create", {
|
|
514
|
+
method: "POST",
|
|
515
|
+
headers: { "Content-Type": "application/json" },
|
|
516
|
+
body: JSON.stringify({ name, hostname }),
|
|
517
|
+
});
|
|
518
|
+
const reader = resp.body.getReader();
|
|
519
|
+
const dec = new TextDecoder();
|
|
520
|
+
let buf = "";
|
|
521
|
+
while (true) {
|
|
522
|
+
const { done, value } = await reader.read();
|
|
523
|
+
if (done) break;
|
|
524
|
+
buf += dec.decode(value, { stream: true });
|
|
525
|
+
const lines = buf.split("\n");
|
|
526
|
+
buf = lines.pop();
|
|
527
|
+
for (const line of lines) {
|
|
528
|
+
if (!line.startsWith("data:")) continue;
|
|
529
|
+
try {
|
|
530
|
+
const d = JSON.parse(line.slice(5).trim());
|
|
531
|
+
if (d.line !== undefined) { log.textContent += d.line + "\n"; log.scrollTop = log.scrollHeight; }
|
|
532
|
+
if (d.done && d.hostname) {
|
|
533
|
+
await apiFetch("/tunnel/start", { method: "POST" });
|
|
534
|
+
this.wizShowMcpSetup(d.hostname);
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
if (d.error) { err.textContent = d.error; err.style.display = "block"; btn.disabled = false; btn.textContent = "Retry"; return; }
|
|
538
|
+
} catch {}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
} catch (e) {
|
|
542
|
+
if (err) { err.textContent = e.message; err.style.display = "block"; }
|
|
543
|
+
btn.disabled = false; btn.textContent = "Retry";
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Step: MCP setup in claude.ai
|
|
548
|
+
wizShowMcpSetup(hostname) {
|
|
549
|
+
this.wizStep = "mcp_setup";
|
|
550
|
+
const base = `https://${hostname}`;
|
|
551
|
+
|
|
552
|
+
this.renderWizBody(`
|
|
553
|
+
<div class="wiz-desc">Tunnel is live. Add connectors in claude.ai → Integrations.</div>
|
|
554
|
+
<div style="display:flex;flex-direction:column;gap:8px;margin-top:10px">
|
|
555
|
+
<div class="mcp-row">
|
|
556
|
+
<span class="mcp-label">Clauth</span>
|
|
557
|
+
<span class="mcp-val" id="wiz-mcp-url">${base}/clauth</span>
|
|
558
|
+
<button class="mcp-copy" data-action="wizCopy" data-copy-target="wiz-mcp-url">copy</button>
|
|
559
|
+
</div>
|
|
560
|
+
<div class="mcp-row">
|
|
561
|
+
<span class="mcp-label">GWS</span>
|
|
562
|
+
<span class="mcp-val" id="wiz-mcp-gws">${base}/gws</span>
|
|
563
|
+
<button class="mcp-copy" data-action="wizCopy" data-copy-target="wiz-mcp-gws">copy</button>
|
|
564
|
+
</div>
|
|
565
|
+
<div class="mcp-row">
|
|
566
|
+
<span class="mcp-label">FS</span>
|
|
567
|
+
<span class="mcp-val" id="wiz-mcp-fs">${base}/fs</span>
|
|
568
|
+
<button class="mcp-copy" data-action="wizCopy" data-copy-target="wiz-mcp-fs">copy</button>
|
|
569
|
+
</div>
|
|
570
|
+
</div>
|
|
571
|
+
<div style="margin-top:10px;font-size:.78rem;color:#64748b">
|
|
572
|
+
MCP endpoints for direct servers (Regen Media, Web Research) are stored as <code>mcp-*</code> services in the vault.
|
|
573
|
+
</div>
|
|
574
|
+
`, [], [
|
|
575
|
+
`<button class="btn-wiz-primary" data-action="wizOpenClaudeAndTest">I've Added It — Test Now</button>`,
|
|
576
|
+
`<button class="btn-wiz-secondary" data-action="wizShowTest">Skip Test</button>`,
|
|
577
|
+
`<button class="btn-wiz-secondary" data-action="closeSetupWizard">Done</button>`
|
|
578
|
+
]);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
wizOpenClaudeAndTest() {
|
|
582
|
+
window.open("https://claude.ai/settings/integrations", "_blank");
|
|
583
|
+
this.wizShowTest();
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
async wizRollCreds() {
|
|
587
|
+
if (!confirm("Invalidate all OAuth tokens? Claude.ai connectors will need to re-authenticate.")) return;
|
|
588
|
+
try {
|
|
589
|
+
const resp = await apiFetch("/roll-mcp-creds", { method: "POST" });
|
|
590
|
+
if (resp?.tokens_invalidated) {
|
|
591
|
+
alert("All OAuth clients and tokens invalidated. Claude.ai will re-register automatically on next connection.");
|
|
592
|
+
}
|
|
593
|
+
} catch (e) { alert("Failed to roll credentials: " + e.message); }
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
wizCopy(e, btn) {
|
|
597
|
+
const elId = btn.dataset.copyTarget;
|
|
598
|
+
const val = this.$("#" + elId)?.textContent?.trim();
|
|
599
|
+
if (!val) return;
|
|
600
|
+
navigator.clipboard.writeText(val).then(() => {
|
|
601
|
+
const orig = btn.textContent;
|
|
602
|
+
btn.textContent = "✓"; btn.classList.add("ok");
|
|
603
|
+
setTimeout(() => { btn.textContent = orig; btn.classList.remove("ok"); }, 1500);
|
|
604
|
+
}).catch(() => {});
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// Step: Test — verify MCP is working
|
|
608
|
+
wizShowTest() {
|
|
609
|
+
this.wizStep = "test";
|
|
610
|
+
this.renderWizBody(`
|
|
611
|
+
<div class="wiz-desc">In a new claude.ai chat, ask Claude to run this MCP tool call to verify the connection:</div>
|
|
612
|
+
<div style="background:#030712;border:1px solid #1e293b;border-radius:6px;padding:12px;margin:10px 0;font-family:'Courier New',monospace;font-size:.82rem;color:#6ee7b7;user-select:all">
|
|
613
|
+
Use the clauth MCP tool: GET /ping — what is the response?
|
|
614
|
+
</div>
|
|
615
|
+
<div class="wiz-desc">Claude should report back: <code style="color:#4ade80">{"status":"ok","version":"..."}</code></div>
|
|
616
|
+
<div class="wiz-test-result" id="wiz-test-result"></div>
|
|
617
|
+
`, [], [
|
|
618
|
+
`<button class="btn-wiz-primary" data-action="wizMarkTestDone">✓ It Worked — Done</button>`,
|
|
619
|
+
`<button class="btn-wiz-secondary" data-action="closeSetupWizard">Close</button>`
|
|
620
|
+
]);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
wizMarkTestDone() {
|
|
624
|
+
const r = this.$("#wiz-test-result");
|
|
625
|
+
r.className = "wiz-test-result ok"; r.style.display = "block";
|
|
626
|
+
r.textContent = "✓ Tunnel and MCP are working. claude.ai can now access your vault.";
|
|
627
|
+
this.$("#wizard-foot").innerHTML = `<button class="btn-wiz-primary" data-action="closeSetupWizard">Close Setup</button>`;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
get actions() {
|
|
631
|
+
return {
|
|
632
|
+
runTunnelSetup: () => this.openSetupWizard(),
|
|
633
|
+
startTunnel: () => this.toggleTunnel("start"),
|
|
634
|
+
stopTunnel: () => this.toggleTunnel("stop"),
|
|
635
|
+
testTunnel: () => this.testTunnel(),
|
|
636
|
+
openClaude: () => this.openClaude(),
|
|
637
|
+
closeSetupWizard: () => this.closeSetupWizard(),
|
|
638
|
+
onCfTokenKeydown: (e) => this.onCfTokenKeydown(e),
|
|
639
|
+
// Dispatches through window.wizSubmitCfToken (the bridge below), NOT
|
|
640
|
+
// this.wizSubmitCfToken() directly -- installWriteAccessGuards()
|
|
641
|
+
// wraps the GLOBAL name with the write-token acquire-and-retry gate.
|
|
642
|
+
// Calling the instance method directly would silently bypass that
|
|
643
|
+
// gate, restoring the exact write-lock defect this mechanism exists
|
|
644
|
+
// to prevent.
|
|
645
|
+
wizSubmitCfToken: () => window.wizSubmitCfToken(),
|
|
646
|
+
wizSelectTunnel: (e, el) => this.wizSelectTunnel(e, el),
|
|
647
|
+
wizSaveTunnel: () => this.wizSaveTunnel(),
|
|
648
|
+
wizShowInstallCheck: () => this.wizShowInstallCheck(),
|
|
649
|
+
wizRunCreateViaCfApi: () => this.wizRunCreateViaCfApi(),
|
|
650
|
+
wizRunCfLogin: () => this.wizRunCfLogin(),
|
|
651
|
+
wizRunCreateTunnel: () => this.wizRunCreateTunnel(),
|
|
652
|
+
wizOpenClaudeAndTest: () => this.wizOpenClaudeAndTest(),
|
|
653
|
+
wizRollCreds: () => this.wizRollCreds(),
|
|
654
|
+
wizCopy: (e, btn) => this.wizCopy(e, btn),
|
|
655
|
+
wizShowTest: () => this.wizShowTest(),
|
|
656
|
+
wizMarkTestDone: () => this.wizMarkTestDone(),
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
customElements.define(ClauthTunnelPanel.tagName, ClauthTunnelPanel);
|
|
661
|
+
|
|
662
|
+
// Write-guard bridge: installWriteAccessGuards() (dashboard.js) wraps every
|
|
663
|
+
// name in WRITE_ACTIONS by reassigning window[name] to a guarded version --
|
|
664
|
+
// it can only do that for a real global function, not a class instance
|
|
665
|
+
// method. This thin global delegates to the real implementation on the live
|
|
666
|
+
// panel instance; installWriteAccessGuards wraps THIS name, and the
|
|
667
|
+
// component's own action dispatcher calls window.wizSubmitCfToken() (see
|
|
668
|
+
// the `actions` getter above), never this.wizSubmitCfToken() directly, so
|
|
669
|
+
// the guard is never bypassed.
|
|
670
|
+
window.wizSubmitCfToken = () => {
|
|
671
|
+
const el = document.querySelector(ClauthTunnelPanel.tagName);
|
|
672
|
+
return el?.wizSubmitCfToken();
|
|
673
|
+
};
|
|
674
|
+
|
|
675
|
+
// Panel interface registration — see ./panel-registry.js. mount() runs
|
|
676
|
+
// connectedCallback() only builds the shadow DOM (safe -- no external
|
|
677
|
+
// dependency); it deliberately does NOT call mount() itself, since it fires
|
|
678
|
+
// during HTML parsing, before dashboard.js (and apiFetch) has loaded. The
|
|
679
|
+
// registry's mountAll() runs after every script has loaded, so THIS is the
|
|
680
|
+
// first safe point to call the real mount() -- which initializes instance
|
|
681
|
+
// state and starts polling.
|
|
682
|
+
window.ClauthPanelRegistry.register({
|
|
683
|
+
id: "tunnel",
|
|
684
|
+
mount: () => {
|
|
685
|
+
const el = document.querySelector(ClauthTunnelPanel.tagName);
|
|
686
|
+
if (el) el.mount();
|
|
687
|
+
},
|
|
688
|
+
});
|