@rubytech/create-maxy-code 0.1.109 → 0.1.111

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.
Files changed (32) hide show
  1. package/dist/index.js +50 -150
  2. package/dist/snap-chromium.js +1 -2
  3. package/dist/uninstall.js +10 -9
  4. package/package.json +1 -1
  5. package/payload/platform/neo4j/schema.cypher +6 -3
  6. package/payload/platform/plugins/admin/PLUGIN.md +1 -0
  7. package/payload/platform/plugins/admin/mcp/dist/index.js +1 -1
  8. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  9. package/payload/platform/plugins/admin/skills/upgrade/SKILL.md +34 -0
  10. package/payload/platform/plugins/cloudflare/.claude-plugin/plugin.json +1 -1
  11. package/payload/platform/plugins/cloudflare/PLUGIN.md +9 -16
  12. package/payload/platform/plugins/cloudflare/mcp/dist/index.js +7 -12
  13. package/payload/platform/plugins/cloudflare/mcp/dist/index.js.map +1 -1
  14. package/payload/platform/plugins/cloudflare/references/dashboard-guide.md +3 -3
  15. package/payload/platform/plugins/cloudflare/references/manual-setup.md +19 -54
  16. package/payload/platform/plugins/cloudflare/references/reset-guide.md +29 -28
  17. package/payload/platform/plugins/cloudflare/skills/setup-tunnel/SKILL.md +29 -149
  18. package/payload/platform/plugins/docs/references/admin-session.md +1 -1
  19. package/payload/platform/plugins/docs/references/admin-ui.md +1 -1
  20. package/payload/platform/plugins/docs/references/cloudflare.md +20 -29
  21. package/payload/platform/plugins/docs/references/platform.md +1 -1
  22. package/payload/platform/plugins/docs/references/plugins-guide.md +1 -1
  23. package/payload/platform/plugins/docs/references/troubleshooting.md +1 -1
  24. package/payload/platform/scripts/check-no-task-id-leaks.mjs +1 -1
  25. package/payload/platform/templates/agents/admin/IDENTITY.md +1 -1
  26. package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
  27. package/payload/platform/plugins/cloudflare/scripts/__tests__/tunnel-ingress.test.ts +0 -241
  28. package/payload/platform/plugins/cloudflare/scripts/list-cf-domains.sh +0 -98
  29. package/payload/platform/plugins/cloudflare/scripts/list-cf-domains.ts +0 -715
  30. package/payload/platform/plugins/cloudflare/scripts/reset-tunnel.sh +0 -107
  31. package/payload/platform/plugins/cloudflare/scripts/setup-tunnel.sh +0 -852
  32. package/payload/platform/plugins/cloudflare/scripts/tunnel-ingress.ts +0 -291
