@rubytech/create-realagent 1.0.616 → 1.0.617
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/config/brand.json +0 -4
- package/payload/platform/package-lock.json +1547 -1
- package/payload/platform/plugins/cloudflare/PLUGIN.md +2 -2
- package/payload/platform/plugins/cloudflare/mcp/__tests__/auth-binding.test.ts +0 -1
- package/payload/platform/plugins/cloudflare/mcp/dist/index.js +23 -98
- package/payload/platform/plugins/cloudflare/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts +94 -68
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts.map +1 -1
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js +260 -529
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js.map +1 -1
- package/payload/platform/plugins/cloudflare/references/setup-guide.md +10 -13
- package/payload/platform/plugins/cloudflare/skills/setup-tunnel/SKILL.md +16 -14
- package/payload/platform/plugins/docs/references/cloudflare.md +31 -48
- package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
- package/payload/server/server.js +1 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/brand-load.test.ts +0 -81
- package/payload/platform/plugins/cloudflare/mcp/__tests__/manifest-scope.test.ts +0 -65
- package/payload/platform/plugins/cloudflare/mcp/__tests__/verify-scenario-0.test.ts +0 -70
- package/payload/platform/plugins/cloudflare/mcp/__tests__/verify-scenario-B.test.ts +0 -124
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
-
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { _resetBrandCache, loadBrand } from "../src/lib/cloudflared.js";
|
|
6
|
-
|
|
7
|
-
// loadBrand() reads PLATFORM_ROOT/config/brand.json. We point PLATFORM_ROOT at
|
|
8
|
-
// a fresh tmpdir per test, write the brand.json shape we want, reset the
|
|
9
|
-
// module-level cache, then assert load behaviour.
|
|
10
|
-
|
|
11
|
-
let tmpRoot: string;
|
|
12
|
-
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
tmpRoot = mkdtempSync(join(tmpdir(), "maxy-cf-brand-"));
|
|
15
|
-
process.env.PLATFORM_ROOT = tmpRoot;
|
|
16
|
-
_resetBrandCache();
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
afterEach(() => {
|
|
20
|
-
rmSync(tmpRoot, { recursive: true, force: true });
|
|
21
|
-
delete process.env.PLATFORM_ROOT;
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
function writeBrand(content: object): void {
|
|
25
|
-
const dir = join(tmpRoot, "config");
|
|
26
|
-
mkdirSync(dir, { recursive: true });
|
|
27
|
-
writeFileSync(join(dir, "brand.json"), JSON.stringify(content));
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
describe("loadBrand cloudflare.zones validation", () => {
|
|
31
|
-
it("loads when cloudflare.zones is present", () => {
|
|
32
|
-
writeBrand({
|
|
33
|
-
productName: "Maxy",
|
|
34
|
-
configDir: ".maxy",
|
|
35
|
-
cloudflare: { zones: ["maxy.bot", "maxy.chat"] },
|
|
36
|
-
});
|
|
37
|
-
const brand = loadBrand();
|
|
38
|
-
expect(brand.cloudflare.zones).toEqual(["maxy.bot", "maxy.chat"]);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("hard-fails when cloudflare key is absent", () => {
|
|
42
|
-
writeBrand({ productName: "Maxy", configDir: ".maxy" });
|
|
43
|
-
expect(() => loadBrand()).toThrow(/cloudflare\.zones/);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it("hard-fails when cloudflare.zones is empty", () => {
|
|
47
|
-
writeBrand({
|
|
48
|
-
productName: "Maxy",
|
|
49
|
-
configDir: ".maxy",
|
|
50
|
-
cloudflare: { zones: [] },
|
|
51
|
-
});
|
|
52
|
-
expect(() => loadBrand()).toThrow(/cloudflare\.zones/);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("hard-fails when cloudflare.zones is not an array", () => {
|
|
56
|
-
writeBrand({
|
|
57
|
-
productName: "Maxy",
|
|
58
|
-
configDir: ".maxy",
|
|
59
|
-
cloudflare: { zones: "maxy.bot" },
|
|
60
|
-
});
|
|
61
|
-
expect(() => loadBrand()).toThrow(/cloudflare\.zones/);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it("hard-fails when cloudflare.zones contains a non-string", () => {
|
|
65
|
-
writeBrand({
|
|
66
|
-
productName: "Maxy",
|
|
67
|
-
configDir: ".maxy",
|
|
68
|
-
cloudflare: { zones: ["maxy.bot", 42] },
|
|
69
|
-
});
|
|
70
|
-
expect(() => loadBrand()).toThrow(/invalid cloudflare\.zones entry/);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it("hard-fails when PLATFORM_ROOT is unset", () => {
|
|
74
|
-
delete process.env.PLATFORM_ROOT;
|
|
75
|
-
expect(() => loadBrand()).toThrow(/PLATFORM_ROOT/);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it("hard-fails when brand.json is absent at the configured path", () => {
|
|
79
|
-
expect(() => loadBrand()).toThrow(/brand\.json not found/);
|
|
80
|
-
});
|
|
81
|
-
});
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { matchManifestZone } from "../src/lib/cloudflared.js";
|
|
3
|
-
|
|
4
|
-
// Pure function — no fs, no network. Tests the registrable-parent suffix-match
|
|
5
|
-
// against an explicit declared-zone list. The whole point of declarative zones
|
|
6
|
-
// is that we never need a PSL lookup or a "last two labels" heuristic.
|
|
7
|
-
|
|
8
|
-
describe("matchManifestZone", () => {
|
|
9
|
-
const zones = ["maxy.bot", "maxy.chat", "example.co.uk"];
|
|
10
|
-
|
|
11
|
-
it("matches a hostname whose registrable parent is in the zone list", () => {
|
|
12
|
-
const r = matchManifestZone("admin.maxy.bot", zones);
|
|
13
|
-
expect(r.ok).toBe(true);
|
|
14
|
-
expect(r.matchedZone).toBe("maxy.bot");
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it("matches the zone itself (no subdomain)", () => {
|
|
18
|
-
const r = matchManifestZone("maxy.bot", zones);
|
|
19
|
-
expect(r.ok).toBe(true);
|
|
20
|
-
expect(r.matchedZone).toBe("maxy.bot");
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it("matches deeper subdomains", () => {
|
|
24
|
-
const r = matchManifestZone("foo.bar.maxy.bot", zones);
|
|
25
|
-
expect(r.ok).toBe(true);
|
|
26
|
-
expect(r.matchedZone).toBe("maxy.bot");
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it("matches case-insensitively", () => {
|
|
30
|
-
const r = matchManifestZone("Admin.MAXY.BOT", zones);
|
|
31
|
-
expect(r.ok).toBe(true);
|
|
32
|
-
expect(r.matchedZone).toBe("maxy.bot");
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("refuses hostnames outside the zone list", () => {
|
|
36
|
-
const r = matchManifestZone("admin.example.org", zones);
|
|
37
|
-
expect(r.ok).toBe(false);
|
|
38
|
-
expect(r.matchedZone).toBeNull();
|
|
39
|
-
expect(r.declaredZones).toEqual(zones);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it("does not partial-match — admin.notmaxy.bot must not match maxy.bot", () => {
|
|
43
|
-
// The leading-dot boundary is what prevents this.
|
|
44
|
-
const r = matchManifestZone("admin.notmaxy.bot", zones);
|
|
45
|
-
expect(r.ok).toBe(false);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("handles multi-label TLDs (.co.uk) when declared", () => {
|
|
49
|
-
const r = matchManifestZone("admin.example.co.uk", zones);
|
|
50
|
-
expect(r.ok).toBe(true);
|
|
51
|
-
expect(r.matchedZone).toBe("example.co.uk");
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("prefers the longest match when zones overlap", () => {
|
|
55
|
-
const overlap = ["bot", "maxy.bot"];
|
|
56
|
-
const r = matchManifestZone("admin.maxy.bot", overlap);
|
|
57
|
-
expect(r.ok).toBe(true);
|
|
58
|
-
expect(r.matchedZone).toBe("maxy.bot");
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it("refuses against an empty zone list", () => {
|
|
62
|
-
const r = matchManifestZone("admin.maxy.bot", []);
|
|
63
|
-
expect(r.ok).toBe(false);
|
|
64
|
-
});
|
|
65
|
-
});
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
-
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import {
|
|
6
|
-
_resetBrandCache,
|
|
7
|
-
cfVerifyCore,
|
|
8
|
-
liveScopeContext,
|
|
9
|
-
} from "../src/lib/cloudflared.js";
|
|
10
|
-
|
|
11
|
-
// Scenario 0 (per task spec): fresh install — no cert.pem, no binding,
|
|
12
|
-
// no tunnels, no config.yml. cf-verify must return zero OUT-OF-SCOPE,
|
|
13
|
-
// every declared zone tagged MISSING, no throws. Proves the audit runs
|
|
14
|
-
// before any login completes.
|
|
15
|
-
|
|
16
|
-
let tmpRoot: string;
|
|
17
|
-
let homeRoot: string;
|
|
18
|
-
let originalHome: string | undefined;
|
|
19
|
-
|
|
20
|
-
beforeEach(() => {
|
|
21
|
-
tmpRoot = mkdtempSync(join(tmpdir(), "maxy-cf-verify-"));
|
|
22
|
-
homeRoot = mkdtempSync(join(tmpdir(), "maxy-cf-home-"));
|
|
23
|
-
originalHome = process.env.HOME;
|
|
24
|
-
process.env.HOME = homeRoot;
|
|
25
|
-
process.env.PLATFORM_ROOT = tmpRoot;
|
|
26
|
-
_resetBrandCache();
|
|
27
|
-
const cfg = join(tmpRoot, "config");
|
|
28
|
-
mkdirSync(cfg, { recursive: true });
|
|
29
|
-
writeFileSync(
|
|
30
|
-
join(cfg, "brand.json"),
|
|
31
|
-
JSON.stringify({
|
|
32
|
-
productName: "Maxy",
|
|
33
|
-
configDir: ".maxy",
|
|
34
|
-
cloudflare: { zones: ["b.test", "c.test"] },
|
|
35
|
-
}),
|
|
36
|
-
);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
afterEach(() => {
|
|
40
|
-
if (originalHome !== undefined) process.env.HOME = originalHome;
|
|
41
|
-
else delete process.env.HOME;
|
|
42
|
-
delete process.env.PLATFORM_ROOT;
|
|
43
|
-
rmSync(tmpRoot, { recursive: true, force: true });
|
|
44
|
-
rmSync(homeRoot, { recursive: true, force: true });
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
describe("cfVerifyCore — Scenario 0 (fresh install, no login)", () => {
|
|
48
|
-
it("runs without throwing and reports everything as MISSING", async () => {
|
|
49
|
-
const ctx = liveScopeContext();
|
|
50
|
-
const report = await cfVerifyCore(ctx);
|
|
51
|
-
|
|
52
|
-
expect(report.brand).toBe("Maxy");
|
|
53
|
-
expect(report.declaredZones).toEqual(["b.test", "c.test"]);
|
|
54
|
-
expect(report.bindingPresent).toBe(false);
|
|
55
|
-
expect(report.bindingMatchesCert).toBe(false);
|
|
56
|
-
expect(report.certPresent).toBe(false);
|
|
57
|
-
|
|
58
|
-
// No OUT-OF-SCOPE artefacts on a clean fresh device.
|
|
59
|
-
expect(report.counts.outOfScope).toBe(0);
|
|
60
|
-
|
|
61
|
-
// Cert, binding, tunnel.state, and each declared zone all MISSING.
|
|
62
|
-
const missingTypes = report.artefacts
|
|
63
|
-
.filter((a) => a.tag === "missing")
|
|
64
|
-
.map((a) => a.type);
|
|
65
|
-
expect(missingTypes).toContain("cert.pem");
|
|
66
|
-
expect(missingTypes).toContain("binding");
|
|
67
|
-
expect(missingTypes).toContain("tunnel.state");
|
|
68
|
-
expect(missingTypes.filter((t) => t === "declared-zone").length).toBe(2);
|
|
69
|
-
});
|
|
70
|
-
});
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
-
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import {
|
|
6
|
-
_resetBrandCache,
|
|
7
|
-
cfVerifyCore,
|
|
8
|
-
writeAccountBinding,
|
|
9
|
-
type ScopeContext,
|
|
10
|
-
} from "../src/lib/cloudflared.js";
|
|
11
|
-
|
|
12
|
-
// Scenario B (per task spec): stale tunnel state. Brand declares
|
|
13
|
-
// cloudflare.zones=["b.test"]. cert.pem and binding agree on account Y.
|
|
14
|
-
// But tunnel.state references a deleted tunnel, config.yml names a
|
|
15
|
-
// hostname under a zone NOT in declared scope, and alias-domains.json
|
|
16
|
-
// has an entry for removed.test (also out of scope).
|
|
17
|
-
//
|
|
18
|
-
// We bypass the SDK (no live calls) by passing a ScopeContext with
|
|
19
|
-
// client=null. The local-artefact analysis runs in full; the account-side
|
|
20
|
-
// analysis surfaces declared zones as MISSING (no client = nothing to
|
|
21
|
-
// enumerate), which is correct behaviour for this audit-only test.
|
|
22
|
-
|
|
23
|
-
let tmpRoot: string;
|
|
24
|
-
let homeRoot: string;
|
|
25
|
-
let originalHome: string | undefined;
|
|
26
|
-
const CONFIG_DIR = ".maxy";
|
|
27
|
-
|
|
28
|
-
function home(...p: string[]): string {
|
|
29
|
-
return join(homeRoot, ...p);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function writeCertPem(accountId: string, apiToken: string): void {
|
|
33
|
-
const certDir = home(CONFIG_DIR, "cloudflared");
|
|
34
|
-
mkdirSync(certDir, { recursive: true });
|
|
35
|
-
const tokenJson = JSON.stringify({ APIToken: apiToken, AccountTag: accountId });
|
|
36
|
-
const b64 = Buffer.from(tokenJson, "utf-8").toString("base64");
|
|
37
|
-
const pem = `-----BEGIN ARGO TUNNEL TOKEN-----
|
|
38
|
-
${b64}
|
|
39
|
-
-----END ARGO TUNNEL TOKEN-----
|
|
40
|
-
`;
|
|
41
|
-
writeFileSync(join(certDir, "cert.pem"), pem);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
beforeEach(() => {
|
|
45
|
-
tmpRoot = mkdtempSync(join(tmpdir(), "maxy-cf-verifyB-"));
|
|
46
|
-
homeRoot = mkdtempSync(join(tmpdir(), "maxy-cf-home-"));
|
|
47
|
-
originalHome = process.env.HOME;
|
|
48
|
-
process.env.HOME = homeRoot;
|
|
49
|
-
process.env.PLATFORM_ROOT = tmpRoot;
|
|
50
|
-
_resetBrandCache();
|
|
51
|
-
const cfg = join(tmpRoot, "config");
|
|
52
|
-
mkdirSync(cfg, { recursive: true });
|
|
53
|
-
writeFileSync(
|
|
54
|
-
join(cfg, "brand.json"),
|
|
55
|
-
JSON.stringify({
|
|
56
|
-
productName: "Maxy",
|
|
57
|
-
configDir: CONFIG_DIR,
|
|
58
|
-
cloudflare: { zones: ["b.test"] },
|
|
59
|
-
}),
|
|
60
|
-
);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
afterEach(() => {
|
|
64
|
-
if (originalHome !== undefined) process.env.HOME = originalHome;
|
|
65
|
-
else delete process.env.HOME;
|
|
66
|
-
delete process.env.PLATFORM_ROOT;
|
|
67
|
-
rmSync(tmpRoot, { recursive: true, force: true });
|
|
68
|
-
rmSync(homeRoot, { recursive: true, force: true });
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
describe("cfVerifyCore — Scenario B (stale tunnel state)", () => {
|
|
72
|
-
it("tags out-of-scope local artefacts even without a live SDK client", async () => {
|
|
73
|
-
writeCertPem("acct-Y", "cfut_dummy");
|
|
74
|
-
writeAccountBinding("acct-Y");
|
|
75
|
-
|
|
76
|
-
// Stale tunnel.state pointing at a deleted tunnel + off-scope hostname.
|
|
77
|
-
const cloudflaredDir = home(CONFIG_DIR, "cloudflared");
|
|
78
|
-
mkdirSync(cloudflaredDir, { recursive: true });
|
|
79
|
-
const configYmlPath = join(cloudflaredDir, "config.yml");
|
|
80
|
-
writeFileSync(configYmlPath, "tunnel: stale\n");
|
|
81
|
-
writeFileSync(
|
|
82
|
-
join(cloudflaredDir, "tunnel.state"),
|
|
83
|
-
JSON.stringify({
|
|
84
|
-
tunnelId: "stale-tunnel-id",
|
|
85
|
-
tunnelName: "stale",
|
|
86
|
-
domain: "removed.test", // NOT in brand.cloudflare.zones
|
|
87
|
-
configPath: configYmlPath,
|
|
88
|
-
credentialsPath: join(cloudflaredDir, "stale-tunnel-id.json"),
|
|
89
|
-
adminHostname: "admin.removed.test", // off-scope
|
|
90
|
-
publicHostname: null,
|
|
91
|
-
pid: null,
|
|
92
|
-
startedAt: null,
|
|
93
|
-
}),
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
// alias-domains entry for an off-scope hostname.
|
|
97
|
-
writeFileSync(
|
|
98
|
-
home(CONFIG_DIR, "alias-domains.json"),
|
|
99
|
-
JSON.stringify(["removed.test"]),
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
// Bypass the SDK — pass a ScopeContext with client=null. The audit
|
|
103
|
-
// tags every declared zone as MISSING (cannot enumerate without
|
|
104
|
-
// client) but completes the local-artefact analysis in full.
|
|
105
|
-
const ctx: ScopeContext = {
|
|
106
|
-
declaredZones: ["b.test"],
|
|
107
|
-
binding: { accountId: "acct-Y", boundAt: new Date().toISOString() },
|
|
108
|
-
client: null,
|
|
109
|
-
};
|
|
110
|
-
const report = await cfVerifyCore(ctx);
|
|
111
|
-
|
|
112
|
-
const tunnelState = report.artefacts.find((a) => a.type === "tunnel.state");
|
|
113
|
-
expect(tunnelState?.tag).toBe("out-of-scope");
|
|
114
|
-
expect(tunnelState?.reason).toContain("hostnames outside declared scope");
|
|
115
|
-
|
|
116
|
-
const aliasEntry = report.artefacts.find(
|
|
117
|
-
(a) => a.type === "alias-domain" && a.id === "removed.test",
|
|
118
|
-
);
|
|
119
|
-
expect(aliasEntry?.tag).toBe("out-of-scope");
|
|
120
|
-
|
|
121
|
-
// Counts: at least the two out-of-scope local artefacts identified above.
|
|
122
|
-
expect(report.counts.outOfScope).toBeGreaterThanOrEqual(2);
|
|
123
|
-
});
|
|
124
|
-
});
|