@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.
- package/dist/index.js +50 -150
- package/dist/snap-chromium.js +1 -2
- package/dist/uninstall.js +10 -9
- package/package.json +1 -1
- package/payload/platform/neo4j/schema.cypher +6 -3
- package/payload/platform/plugins/admin/PLUGIN.md +1 -0
- package/payload/platform/plugins/admin/mcp/dist/index.js +1 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/skills/upgrade/SKILL.md +34 -0
- package/payload/platform/plugins/cloudflare/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/cloudflare/PLUGIN.md +9 -16
- package/payload/platform/plugins/cloudflare/mcp/dist/index.js +7 -12
- package/payload/platform/plugins/cloudflare/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/cloudflare/references/dashboard-guide.md +3 -3
- package/payload/platform/plugins/cloudflare/references/manual-setup.md +19 -54
- package/payload/platform/plugins/cloudflare/references/reset-guide.md +29 -28
- package/payload/platform/plugins/cloudflare/skills/setup-tunnel/SKILL.md +29 -149
- package/payload/platform/plugins/docs/references/admin-session.md +1 -1
- package/payload/platform/plugins/docs/references/admin-ui.md +1 -1
- package/payload/platform/plugins/docs/references/cloudflare.md +20 -29
- package/payload/platform/plugins/docs/references/platform.md +1 -1
- package/payload/platform/plugins/docs/references/plugins-guide.md +1 -1
- package/payload/platform/plugins/docs/references/troubleshooting.md +1 -1
- package/payload/platform/scripts/check-no-task-id-leaks.mjs +1 -1
- package/payload/platform/templates/agents/admin/IDENTITY.md +1 -1
- package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
- package/payload/platform/plugins/cloudflare/scripts/__tests__/tunnel-ingress.test.ts +0 -241
- package/payload/platform/plugins/cloudflare/scripts/list-cf-domains.sh +0 -98
- package/payload/platform/plugins/cloudflare/scripts/list-cf-domains.ts +0 -715
- package/payload/platform/plugins/cloudflare/scripts/reset-tunnel.sh +0 -107
- package/payload/platform/plugins/cloudflare/scripts/setup-tunnel.sh +0 -852
- 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}"
|