@@ -1,291 +0,0 @@
1
- // Pure tunnel-ingress rendering + state I/O.
2
- //
3
- // Mirror of `samba-provision.ts`: pure decision functions in this file with
4
- // no side effects; `setup-tunnel.sh` orchestrates I/O around them. Invoked
5
- // from bash via `node --experimental-strip-types`. The CLI shim at the
6
- // bottom of this file takes a JSON spec on argv and prints rendered YAML
7
- // (or the requested state snippet) on stdout so the shell can `>` it into
8
- // the brand's config.yml or tunnel.state.
9
- //
10
- // Why a pure module:
11
- // - The existing setup-tunnel.sh rebuilds config.yml from scratch every
12
- // run. Adding SSH and SMB conditionally inline doubles the rewrite
13
- // logic and makes it untestable. Extracting to a pure function with a
14
- // test grid locks the YAML shape against future drift.
15
- // - tunnel.state needs additive sshHostname / smbHostname fields so a
16
- // re-run with no env vars rehydrates the ingress instead of silently
17
- // dropping it. The persistence shape lives here so the test grid
18
- // covers round-trip parity.
19
- //
20
- // Out of scope:
21
- // - Cloudflare API calls (banned per `feedback_cf_api_total_eradication`).
22
- // - Access policy creation (operator authors it in the dashboard).
23
- // - DNS routing (the bash wrapper invokes `cloudflared tunnel route dns`).
24
-
25
- import { readFileSync, existsSync, appendFileSync } from "node:fs";
26
-
27
- // STREAM_LOG_PATH writer (contract). The four CLI
28
- // subcommands invoked from setup-tunnel.sh (probe-samba, render-config,
29
- // render-state, action-req) emit a `phase=<cmd>-start` line on entry and
30
- // `phase=<cmd>-ok` / `phase=<cmd>-fail` on exit. Pattern ported verbatim
31
- // from list-cf-domains.ts:94-133: sticky-flag on first write failure to
32
- // avoid stderr spam if STREAM_LOG_PATH is unwritable; observability
33
- // failure must not mask the subcommand's real signal.
34
- let streamLogWriteFailed = false;
35
-
36
- function logPhase(line: string): void {
37
- const streamLogPath = process.env.STREAM_LOG_PATH;
38
- if (!streamLogPath || streamLogWriteFailed) return;
39
- try {
40
- appendFileSync(
41
- streamLogPath,
42
- `[${new Date().toISOString()}] [script:tunnel-ingress] ${line}\n`,
43
- );
44
- } catch (err) {
45
- streamLogWriteFailed = true;
46
- const detail = (err instanceof Error ? err.message : String(err))
47
- .slice(0, 200)
48
- .replace(/"/g, "'");
49
- process.stderr.write(
50
- `[tunnel-ingress] phase=stream-log-write-failed detail="${detail}"\n`,
51
- );
52
- }
53
- }
54
-
55
- // ---------------------------------------------------------------------------
56
- // Types
57
- // ---------------------------------------------------------------------------
58
-
59
- export interface IngressSpec {
60
- tunnelId: string;
61
- credentialsPath: string;
62
- httpPort: number;
63
- httpHostnames: string[];
64
- sshHostname?: string | null;
65
- smbHostname?: string | null;
66
- }
67
-
68
- export interface TunnelState {
69
- tunnelId: string;
70
- tunnelName: string;
71
- domain: string;
72
- configPath: string;
73
- credentialsPath: string;
74
- sshHostname?: string | null;
75
- smbHostname?: string | null;
76
- }
77
-
78
- // ---------------------------------------------------------------------------
79
- // Pure renderer
80
- // ---------------------------------------------------------------------------
81
-
82
- /**
83
- * Render config.yml from a fully-resolved IngressSpec. Order matters:
84
- * cloudflared matches ingress rules top-to-bottom, so the catch-all
85
- * `http_status:404` MUST be last. SSH and SMB are inserted between the
86
- * HTTPS hostnames and the catch-all — they carry non-HTTP services, so
87
- * placing them after HTTPS keeps the existing HTTP routing behaviour
88
- * unchanged when no SSH/SMB hostname is configured.
89
- */
90
- export function renderConfigYml(spec: IngressSpec): string {
91
- const lines: string[] = [];
92
- lines.push(`tunnel: ${spec.tunnelId}`);
93
- lines.push(`credentials-file: ${spec.credentialsPath}`);
94
- lines.push(`ingress:`);
95
- for (const h of spec.httpHostnames) {
96
- lines.push(` - hostname: ${h}`);
97
- lines.push(` service: http://localhost:${spec.httpPort}`);
98
- }
99
- if (spec.sshHostname) {
100
- lines.push(` - hostname: ${spec.sshHostname}`);
101
- lines.push(` service: ssh://localhost:22`);
102
- }
103
- if (spec.smbHostname) {
104
- lines.push(` - hostname: ${spec.smbHostname}`);
105
- lines.push(` service: tcp://localhost:445`);
106
- }
107
- lines.push(` - service: http_status:404`);
108
- lines.push(``);
109
- return lines.join("\n");
110
- }
111
-
112
- /**
113
- * Render the tunnel.state JSON, preserving SSH/SMB hostnames as additive
114
- * fields so re-runs can rehydrate them. The base shape (tunnelId,
115
- * tunnelName, domain, configPath, credentialsPath) is a superset that
116
- * consumers without ssh/smb knowledge continue to read correctly.
117
- */
118
- export function renderTunnelState(state: TunnelState): string {
119
- const out: Record<string, string> = {
120
- tunnelId: state.tunnelId,
121
- tunnelName: state.tunnelName,
122
- domain: state.domain,
123
- configPath: state.configPath,
124
- credentialsPath: state.credentialsPath,
125
- };
126
- if (state.sshHostname) out.sshHostname = state.sshHostname;
127
- if (state.smbHostname) out.smbHostname = state.smbHostname;
128
- return JSON.stringify(out, null, 2) + "\n";
129
- }
130
-
131
- /**
132
- * Read an existing tunnel.state from disk, returning the persisted
133
- * sshHostname / smbHostname if present. Used to rehydrate on re-runs
134
- * where the operator did not pass the env vars again. Missing file or
135
- * malformed JSON returns nulls — the caller decides whether to fail.
136
- */
137
- export function readPersistedHostnames(statePath: string): {
138
- sshHostname: string | null;
139
- smbHostname: string | null;
140
- } {
141
- if (!existsSync(statePath)) return { sshHostname: null, smbHostname: null };
142
- try {
143
- const raw = readFileSync(statePath, "utf8");
144
- const parsed = JSON.parse(raw) as Partial<TunnelState>;
145
- return {
146
- sshHostname: typeof parsed.sshHostname === "string" ? parsed.sshHostname : null,
147
- smbHostname: typeof parsed.smbHostname === "string" ? parsed.smbHostname : null,
148
- };
149
- } catch {
150
- return { sshHostname: null, smbHostname: null };
151
- }
152
- }
153
-
154
- // ---------------------------------------------------------------------------
155
- // Samba presence probe
156
- // ---------------------------------------------------------------------------
157
-
158
- /**
159
- * Probe whether the per-brand Samba stanza exists in /etc/samba/smb.conf.
160
- * The brand stanza header is `[<brand>]` at the start of a line (the
161
- * `renderBrandStanza` output). Source of truth is the Pi filesystem, not
162
- * brand.json — Samba is provisioned by the installer's post-install step,
163
- * not declared up-front.
164
- */
165
- export function probeSambaStanza(brand: string, smbConfPath = "/etc/samba/smb.conf"): boolean {
166
- if (!existsSync(smbConfPath)) return false;
167
- try {
168
- const raw = readFileSync(smbConfPath, "utf8");
169
- const re = new RegExp(`^\\[${escapeRegex(brand)}\\]\\s*$`, "m");
170
- return re.test(raw);
171
- } catch {
172
- return false;
173
- }
174
- }
175
-
176
- function escapeRegex(s: string): string {
177
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
178
- }
179
-
180
- // ---------------------------------------------------------------------------
181
- // ACTION REQUIRED block
182
- // ---------------------------------------------------------------------------
183
-
184
- /**
185
- * Render the dashboard click-path the operator follows to author the Access
186
- * policy. Same shape as the existing apex-CNAME ACTION REQUIRED block in
187
- * setup-tunnel.sh. The script does NOT create the policy — Cloudflare API
188
- * is banned (`feedback_cf_api_total_eradication`) and `cloudflared` CLI
189
- * has no Access-application create subcommand. The operator clicks through
190
- * Zero Trust → Access → Applications → Add → Self-hosted.
191
- */
192
- export function renderAccessPolicyActionRequired(input: {
193
- sshHostname?: string | null;
194
- smbHostname?: string | null;
195
- operatorEmail: string;
196
- }): string {
197
- const hosts: Array<{ kind: "SSH" | "SMB"; hostname: string }> = [];
198
- if (input.sshHostname) hosts.push({ kind: "SSH", hostname: input.sshHostname });
199
- if (input.smbHostname) hosts.push({ kind: "SMB", hostname: input.smbHostname });
200
- if (hosts.length === 0) return "";
201
-
202
- const lines: string[] = [];
203
- lines.push("");
204
- lines.push("============================================================");
205
- lines.push("ACTION REQUIRED — Cloudflare Zero Trust Access policy");
206
- lines.push("============================================================");
207
- for (const { kind, hostname } of hosts) {
208
- lines.push(` ${kind}: ${hostname}`);
209
- lines.push(` Cloudflare dashboard → Zero Trust → Access → Applications`);
210
- lines.push(` Add → Self-hosted`);
211
- lines.push(` Application name: ${hostname}`);
212
- lines.push(` Application domain: ${hostname}`);
213
- lines.push(` Policy → Action: Allow`);
214
- lines.push(` Include → Emails: ${input.operatorEmail}`);
215
- lines.push("");
216
- }
217
- lines.push("Until each Access application is created, off-LAN clients see");
218
- lines.push("Cloudflare's identity-rejection page and bytes never reach the Pi.");
219
- lines.push("============================================================");
220
- return lines.join("\n");
221
- }
222
-
223
- // ---------------------------------------------------------------------------
224
- // CLI shim
225
- // ---------------------------------------------------------------------------
226
-
227
- // Bash invokes this file with one of three subcommands:
228
- // render-config <spec.json> → stdout YAML
229
- // render-state <state.json> → stdout JSON
230
- // read-state <state-path> → stdout JSON {sshHostname,smbHostname}
231
- // probe-samba <brand> [smb.conf] → exit 0 if present, 1 if absent
232
- // action-req <input.json> → stdout ACTION REQUIRED block
233
-
234
- function readJsonArg(path: string): unknown {
235
- const raw = readFileSync(path, "utf8");
236
- return JSON.parse(raw);
237
- }
238
-
239
- const isCli =
240
- typeof process !== "undefined" &&
241
- Array.isArray(process.argv) &&
242
- process.argv[1] !== undefined &&
243
- /tunnel-ingress\.(ts|js|mjs|mts)$/.test(process.argv[1]);
244
-
245
- if (isCli) {
246
- const [, , cmd, ...rest] = process.argv;
247
- // Track which subcommands carry the stream-log contract — for these
248
- // we emit start/ok/fail phase pairs. Unknown subcommands and read-state are
249
- // out of scope per the task (read-state has no setup-tunnel.sh caller line).
250
- const instrumented = new Set(["probe-samba", "render-config", "render-state", "action-req"]);
251
- if (instrumented.has(cmd)) logPhase(`phase=${cmd}-start`);
252
- try {
253
- if (cmd === "render-config") {
254
- const spec = readJsonArg(rest[0]) as IngressSpec;
255
- process.stdout.write(renderConfigYml(spec));
256
- logPhase(`phase=${cmd}-ok`);
257
- } else if (cmd === "render-state") {
258
- const state = readJsonArg(rest[0]) as TunnelState;
259
- process.stdout.write(renderTunnelState(state));
260
- logPhase(`phase=${cmd}-ok`);
261
- } else if (cmd === "read-state") {
262
- const persisted = readPersistedHostnames(rest[0]);
263
- process.stdout.write(JSON.stringify(persisted));
264
- } else if (cmd === "probe-samba") {
265
- const brand = rest[0];
266
- const smbConf = rest[1] ?? "/etc/samba/smb.conf";
267
- const present = probeSambaStanza(brand, smbConf);
268
- logPhase(`phase=${cmd}-ok brand=${brand} present=${present}`);
269
- process.exit(present ? 0 : 1);
270
- } else if (cmd === "action-req") {
271
- const input = readJsonArg(rest[0]) as {
272
- sshHostname?: string | null;
273
- smbHostname?: string | null;
274
- operatorEmail: string;
275
- };
276
- process.stdout.write(renderAccessPolicyActionRequired(input));
277
- logPhase(`phase=${cmd}-ok`);
278
- } else {
279
- process.stderr.write(`tunnel-ingress: unknown subcommand: ${cmd}\n`);
280
- process.exit(2);
281
- }
282
- } catch (err) {
283
- const msg = (err as Error).message;
284
- if (instrumented.has(cmd)) {
285
- const detail = msg.slice(0, 200).replace(/"/g, "'");
286
- logPhase(`phase=${cmd}-fail error="${detail}"`);
287
- }
288
- process.stderr.write(`tunnel-ingress: ${msg}\n`);
289
- process.exit(1);
290
- }
291
- }