@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,241 +0,0 @@
1
- // acceptance grid for tunnel-ingress.ts pure layer.
2
- //
3
- // Runs under `node --test --experimental-strip-types` (no compile step). No
4
- // filesystem reads except via temp files written by the test itself; no
5
- // network; no shell-outs. Mirrors apt-resolve / samba-provision style.
6
-
7
- import test from "node:test";
8
- import assert from "node:assert/strict";
9
- import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
10
- import { join } from "node:path";
11
- import { tmpdir } from "node:os";
12
- import {
13
- renderConfigYml,
14
- renderTunnelState,
15
- readPersistedHostnames,
16
- probeSambaStanza,
17
- renderAccessPolicyActionRequired,
18
- type IngressSpec,
19
- type TunnelState,
20
- } from "../tunnel-ingress.ts";
21
-
22
- // ---------------------------------------------------------------------------
23
- // renderConfigYml
24
- // ---------------------------------------------------------------------------
25
-
26
- test("renderConfigYml: HTTPS-only matches expected shape byte-for-byte", () => {
27
- const spec: IngressSpec = {
28
- tunnelId: "abc-123",
29
- credentialsPath: "/home/admin/.maxy/cloudflared/abc-123.json",
30
- httpPort: 19200,
31
- httpHostnames: ["admin.maxy.bot", "public.maxy.bot"],
32
- };
33
- assert.equal(
34
- renderConfigYml(spec),
35
- [
36
- "tunnel: abc-123",
37
- "credentials-file: /home/admin/.maxy/cloudflared/abc-123.json",
38
- "ingress:",
39
- " - hostname: admin.maxy.bot",
40
- " service: http://localhost:19200",
41
- " - hostname: public.maxy.bot",
42
- " service: http://localhost:19200",
43
- " - service: http_status:404",
44
- "",
45
- ].join("\n"),
46
- );
47
- });
48
-
49
- test("renderConfigYml: SSH+SMB appear AFTER http hostnames and BEFORE catch-all", () => {
50
- const spec: IngressSpec = {
51
- tunnelId: "t-id",
52
- credentialsPath: "/creds.json",
53
- httpPort: 19200,
54
- httpHostnames: ["admin.maxy.bot"],
55
- sshHostname: "ssh.maxy.bot",
56
- smbHostname: "smb.maxy.bot",
57
- };
58
- const out = renderConfigYml(spec);
59
- const idxHttp = out.indexOf("admin.maxy.bot");
60
- const idxSsh = out.indexOf("ssh.maxy.bot");
61
- const idxSmb = out.indexOf("smb.maxy.bot");
62
- const idx404 = out.indexOf("http_status:404");
63
- assert.ok(idxHttp < idxSsh, "HTTP before SSH");
64
- assert.ok(idxSsh < idxSmb, "SSH before SMB");
65
- assert.ok(idxSmb < idx404, "SMB before catch-all 404");
66
- assert.match(out, /\s+service: ssh:\/\/localhost:22$/m);
67
- assert.match(out, /\s+service: tcp:\/\/localhost:445$/m);
68
- });
69
-
70
- test("renderConfigYml: SSH only — no SMB block when smbHostname is null", () => {
71
- const spec: IngressSpec = {
72
- tunnelId: "t-id",
73
- credentialsPath: "/creds.json",
74
- httpPort: 19200,
75
- httpHostnames: ["admin.maxy.bot"],
76
- sshHostname: "ssh.maxy.bot",
77
- smbHostname: null,
78
- };
79
- const out = renderConfigYml(spec);
80
- assert.ok(out.includes("ssh.maxy.bot"));
81
- assert.ok(!out.includes("tcp://localhost:445"));
82
- });
83
-
84
- test("renderConfigYml: empty string hostnames are treated as absent", () => {
85
- const spec: IngressSpec = {
86
- tunnelId: "t-id",
87
- credentialsPath: "/creds.json",
88
- httpPort: 19200,
89
- httpHostnames: ["admin.maxy.bot"],
90
- sshHostname: "",
91
- smbHostname: "",
92
- };
93
- const out = renderConfigYml(spec);
94
- assert.ok(!out.includes("ssh://localhost:22"));
95
- assert.ok(!out.includes("tcp://localhost:445"));
96
- });
97
-
98
- // ---------------------------------------------------------------------------
99
- // renderTunnelState
100
- // ---------------------------------------------------------------------------
101
-
102
- test("renderTunnelState: base fields preserved when ssh/smb absent", () => {
103
- const state: TunnelState = {
104
- tunnelId: "abc",
105
- tunnelName: "maxy",
106
- domain: "admin.maxy.bot",
107
- configPath: "/home/admin/.maxy/cloudflared/config.yml",
108
- credentialsPath: "/home/admin/.maxy/cloudflared/abc.json",
109
- };
110
- const parsed = JSON.parse(renderTunnelState(state));
111
- assert.deepEqual(parsed, {
112
- tunnelId: "abc",
113
- tunnelName: "maxy",
114
- domain: "admin.maxy.bot",
115
- configPath: "/home/admin/.maxy/cloudflared/config.yml",
116
- credentialsPath: "/home/admin/.maxy/cloudflared/abc.json",
117
- });
118
- });
119
-
120
- test("renderTunnelState: ssh/smb hostnames persist when present", () => {
121
- const state: TunnelState = {
122
- tunnelId: "abc",
123
- tunnelName: "maxy",
124
- domain: "admin.maxy.bot",
125
- configPath: "/cfg.yml",
126
- credentialsPath: "/creds.json",
127
- sshHostname: "ssh.maxy.bot",
128
- smbHostname: "smb.maxy.bot",
129
- };
130
- const parsed = JSON.parse(renderTunnelState(state));
131
- assert.equal(parsed.sshHostname, "ssh.maxy.bot");
132
- assert.equal(parsed.smbHostname, "smb.maxy.bot");
133
- });
134
-
135
- // ---------------------------------------------------------------------------
136
- // readPersistedHostnames
137
- // ---------------------------------------------------------------------------
138
-
139
- test("readPersistedHostnames: returns nulls when file missing", () => {
140
- const got = readPersistedHostnames("/nope/does/not/exist.json");
141
- assert.deepEqual(got, { sshHostname: null, smbHostname: null });
142
- });
143
-
144
- test("readPersistedHostnames: round-trips ssh/smb hostnames from disk", () => {
145
- const dir = mkdtempSync(join(tmpdir(), "tunnel-state-"));
146
- const path = join(dir, "tunnel.state");
147
- try {
148
- writeFileSync(
149
- path,
150
- JSON.stringify({
151
- tunnelId: "abc",
152
- tunnelName: "maxy",
153
- domain: "admin.maxy.bot",
154
- configPath: "/cfg.yml",
155
- credentialsPath: "/creds.json",
156
- sshHostname: "ssh.maxy.bot",
157
- smbHostname: "smb.maxy.bot",
158
- }),
159
- );
160
- const got = readPersistedHostnames(path);
161
- assert.deepEqual(got, { sshHostname: "ssh.maxy.bot", smbHostname: "smb.maxy.bot" });
162
- } finally {
163
- rmSync(dir, { recursive: true, force: true });
164
- }
165
- });
166
-
167
- test("readPersistedHostnames: malformed JSON returns nulls (no throw)", () => {
168
- const dir = mkdtempSync(join(tmpdir(), "tunnel-state-bad-"));
169
- const path = join(dir, "tunnel.state");
170
- try {
171
- writeFileSync(path, "not json");
172
- assert.deepEqual(readPersistedHostnames(path), { sshHostname: null, smbHostname: null });
173
- } finally {
174
- rmSync(dir, { recursive: true, force: true });
175
- }
176
- });
177
-
178
- // ---------------------------------------------------------------------------
179
- // probeSambaStanza
180
- // ---------------------------------------------------------------------------
181
-
182
- test("probeSambaStanza: true when [<brand>] header present", () => {
183
- const dir = mkdtempSync(join(tmpdir(), "smb-probe-"));
184
- const path = join(dir, "smb.conf");
185
- try {
186
- writeFileSync(path, "[global]\nworkgroup = WORKGROUP\n\n[maxy]\n path = /home/admin\n");
187
- assert.equal(probeSambaStanza("maxy", path), true);
188
- } finally {
189
- rmSync(dir, { recursive: true, force: true });
190
- }
191
- });
192
-
193
- test("probeSambaStanza: false when only peer brand present (isolation)", () => {
194
- const dir = mkdtempSync(join(tmpdir(), "smb-probe-peer-"));
195
- const path = join(dir, "smb.conf");
196
- try {
197
- writeFileSync(path, "[global]\n\n[realagent]\n path = /home/admin\n");
198
- assert.equal(probeSambaStanza("maxy", path), false);
199
- } finally {
200
- rmSync(dir, { recursive: true, force: true });
201
- }
202
- });
203
-
204
- test("probeSambaStanza: false when smb.conf missing", () => {
205
- assert.equal(probeSambaStanza("maxy", "/nope/smb.conf"), false);
206
- });
207
-
208
- test("probeSambaStanza: brand names with regex meta characters are escaped", () => {
209
- const dir = mkdtempSync(join(tmpdir(), "smb-probe-regex-"));
210
- const path = join(dir, "smb.conf");
211
- try {
212
- writeFileSync(path, "[some.brand]\n path = /x\n");
213
- assert.equal(probeSambaStanza("some.brand", path), true);
214
- assert.equal(probeSambaStanza("someXbrand", path), false);
215
- } finally {
216
- rmSync(dir, { recursive: true, force: true });
217
- }
218
- });
219
-
220
- // ---------------------------------------------------------------------------
221
- // renderAccessPolicyActionRequired
222
- // ---------------------------------------------------------------------------
223
-
224
- test("renderAccessPolicyActionRequired: empty string when neither hostname set", () => {
225
- assert.equal(
226
- renderAccessPolicyActionRequired({ operatorEmail: "x@y.com" }),
227
- "",
228
- );
229
- });
230
-
231
- test("renderAccessPolicyActionRequired: lists each hostname with operator email", () => {
232
- const out = renderAccessPolicyActionRequired({
233
- sshHostname: "ssh.maxy.bot",
234
- smbHostname: "smb.maxy.bot",
235
- operatorEmail: "joel@example.com",
236
- });
237
- assert.match(out, /SSH: ssh\.maxy\.bot/);
238
- assert.match(out, /SMB: smb\.maxy\.bot/);
239
- assert.match(out, /Emails: joel@example\.com/);
240
- assert.match(out, /Zero Trust → Access → Applications/);
241
- });
@@ -1,98 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Deterministic scrape of the operator's Cloudflare dashboard to discover
3
- # the domains on the logged-in account. Output on stdout is a JSON `string[]`,
4
- # sorted and deduped. Every failure exits non-zero with a `reason=<enum>`
5
- # token on stderr tagged `[list-cf-domains]`.
6
- #
7
- # Usage:
8
- # list-cf-domains.sh <brand>
9
- #
10
- # brand arg is REQUIRED — the prior silent default (string
11
- # fallback to "maxy" when $1 was empty) made every non-Maxy brand's CF
12
- # setup form fail on first use because the wrapper's child node helper
13
- # read the Maxy CDP port from a hardcoded fallback. Missing arg now exits
14
- # 1 with `phase=error reason=brand-arg-missing`.
15
- #
16
- # The wrapper also resolves and exports MAXY_PLATFORM_ROOT from its own
17
- # script location so the .ts helper can read `<root>/config/brand.json` for
18
- # the brand's `cdpPort` (the source of truth). Direct-SSH invocation
19
- # works because the script lives at `<install>/platform/plugins/cloudflare/
20
- # scripts/`, making platform root three dirs up from the resolved script.
21
- #
22
- # Runtime: Node 22's `--experimental-strip-types` runs the .ts helper
23
- # directly, so no tsx / playwright / ws dependency exists.
24
-
25
- set -euo pipefail
26
-
27
- # Resolve symlinks before dirname — ~/list-cf-domains.sh is installed as a
28
- # symlink into $HOME, so the raw BASH_SOURCE[0] points at $HOME, not the
29
- # scripts directory.
30
- SCRIPT_DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"
31
-
32
- # Phase-line helper. Stream-log plumbing retired in Task 287 — every
33
- # observation goes straight to stdout for the PTY to render.
34
- phase_line() {
35
- local scope=$1
36
- shift
37
- printf '[%s] %s\n' "${scope}" "$*"
38
- }
39
-
40
- if [ "$#" -lt 1 ] || [ -z "${1:-}" ]; then
41
- phase_line list-cf-domains phase=error reason=brand-arg-missing
42
- exit 1
43
- fi
44
-
45
- BRAND="${1}"
46
- CONFIG_DIR=".${BRAND}"
47
- mkdir -p "${HOME}/${CONFIG_DIR}/logs"
48
-
49
- # MAXY_PLATFORM_ROOT may be set by the calling environment. Direct-from-Bash
50
- # invocation has no such env so derive it from the resolved script location:
51
- # scripts/ → cloudflare/ → plugins/ → platform → install root (three dirs up).
52
- if [ -z "${MAXY_PLATFORM_ROOT:-}" ]; then
53
- MAXY_PLATFORM_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
54
- fi
55
-
56
- phase_line list-cf-domains phase=script-start brand="${BRAND}" config_dir="${CONFIG_DIR}"
57
-
58
- TS_ENTRY="${SCRIPT_DIR}/list-cf-domains.ts"
59
-
60
- if [ ! -f "${TS_ENTRY}" ]; then
61
- phase_line list-cf-domains phase=error reason=entry-missing path="${TS_ENTRY}"
62
- exit 1
63
- fi
64
-
65
- # 30s hard ceiling. The node helper has its own per-phase budgets totalling
66
- # ~27s; the wrapper's extra 3s absorbs process startup and CF network jitter.
67
- # Use `timeout --preserve-status` so the exit code of a timeout is distinct
68
- # (124) from a helper-reported failure (1).
69
- HARD_TIMEOUT_SECS=30
70
-
71
- # Env passed through:
72
- # - CONFIG_DIR — consumed by the node helper when writing selector-drift
73
- # dumps to ~/.${BRAND}/logs/list-cf-domains-<ts>.html.
74
- # - BRAND — names the brand for log lines naming the brand on
75
- # config-class failures (helper does not derive it from
76
- # CONFIG_DIR to keep the two concerns independent).
77
- # - MAXY_PLATFORM_ROOT — install root the helper reads `config/brand.json`
78
- # from for `cdpPort`. Resolved above so direct-SSH
79
- # invocation matches the systemd-spawned path.
80
- #
81
- # `node --experimental-strip-types` (stable in Node 22.22) runs the .ts file
82
- # natively: type annotations are stripped, no enums / decorators are used, so
83
- # no TS type-transform is needed. `--no-warnings` suppresses the experimental
84
- # banner which would otherwise leak into stderr and confuse the route parser.
85
- set +e
86
- CONFIG_DIR="${CONFIG_DIR}" BRAND="${BRAND}" MAXY_PLATFORM_ROOT="${MAXY_PLATFORM_ROOT}" \
87
- timeout --preserve-status --signal=TERM "${HARD_TIMEOUT_SECS}" \
88
- node --experimental-strip-types --no-warnings "${TS_ENTRY}"
89
- EXIT_CODE=$?
90
- set -e
91
-
92
- if [ "${EXIT_CODE}" -eq 124 ]; then
93
- phase_line list-cf-domains phase=script-exit code=124 reason=wrapper-timeout budget_secs="${HARD_TIMEOUT_SECS}"
94
- exit 1
95
- fi
96
-
97
- phase_line list-cf-domains phase=script-exit code="${EXIT_CODE}"
98
- exit "${EXIT_CODE}"