@rubytech/create-maxy 1.0.622 → 1.0.623
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/package.json +1 -1
- package/payload/platform/plugins/cloudflare/PLUGIN.md +28 -2
- package/payload/platform/plugins/cloudflare/mcp/dist/index.js +74 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.d.ts +90 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.d.ts.map +1 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.js +550 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.js.map +1 -0
- package/payload/platform/plugins/cloudflare/skills/setup-tunnel/SKILL.md +4 -55
- package/payload/platform/plugins/docs/references/cloudflare.md +18 -11
- package/payload/platform/templates/agents/admin/IDENTITY.md +8 -0
- package/payload/server/server.js +296 -189
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare tunnel setup — deterministic orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* Task 547: the tunnel setup flow (login → create → enable → verify) has a
|
|
5
|
+
* deterministic shape. Every branch is driven by a structured signal —
|
|
6
|
+
* either `tunnel-status`'s `unhealthyReason` enum, or the operator's
|
|
7
|
+
* literal input (domain name, subdomain names, "done"). Before Task 547
|
|
8
|
+
* the flow was driven by the LLM reading SKILL.md prose and picking the
|
|
9
|
+
* next `tunnel-*` tool; that allowed a dozen+ recurrences where the LLM
|
|
10
|
+
* forked, dispatched Playwright to the Cloudflare dashboard, or fabricated
|
|
11
|
+
* a `cp cert.pem` workaround. None of those deviations are reachable here
|
|
12
|
+
* because the LLM no longer chooses — this module does.
|
|
13
|
+
*
|
|
14
|
+
* Contract:
|
|
15
|
+
* - `runOrchestrator(input)` is the single entry point. One call advances
|
|
16
|
+
* one deterministic step. The LLM's only job is to pass the operator's
|
|
17
|
+
* literal input through (domain, "done", etc.) and relay the structured
|
|
18
|
+
* result verbatim.
|
|
19
|
+
* - Every transition emits a `[cloudflare:setup-run:phase-transition]` log
|
|
20
|
+
* line. Every internal sub-tool call emits
|
|
21
|
+
* `[cloudflare:setup-run:subtool-call]`. Every terminal state emits
|
|
22
|
+
* `[cloudflare:setup-run:terminal]`. These give a single grep path
|
|
23
|
+
* (`[cloudflare:setup-run:`) to the full transition chain post-hoc.
|
|
24
|
+
* - State is persisted at `~/{configDir}/cloudflared/setup.state.json`
|
|
25
|
+
* alongside `tunnel.state` and `login.state`. On corruption or absence
|
|
26
|
+
* the orchestrator reconciles from live `tunnel-status` + operator
|
|
27
|
+
* input (no "state file not found" crash).
|
|
28
|
+
* - The tool-surface gate in `platform/ui/app/lib/tool-surface-filter.ts`
|
|
29
|
+
* reads this state file every turn and removes the raw `tunnel-*`,
|
|
30
|
+
* Playwright, Bash, Write, Edit tools from the LLM's menu whenever
|
|
31
|
+
* `phase !== 'idle' && phase !== 'healthy'`. No LLM regression can
|
|
32
|
+
* re-enter the loop because the tools that enabled it are literally
|
|
33
|
+
* absent from the menu.
|
|
34
|
+
*/
|
|
35
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync, } from "node:fs";
|
|
36
|
+
import { join } from "node:path";
|
|
37
|
+
import { homedir } from "node:os";
|
|
38
|
+
import * as cloudflared from "./cloudflared.js";
|
|
39
|
+
import { deviceUrlBlock } from "../../../../../lib/device-url/dist/index.js";
|
|
40
|
+
import { hostname as osHostname } from "node:os";
|
|
41
|
+
function initialState() {
|
|
42
|
+
return {
|
|
43
|
+
phase: "idle",
|
|
44
|
+
domain: null,
|
|
45
|
+
adminSubdomain: null,
|
|
46
|
+
publicSubdomain: null,
|
|
47
|
+
tunnelId: null,
|
|
48
|
+
phaseEnteredAt: new Date().toISOString(),
|
|
49
|
+
unhealthyReason: null,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// State file I/O
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
/**
|
|
56
|
+
* The orchestrator state file path. Consumers OUTSIDE this module (e.g. the
|
|
57
|
+
* tool-surface filter in `platform/ui/app/lib/tool-surface-filter.ts`) also
|
|
58
|
+
* need this path to read the phase. Exported so the two sides cannot drift.
|
|
59
|
+
*
|
|
60
|
+
* Brand-aware via loadBrand(); falls back to `.maxy` when PLATFORM_ROOT is
|
|
61
|
+
* unset. The fallback matters only for the tool-surface filter running
|
|
62
|
+
* outside the cloudflare MCP server's process — the MCP server always has
|
|
63
|
+
* PLATFORM_ROOT set by the claude-agent spawn.
|
|
64
|
+
*/
|
|
65
|
+
export function setupStatePath(configDirOverride) {
|
|
66
|
+
const configDir = configDirOverride ?? (() => {
|
|
67
|
+
try {
|
|
68
|
+
return cloudflared.loadBrand().configDir;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return ".maxy";
|
|
72
|
+
}
|
|
73
|
+
})();
|
|
74
|
+
return join(homedir(), configDir, "cloudflared", "setup.state.json");
|
|
75
|
+
}
|
|
76
|
+
function readState() {
|
|
77
|
+
const path = setupStatePath();
|
|
78
|
+
if (!existsSync(path))
|
|
79
|
+
return initialState();
|
|
80
|
+
try {
|
|
81
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
82
|
+
// Loose validation — if the file is malformed, treat as idle and rely
|
|
83
|
+
// on `tunnel-status` reconciliation to map back to the right phase.
|
|
84
|
+
// A "state file not found" crash would be a silent failure mode the
|
|
85
|
+
// operator cannot recover from; mapping to idle is loud-failure (log)
|
|
86
|
+
// but non-fatal.
|
|
87
|
+
if (typeof parsed?.phase !== "string") {
|
|
88
|
+
console.error(`[cloudflare:setup-run:state-malformed] path=${path} — resetting to idle`);
|
|
89
|
+
return initialState();
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
phase: parsed.phase,
|
|
93
|
+
domain: parsed.domain ?? null,
|
|
94
|
+
adminSubdomain: parsed.adminSubdomain ?? null,
|
|
95
|
+
publicSubdomain: parsed.publicSubdomain ?? null,
|
|
96
|
+
tunnelId: parsed.tunnelId ?? null,
|
|
97
|
+
phaseEnteredAt: parsed.phaseEnteredAt ?? new Date().toISOString(),
|
|
98
|
+
unhealthyReason: parsed.unhealthyReason ?? null,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
console.error(`[cloudflare:setup-run:state-read-error] path=${path} err=${err instanceof Error ? err.message : String(err)}`);
|
|
103
|
+
return initialState();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function writeState(state) {
|
|
107
|
+
const path = setupStatePath();
|
|
108
|
+
mkdirSync(join(homedir(), cloudflared.loadBrand().configDir, "cloudflared"), { recursive: true });
|
|
109
|
+
writeFileSync(path, JSON.stringify(state, null, 2), { mode: 0o600 });
|
|
110
|
+
}
|
|
111
|
+
function deleteState() {
|
|
112
|
+
const path = setupStatePath();
|
|
113
|
+
try {
|
|
114
|
+
unlinkSync(path);
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
const code = err.code;
|
|
118
|
+
if (code !== "ENOENT") {
|
|
119
|
+
console.error(`[cloudflare:setup-run:state-delete-error] path=${path} err=${err}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function transition(state, next, reason) {
|
|
124
|
+
if (state.phase !== next) {
|
|
125
|
+
console.error(`[cloudflare:setup-run:phase-transition] from=${state.phase} to=${next} reason=${reason}`);
|
|
126
|
+
}
|
|
127
|
+
const updated = {
|
|
128
|
+
...state,
|
|
129
|
+
phase: next,
|
|
130
|
+
phaseEnteredAt: new Date().toISOString(),
|
|
131
|
+
};
|
|
132
|
+
writeState(updated);
|
|
133
|
+
return updated;
|
|
134
|
+
}
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// Read-only status
|
|
137
|
+
//
|
|
138
|
+
// Exposed so the tool-surface filter (outside this MCP server) can read
|
|
139
|
+
// orchestrator phase without advancing it, and so the companion
|
|
140
|
+
// `cloudflare-setup-status` tool can surface it to the admin agent.
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
export function readOrchestratorState() {
|
|
143
|
+
return readState();
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Whether the tool-surface filter should treat this device as "setup in
|
|
147
|
+
* progress". Matches the gate condition named in the task file: the filter
|
|
148
|
+
* lifts whenever phase is idle or terminal (healthy / unhealthy).
|
|
149
|
+
*/
|
|
150
|
+
export function isSetupActive(state) {
|
|
151
|
+
return state.phase !== "idle" && state.phase !== "healthy" && state.phase !== "unhealthy";
|
|
152
|
+
}
|
|
153
|
+
function subtoolLog(name, phase) {
|
|
154
|
+
console.error(`[cloudflare:setup-run:subtool-call] subtool=${name} phase=${phase}`);
|
|
155
|
+
}
|
|
156
|
+
function terminalLog(outcome, reason) {
|
|
157
|
+
console.error(`[cloudflare:setup-run:terminal] outcome=${outcome} reason=${reason}`);
|
|
158
|
+
}
|
|
159
|
+
function operatorInputLog(phase, repr) {
|
|
160
|
+
console.error(`[cloudflare:setup-run:operator-input] phase=${phase} input=${repr}`);
|
|
161
|
+
}
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
// Subdomain validation
|
|
164
|
+
//
|
|
165
|
+
// The operator types subdomain labels directly. Reject anything that would
|
|
166
|
+
// break `cloudflared tunnel route dns` before shelling out. Labels: letters,
|
|
167
|
+
// digits, hyphens; 1-63 chars; no leading/trailing hyphen.
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
const SUBDOMAIN_PATTERN = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/i;
|
|
170
|
+
function validateSubdomain(label, name) {
|
|
171
|
+
if (label.length === 0)
|
|
172
|
+
return `${name} is empty`;
|
|
173
|
+
if (label.includes("."))
|
|
174
|
+
return `${name} must be a single label (got "${label}"). Use just the prefix, e.g. "admin" — not "admin.example.com".`;
|
|
175
|
+
if (!SUBDOMAIN_PATTERN.test(label))
|
|
176
|
+
return `${name} "${label}" has invalid characters. Use letters, digits, and hyphens only.`;
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// Card builder — the `maxy-device-url` block wrapped in intent prose
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
function signInCard(authUrl, accountHint) {
|
|
183
|
+
const block = deviceUrlBlock({
|
|
184
|
+
url: authUrl,
|
|
185
|
+
intent: "Sign in to Cloudflare",
|
|
186
|
+
hostname: osHostname(),
|
|
187
|
+
});
|
|
188
|
+
return (`Cloudflare sign-in started on this device.\n\n` +
|
|
189
|
+
`${block}\n\n` +
|
|
190
|
+
`Click the button to open the sign-in page on this device's browser. ` +
|
|
191
|
+
`Pick the account that owns ${accountHint} — the account name is in the ` +
|
|
192
|
+
`top-left of the Cloudflare page, next to the little orange cloud — then ` +
|
|
193
|
+
`click Authorize. Tell me when you have finished.`);
|
|
194
|
+
}
|
|
195
|
+
function accountSwitchCard(hostname) {
|
|
196
|
+
return (`That Cloudflare account does not own ${hostname}.\n\n` +
|
|
197
|
+
`Open Cloudflare in your browser. Look at the name in the top-left. ` +
|
|
198
|
+
`If it is not the account that owns ${hostname}, switch to the correct ` +
|
|
199
|
+
`account using the top-left dropdown. Once the correct account is showing, ` +
|
|
200
|
+
`tell me and I will start the sign-in again.`);
|
|
201
|
+
}
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
// The orchestrator entry point
|
|
204
|
+
//
|
|
205
|
+
// Every call:
|
|
206
|
+
// (1) Reads persisted state.
|
|
207
|
+
// (2) Probes live tunnel-status.
|
|
208
|
+
// (3) Reconciles — if status shows we're further along than persisted,
|
|
209
|
+
// advance persisted to match.
|
|
210
|
+
// (4) Handles current phase.
|
|
211
|
+
//
|
|
212
|
+
// Step (3) is what makes re-entry and cold-start correct. An operator who
|
|
213
|
+
// deletes the state file mid-flow gets reconciled to the right phase on
|
|
214
|
+
// the next call because tunnel-status is the ground truth. Fresh-install
|
|
215
|
+
// with no state and no cert starts at `idle` → `awaiting-domain`. Fresh-
|
|
216
|
+
// install with no state but cert already bound (migration from a prior
|
|
217
|
+
// setup) maps to `awaiting-hostnames`.
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
export async function runOrchestrator(input) {
|
|
220
|
+
let state = readState();
|
|
221
|
+
console.error(`[cloudflare:setup-run:enter] phase=${state.phase} domain=${state.domain ?? "none"}`);
|
|
222
|
+
// Log operator input once, terse — helpful post-hoc when diagnosing how
|
|
223
|
+
// the state transitioned. Never log secrets; domain is public by design.
|
|
224
|
+
const inputRepr = JSON.stringify({
|
|
225
|
+
domain: input.domain ?? null,
|
|
226
|
+
adminSubdomain: input.adminSubdomain ?? null,
|
|
227
|
+
publicSubdomain: input.publicSubdomain ?? null,
|
|
228
|
+
confirmed: input.confirmed ?? false,
|
|
229
|
+
});
|
|
230
|
+
operatorInputLog(state.phase, inputRepr);
|
|
231
|
+
// ---------------------------------------------------------------------
|
|
232
|
+
// Reconcile persisted phase against live tunnel-status.
|
|
233
|
+
//
|
|
234
|
+
// The reconciliation rule: tunnel-status is always right. If status
|
|
235
|
+
// says we're healthy, we are healthy (regardless of persisted phase).
|
|
236
|
+
// If status says we have a cert but persisted says we still need one,
|
|
237
|
+
// we skip ahead to `awaiting-hostnames`. If persisted domain is null
|
|
238
|
+
// but input provides one, adopt it.
|
|
239
|
+
// ---------------------------------------------------------------------
|
|
240
|
+
if (input.domain) {
|
|
241
|
+
const domainTrimmed = input.domain.trim().replace(/^https?:\/\//, "").replace(/\/.*$/, "");
|
|
242
|
+
if (domainTrimmed.length === 0) {
|
|
243
|
+
return { text: "Invalid domain — please give me the bare domain, e.g. maxy.bot.", phase: state.phase, healthy: false };
|
|
244
|
+
}
|
|
245
|
+
if (state.domain !== domainTrimmed) {
|
|
246
|
+
state = { ...state, domain: domainTrimmed };
|
|
247
|
+
writeState(state);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (!state.domain && state.phase === "idle") {
|
|
251
|
+
state = transition(state, "awaiting-domain", "initial-entry-no-domain");
|
|
252
|
+
return {
|
|
253
|
+
text: "What domain do you want to use for the tunnel? For example: maxy.bot.",
|
|
254
|
+
phase: state.phase,
|
|
255
|
+
healthy: false,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
// Probe live tunnel-status. When `state.domain` is null we pass undefined
|
|
259
|
+
// and let cloudflared use persisted state.
|
|
260
|
+
subtoolLog("tunnel-status", state.phase);
|
|
261
|
+
const status = await cloudflared.getStatus(state.domain ?? undefined);
|
|
262
|
+
// Terminal healthy short-circuit — same answer from any phase.
|
|
263
|
+
if (status.healthy) {
|
|
264
|
+
// A second call after initial healthy convergence is a no-op. Log once
|
|
265
|
+
// as terminal so the review-detector can confirm the flow closed.
|
|
266
|
+
if (state.phase !== "healthy") {
|
|
267
|
+
state = transition(state, "healthy", "status-healthy");
|
|
268
|
+
terminalLog("healthy", "all-probes-ok");
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
console.error(`[cloudflare:setup-run:no-op] reason=already-healthy`);
|
|
272
|
+
}
|
|
273
|
+
const urls = status.probes.map((p) => ` https://${p.hostname}`).join("\n");
|
|
274
|
+
return {
|
|
275
|
+
text: `Tunnel is healthy.\n\n${urls}`,
|
|
276
|
+
phase: state.phase,
|
|
277
|
+
healthy: true,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
// --- From here: phase-specific advance logic -------------------------
|
|
281
|
+
// If we have a cert and it matches the binding, we are past the login
|
|
282
|
+
// phase. Don't re-spawn login.
|
|
283
|
+
const authedAndReady = status.hasCert && status.bound;
|
|
284
|
+
// Determine next action from (persisted phase, status, input).
|
|
285
|
+
switch (state.phase) {
|
|
286
|
+
case "awaiting-domain": {
|
|
287
|
+
// We have a domain now (input.domain was applied above). Advance.
|
|
288
|
+
if (authedAndReady) {
|
|
289
|
+
state = transition(state, "awaiting-hostnames", "cert-already-bound");
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
state = transition(state, "awaiting-signin", "no-cert-spawning-login");
|
|
293
|
+
}
|
|
294
|
+
return continueFromPhase(state, input, status);
|
|
295
|
+
}
|
|
296
|
+
case "idle": {
|
|
297
|
+
// Cold start with domain already supplied (rare — the check above
|
|
298
|
+
// only transitions to awaiting-domain when there's no domain).
|
|
299
|
+
if (authedAndReady) {
|
|
300
|
+
state = transition(state, "awaiting-hostnames", "cert-already-bound");
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
state = transition(state, "awaiting-signin", "cold-start-needs-login");
|
|
304
|
+
}
|
|
305
|
+
return continueFromPhase(state, input, status);
|
|
306
|
+
}
|
|
307
|
+
case "awaiting-signin": {
|
|
308
|
+
if (authedAndReady) {
|
|
309
|
+
state = transition(state, "awaiting-hostnames", "sign-in-complete");
|
|
310
|
+
return continueFromPhase(state, input, status);
|
|
311
|
+
}
|
|
312
|
+
return continueFromPhase(state, input, status);
|
|
313
|
+
}
|
|
314
|
+
case "awaiting-account-switch": {
|
|
315
|
+
// Operator confirms they have switched accounts. Re-login with force.
|
|
316
|
+
if (input.confirmed) {
|
|
317
|
+
subtoolLog("tunnel-login-force", state.phase);
|
|
318
|
+
cloudflared.resetAuth();
|
|
319
|
+
const res = await cloudflared.tunnelLogin();
|
|
320
|
+
state = transition(state, "awaiting-signin", "account-switch-relogin");
|
|
321
|
+
return {
|
|
322
|
+
text: signInCard(res.authUrl, state.domain ?? "your domain"),
|
|
323
|
+
phase: state.phase,
|
|
324
|
+
healthy: false,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
text: accountSwitchCard(state.domain ?? "your domain"),
|
|
329
|
+
phase: state.phase,
|
|
330
|
+
healthy: false,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
case "awaiting-hostnames": {
|
|
334
|
+
return continueFromPhase(state, input, status);
|
|
335
|
+
}
|
|
336
|
+
case "enabling": {
|
|
337
|
+
// Re-entry after crash in the middle of create/enable. Probe told
|
|
338
|
+
// us we're not healthy; fall through to re-run enable.
|
|
339
|
+
return continueFromPhase(state, input, status);
|
|
340
|
+
}
|
|
341
|
+
case "healthy": {
|
|
342
|
+
// Unreachable — the healthy short-circuit above returns before here.
|
|
343
|
+
// Keep for exhaustiveness.
|
|
344
|
+
return { text: "Tunnel is healthy.", phase: state.phase, healthy: true };
|
|
345
|
+
}
|
|
346
|
+
case "unhealthy": {
|
|
347
|
+
// Re-invoking after an unhealthy terminal re-probes and either
|
|
348
|
+
// converges (returns healthy above) or re-emits the same reason.
|
|
349
|
+
const reason = status.unhealthyReason ?? "unknown";
|
|
350
|
+
return {
|
|
351
|
+
text: `Tunnel is not healthy. Reason: ${reason}. Re-running setup from the current state — ask me again in a minute.`,
|
|
352
|
+
phase: state.phase,
|
|
353
|
+
healthy: false,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Handle a phase when it is the natural next step (or a fall-through from
|
|
360
|
+
* a reconciliation). Kept separate from `runOrchestrator`'s switch so the
|
|
361
|
+
* reconciliation cases above can transition and then delegate here without
|
|
362
|
+
* nested switching.
|
|
363
|
+
*/
|
|
364
|
+
async function continueFromPhase(state, input, status) {
|
|
365
|
+
switch (state.phase) {
|
|
366
|
+
case "awaiting-signin": {
|
|
367
|
+
subtoolLog("tunnel-login", state.phase);
|
|
368
|
+
const res = await cloudflared.tunnelLogin();
|
|
369
|
+
return {
|
|
370
|
+
text: signInCard(res.authUrl, state.domain ?? "your domain"),
|
|
371
|
+
phase: state.phase,
|
|
372
|
+
healthy: false,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
case "awaiting-hostnames": {
|
|
376
|
+
// Need admin subdomain from the operator.
|
|
377
|
+
if (!input.adminSubdomain) {
|
|
378
|
+
return {
|
|
379
|
+
text: `What subdomain should the admin interface use? ` +
|
|
380
|
+
`For example "admin" → admin.${state.domain}. ` +
|
|
381
|
+
`Optionally, what subdomain for the public chat? ` +
|
|
382
|
+
`For example "public" → public.${state.domain}. ` +
|
|
383
|
+
`Leave blank to skip the public chat.`,
|
|
384
|
+
phase: state.phase,
|
|
385
|
+
healthy: false,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
const adminErr = validateSubdomain(input.adminSubdomain, "admin subdomain");
|
|
389
|
+
if (adminErr)
|
|
390
|
+
return { text: adminErr, phase: state.phase, healthy: false };
|
|
391
|
+
let publicSubdomain = null;
|
|
392
|
+
if (input.publicSubdomain) {
|
|
393
|
+
const pubErr = validateSubdomain(input.publicSubdomain, "public subdomain");
|
|
394
|
+
if (pubErr)
|
|
395
|
+
return { text: pubErr, phase: state.phase, healthy: false };
|
|
396
|
+
publicSubdomain = input.publicSubdomain;
|
|
397
|
+
}
|
|
398
|
+
if (publicSubdomain && publicSubdomain === input.adminSubdomain) {
|
|
399
|
+
return {
|
|
400
|
+
text: `Admin and public subdomains must differ — both given as "${publicSubdomain}".`,
|
|
401
|
+
phase: state.phase,
|
|
402
|
+
healthy: false,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
// Persist the choices so re-entry survives mid-call crashes.
|
|
406
|
+
state = { ...state, adminSubdomain: input.adminSubdomain, publicSubdomain };
|
|
407
|
+
writeState(state);
|
|
408
|
+
// Transition to enabling and run create + enable + verify.
|
|
409
|
+
state = transition(state, "enabling", "hostnames-received");
|
|
410
|
+
try {
|
|
411
|
+
// tunnel-create
|
|
412
|
+
subtoolLog("tunnel-create", state.phase);
|
|
413
|
+
const tunnelName = (state.domain ?? "tunnel").replace(/\./g, "-");
|
|
414
|
+
const tunnel = cloudflared.createTunnelCli(tunnelName);
|
|
415
|
+
const adminHostname = `${input.adminSubdomain}.${state.domain}`;
|
|
416
|
+
const publicHostname = publicSubdomain ? `${publicSubdomain}.${state.domain}` : null;
|
|
417
|
+
const hostnames = publicHostname ? [adminHostname, publicHostname] : [adminHostname];
|
|
418
|
+
const platformPort = parseInt(process.env.PLATFORM_PORT ?? "19200", 10);
|
|
419
|
+
const configPath = cloudflared.writeLocalConfig(tunnel.tunnelId, tunnel.credentialsPath, hostnames, platformPort);
|
|
420
|
+
cloudflared.saveTunnelIdentity({
|
|
421
|
+
tunnelId: tunnel.tunnelId,
|
|
422
|
+
tunnelName: tunnel.tunnelName,
|
|
423
|
+
domain: state.domain ?? "",
|
|
424
|
+
configPath,
|
|
425
|
+
credentialsPath: tunnel.credentialsPath,
|
|
426
|
+
adminHostname,
|
|
427
|
+
publicHostname,
|
|
428
|
+
});
|
|
429
|
+
state = { ...state, tunnelId: tunnel.tunnelId };
|
|
430
|
+
writeState(state);
|
|
431
|
+
// Route DNS for each hostname.
|
|
432
|
+
subtoolLog("tunnel-route-dns", state.phase);
|
|
433
|
+
for (const h of hostnames) {
|
|
434
|
+
await cloudflared.routeDnsCli(tunnel.tunnelId, h);
|
|
435
|
+
}
|
|
436
|
+
// tunnel-enable
|
|
437
|
+
subtoolLog("tunnel-enable", state.phase);
|
|
438
|
+
cloudflared.startTunnel({
|
|
439
|
+
tunnelId: tunnel.tunnelId,
|
|
440
|
+
tunnelName: tunnel.tunnelName,
|
|
441
|
+
domain: state.domain ?? "",
|
|
442
|
+
configPath,
|
|
443
|
+
credentialsPath: tunnel.credentialsPath,
|
|
444
|
+
});
|
|
445
|
+
// Poll `tunnel-status` until healthy or a hard reason surfaces.
|
|
446
|
+
// Same retry budget as the standalone tunnel-enable tool's verify
|
|
447
|
+
// loop: 6 attempts × 5s = 30s.
|
|
448
|
+
for (let attempt = 0; attempt < 6; attempt++) {
|
|
449
|
+
if (attempt > 0)
|
|
450
|
+
await new Promise((r) => setTimeout(r, 5000));
|
|
451
|
+
const probe = await cloudflared.getStatus(state.domain ?? undefined);
|
|
452
|
+
if (probe.healthy) {
|
|
453
|
+
state = transition(state, "healthy", "create-enable-verified");
|
|
454
|
+
terminalLog("healthy", "create-enable-verified");
|
|
455
|
+
const urls = probe.probes.map((p) => ` https://${p.hostname}`).join("\n");
|
|
456
|
+
return { text: `Tunnel is healthy.\n\n${urls}`, phase: state.phase, healthy: true };
|
|
457
|
+
}
|
|
458
|
+
if (probe.unhealthyReason === "bound-account-does-not-own-hostname") {
|
|
459
|
+
state = transition(state, "awaiting-account-switch", "post-enable-wrong-account");
|
|
460
|
+
return {
|
|
461
|
+
text: accountSwitchCard(state.domain ?? "your domain"),
|
|
462
|
+
phase: state.phase,
|
|
463
|
+
healthy: false,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
// Couldn't verify within retry budget — emit a diagnostic unhealthy
|
|
468
|
+
// terminal. Operator can re-invoke cloudflare-setup-run later; if
|
|
469
|
+
// the edge caught up by then it will converge to healthy.
|
|
470
|
+
const final = await cloudflared.getStatus(state.domain ?? undefined);
|
|
471
|
+
const reason = final.unhealthyReason ?? "edge-not-yet-reachable";
|
|
472
|
+
state = { ...state, unhealthyReason: reason };
|
|
473
|
+
state = transition(state, "unhealthy", `verify-failed-${reason}`);
|
|
474
|
+
terminalLog("unhealthy", reason);
|
|
475
|
+
return {
|
|
476
|
+
text: `Tunnel created and started but the end-to-end probe has not converged yet ` +
|
|
477
|
+
`(reason: ${reason}). DNS propagation can take 1-5 minutes. Ask me to set up ` +
|
|
478
|
+
`the tunnel again in a minute to re-probe.`,
|
|
479
|
+
phase: state.phase,
|
|
480
|
+
healthy: false,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
catch (err) {
|
|
484
|
+
// tunnel-create shelled refusal — detect the wrong-account class
|
|
485
|
+
// and route to awaiting-account-switch. Other errors bubble up as
|
|
486
|
+
// unhealthy with the message.
|
|
487
|
+
if (err instanceof cloudflared.CloudflareRefusalError) {
|
|
488
|
+
const reason = err.refusal.reason;
|
|
489
|
+
if (reason === "post-flight-fqdn-mismatch" ||
|
|
490
|
+
reason === "hostname-zone-not-routable" ||
|
|
491
|
+
reason === "account-drift" ||
|
|
492
|
+
reason === "unbound-device") {
|
|
493
|
+
state = transition(state, "awaiting-account-switch", `refusal-${reason}`);
|
|
494
|
+
return {
|
|
495
|
+
text: accountSwitchCard(state.domain ?? "your domain"),
|
|
496
|
+
phase: state.phase,
|
|
497
|
+
healthy: false,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
502
|
+
state = { ...state, unhealthyReason: "create-or-enable-failed" };
|
|
503
|
+
state = transition(state, "unhealthy", "create-or-enable-threw");
|
|
504
|
+
terminalLog("unhealthy", "create-or-enable-threw");
|
|
505
|
+
return {
|
|
506
|
+
text: `Tunnel setup failed: ${msg}`,
|
|
507
|
+
phase: state.phase,
|
|
508
|
+
healthy: false,
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
case "enabling": {
|
|
513
|
+
// Re-enter mid-enable after a crash. Delegate back to
|
|
514
|
+
// awaiting-hostnames with the persisted subdomains as "input" so
|
|
515
|
+
// the create/enable/verify block runs again. Idempotent: tunnel
|
|
516
|
+
// create reuses an existing tunnel by name; DNS route is overwritten.
|
|
517
|
+
if (state.adminSubdomain) {
|
|
518
|
+
const recoveredInput = {
|
|
519
|
+
adminSubdomain: state.adminSubdomain,
|
|
520
|
+
publicSubdomain: state.publicSubdomain ?? undefined,
|
|
521
|
+
};
|
|
522
|
+
const recoveredState = transition(state, "awaiting-hostnames", "re-entry-after-enabling-crash");
|
|
523
|
+
return continueFromPhase(recoveredState, recoveredInput, status);
|
|
524
|
+
}
|
|
525
|
+
// Somehow hit enabling without a stored adminSubdomain — reset to
|
|
526
|
+
// awaiting-hostnames and ask again.
|
|
527
|
+
state = transition(state, "awaiting-hostnames", "re-entry-missing-subdomains");
|
|
528
|
+
return continueFromPhase(state, input, status);
|
|
529
|
+
}
|
|
530
|
+
case "idle":
|
|
531
|
+
case "awaiting-domain":
|
|
532
|
+
case "awaiting-account-switch":
|
|
533
|
+
case "healthy":
|
|
534
|
+
case "unhealthy":
|
|
535
|
+
// Unreachable in this helper — the top-level switch handles these.
|
|
536
|
+
return {
|
|
537
|
+
text: `Unexpected phase: ${state.phase}. Ask me again.`,
|
|
538
|
+
phase: state.phase,
|
|
539
|
+
healthy: false,
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
// ---------------------------------------------------------------------------
|
|
544
|
+
// Reset — exposed for tests and for the hypothetical "start over" admin flow
|
|
545
|
+
// ---------------------------------------------------------------------------
|
|
546
|
+
export function resetOrchestrator() {
|
|
547
|
+
console.error(`[cloudflare:setup-run:reset]`);
|
|
548
|
+
deleteState();
|
|
549
|
+
}
|
|
550
|
+
//# sourceMappingURL=setup-orchestrator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-orchestrator.js","sourceRoot":"","sources":["../../src/lib/setup-orchestrator.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,63 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: setup-tunnel
|
|
3
|
-
description: Set up a Cloudflare Tunnel. The
|
|
3
|
+
description: Set up a Cloudflare Tunnel. The `cloudflare-setup-run` orchestrator drives the full flow deterministically — the agent relays its structured output verbatim and passes the operator's literal input through.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Set Up Cloudflare Tunnel
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Call `cloudflare-setup-run` with the operator's domain. Relay every tool result verbatim — do not interpret, do not paraphrase, do not add prose of your own. When the tool emits a `maxy-device-url` card, the chat UI renders it as a button; wait for the operator to confirm completion (e.g. "done"), then call `cloudflare-setup-run` again with `confirmed: true`. When the tool asks for subdomains, pass them through as `adminSubdomain` and (optionally) `publicSubdomain`.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
That is the entire flow. Every branch — sign-in, wrong-account recovery, DNS routing, verification — is chosen by the orchestrator in code, not by you.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
- **Speak dashboard language.** Say "Cloudflare account", "domain", "address", "sign in", "browser". Never say "zone", "CNAME", "account ID", "API", "SDK", "403", "record", or any hexadecimal identifier. Internal logs may use those terms; operator-facing text must not.
|
|
14
|
-
- **One prescribed recovery per failure mode.** When a `tunnel-*` tool returns a structured failure, relay the single sentence the tool gave you verbatim and take it. Never offer the operator a choice of actions — the tool has already told you which is correct (IDENTITY § Questions). Never suggest "open the dashboard" as an alternative when the sentence names something specific.
|
|
15
|
-
- **The operator confirms before the agent re-runs.** After asking the operator to do something in the dashboard, wait for explicit confirmation ("done", "ok", "yes") before the next tool call.
|
|
16
|
-
|
|
17
|
-
## The flow
|
|
18
|
-
|
|
19
|
-
1. **Confirm the operator has a Cloudflare account.** If they do not, say: "You will need a Cloudflare account. Sign up free at cloudflare.com, come back and tell me when you are signed in." Wait for confirmation, then continue.
|
|
20
|
-
|
|
21
|
-
2. **Confirm the operator's domain is on their Cloudflare account.** Ask: "What domain do you want to use, and is it already on Cloudflare?" When it is not on Cloudflare yet, say: "Open Cloudflare in your browser, go to Websites → Add a site, enter the domain, and follow the dashboard's instructions for nameservers. Tell me when the domain shows as Active in the dashboard." Wait for confirmation, then continue.
|
|
22
|
-
|
|
23
|
-
3. **Run `tunnel-login`.** The tool's result includes a `maxy-device-url` block that the chat UI renders as a "Open Cloudflare sign-in on device" button — clicking it drives the device's VNC browser to the Cloudflare auth page. Relay the tool's text verbatim so the button appears; do not paraphrase the URL into prose. Say: "Click the button to open the Cloudflare sign-in page on this device. Pick the account that owns `<domain>` — the account name is in the top-left of the Cloudflare page, next to the little orange cloud — then click Authorize." When the operator confirms, call `tunnel-login` again. The tool reports one of three states:
|
|
24
|
-
- **"Sign-in complete."** — move to step 4.
|
|
25
|
-
- **"Sign-in in progress…"** — the operator has not finished yet. Wait for explicit confirmation and call again. If `[cloudflare:tunnel-login:browser-launch-failed]` appears in the logs, no operator-facing change is needed — the button still drives the VNC browser correctly.
|
|
26
|
-
- **"Sign-in ended without saving the cert. Restarting."** — the tool has already respawned login. Relay the new tool result verbatim so the updated button appears, and wait for the operator.
|
|
27
|
-
|
|
28
|
-
4. **Run `tunnel-create`.** Ask the operator what sub-addresses they want. The tool creates the tunnel and routes each address via `cloudflared`. Refusals:
|
|
29
|
-
- **`hostname-zone-not-routable`** — the domain is not on the signed-in Cloudflare account. Relay the refusal sentence verbatim.
|
|
30
|
-
- **`post-flight-fqdn-mismatch`** — `cloudflared` wrote the address under a different domain than asked. Relay the refusal sentence verbatim.
|
|
31
|
-
|
|
32
|
-
5. **Run `tunnel-enable`.** Starts the tunnel daemon and verifies the admin URL is reachable through Cloudflare. Reports success with the live URLs.
|
|
33
|
-
|
|
34
|
-
6. **Run `tunnel-status`.** The e2e probe should report `healthy: true`. When it is `false`, the tool's trailing text names exactly one next action — relay it verbatim. The recovery sentence handles the following `unhealthyReason` values deterministically:
|
|
35
|
-
- `bound-account-does-not-own-hostname` — the operator must sign into the correct Cloudflare account; the sentence names the domain.
|
|
36
|
-
- `hostname-probes-failed` with `tunnel-not-matched` / `cname-points-elsewhere` — the agent calls `tunnel-add-hostname` for each configured hostname to re-point DNS.
|
|
37
|
-
- `hostname-probes-failed` with `dns-missing` — the agent calls `tunnel-add-hostname` to create DNS.
|
|
38
|
-
- `hostname-probes-failed` with `edge-unreachable` — transient; wait, do not re-probe or open the dashboard.
|
|
39
|
-
- `not-running` — ask the operator for permission to enable the tunnel.
|
|
40
|
-
- `no-tunnel-configured` — run `tunnel-create`.
|
|
41
|
-
|
|
42
|
-
## Adding an alias address
|
|
43
|
-
|
|
44
|
-
An alias address (e.g. `maxy.chat`) serves the public chat directly. The alias's domain must be on the same Cloudflare account the laptop is signed into.
|
|
45
|
-
|
|
46
|
-
1. Ask the operator: "Is `<alias>` on the same Cloudflare account this laptop is signed into?" When it is not, say: "Open Cloudflare in your browser, go to Websites → Add a site, and add `<alias>`. Tell me when it shows as Active." Wait for confirmation.
|
|
47
|
-
2. Run `tunnel-add-hostname` with the alias and the tunnel UUID from `tunnel-status`. Refusals carry the same verbatim recovery sentences as step 4.
|
|
48
|
-
3. Verify with `tunnel-status`. DNS propagation takes 1-5 minutes.
|
|
49
|
-
|
|
50
|
-
## Reinstalling / upgrading the platform
|
|
51
|
-
|
|
52
|
-
When re-running the installer, pass `--hostname` and `--port` to preserve the device's current identity:
|
|
53
|
-
|
|
54
|
-
1. Call `system-status` to get the current `hostname` and `port`.
|
|
55
|
-
2. Derive the brand package (`Maxy` → `@rubytech/create-maxy`, `Real Agent` → `@rubytech/create-realagent`).
|
|
56
|
-
3. Run: `npx -y @rubytech/create-maxy --hostname muvin --port 19400`
|
|
57
|
-
|
|
58
|
-
## Important
|
|
59
|
-
|
|
60
|
-
- **Never interact with Cloudflare dashboard forms on the operator's behalf.** The only Cloudflare URL opened in the VNC browser is the sign-in URL from `tunnel-login`. Everything else in the dashboard, the operator does themselves.
|
|
61
|
-
- **The operator signs in themselves.** Open the URL, tell them what to do, wait. The agent does not evaluate the page.
|
|
62
|
-
- **The tunnel tools are idempotent.** Safe to call after any partial failure — they read filesystem state top-to-bottom on every call.
|
|
63
|
-
- **DNS propagation takes 1-5 minutes.** Nameserver propagation takes minutes to 24 hours.
|
|
12
|
+
The raw `tunnel-login` / `tunnel-create` / `tunnel-enable` / `tunnel-status` / `tunnel-add-hostname` tools are filtered out of your menu while a setup is active. This is not a bug — it is the tool-surface gate. You have one tool during setup: `cloudflare-setup-run`.
|