@rubytech/create-realagent 1.0.772 → 1.0.775
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 +1 -0
- package/payload/platform/lib/entitlement/PUBKEY-HASH.txt +1 -0
- package/payload/platform/lib/entitlement/dist/canonicalize.d.ts +26 -0
- package/payload/platform/lib/entitlement/dist/canonicalize.d.ts.map +1 -0
- package/payload/platform/lib/entitlement/dist/canonicalize.js +54 -0
- package/payload/platform/lib/entitlement/dist/canonicalize.js.map +1 -0
- package/payload/platform/lib/entitlement/dist/index.d.ts +76 -0
- package/payload/platform/lib/entitlement/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/entitlement/dist/index.js +293 -0
- package/payload/platform/lib/entitlement/dist/index.js.map +1 -0
- package/payload/platform/lib/entitlement/rubytech-pubkey.pem +3 -0
- package/payload/platform/package.json +2 -2
- package/payload/platform/plugins/admin/hooks/pre-tool-use.sh +32 -0
- package/payload/platform/plugins/admin/mcp/dist/index.js +140 -10
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/skills/business-profile/SKILL.md +5 -6
- package/payload/platform/plugins/admin/skills/onboarding/SKILL.md +5 -6
- package/payload/platform/plugins/admin/skills/plugin-management/SKILL.md +10 -5
- package/payload/platform/scripts/generate-entitlement-fixture.mjs +152 -0
- package/payload/server/chunk-2HBD6IRL.js +3242 -0
- package/payload/server/chunk-MIP54X7Q.js +3244 -0
- package/payload/server/chunk-PIMJJCOQ.js +9563 -0
- package/payload/server/chunk-TM3EQSID.js +9800 -0
- package/payload/server/client-pool-4MZN42GG.js +28 -0
- package/payload/server/client-pool-U3A5YUO7.js +28 -0
- package/payload/server/maxy-edge.js +2 -2
- package/payload/server/public/assets/{Checkbox-DEE8t2QO.js → Checkbox-C_KxaLc-.js} +1 -1
- package/payload/server/public/assets/{admin-CFttroHB.js → admin-xbKPR6ZI.js} +30 -30
- package/payload/server/public/assets/data-D23IzpJ2.js +1 -0
- package/payload/server/public/assets/graph-D2AS9zFS.js +1 -0
- package/payload/server/public/assets/{jsx-runtime-DSbkOE76.css → jsx-runtime-BZtBxBng.css} +1 -1
- package/payload/server/public/assets/{page-YUT5e7hL.js → page-CjTfZ3O6.js} +2 -2
- package/payload/server/public/assets/{page-ZATk95ZG.js → page-DEWgk_nR.js} +1 -1
- package/payload/server/public/assets/{public-BLi3J8KU.js → public-CehiL-qZ.js} +1 -1
- package/payload/server/public/assets/{share-2-DS7Pnkkq.js → share-2-BG1VXt3z.js} +1 -1
- package/payload/server/public/assets/{useVoiceRecorder-pEHqS1ib.js → useVoiceRecorder-1Dvb-yHn.js} +1 -1
- package/payload/server/public/data.html +5 -5
- package/payload/server/public/graph.html +6 -6
- package/payload/server/public/index.html +8 -8
- package/payload/server/public/public.html +5 -5
- package/payload/server/server.js +52 -17
- package/payload/server/public/assets/data-ryPag-T-.js +0 -1
- package/payload/server/public/assets/graph-BPnH-UZB.js +0 -1
- /package/payload/server/public/assets/{jsx-runtime-DeNudFNA.js → jsx-runtime-DrneHL3t.js} +0 -0
|
@@ -10,6 +10,7 @@ import { appendFileSync, cpSync, existsSync, mkdirSync, readdirSync, readFileSyn
|
|
|
10
10
|
import { writeKey, validateKey, hasKey, keyFilePath, deleteKey } from "../../../../lib/anthropic-key/dist/index.js";
|
|
11
11
|
import { deviceUrlBlock } from "../../../../lib/device-url/dist/index.js";
|
|
12
12
|
import { substituteBrandPlaceholders } from "../../../../lib/brand-templating/dist/index.js";
|
|
13
|
+
import { resolveEntitlement } from "../../../../lib/entitlement/dist/index.js";
|
|
13
14
|
import { createHash, randomInt, randomUUID } from "node:crypto";
|
|
14
15
|
import { createConnection } from "node:net";
|
|
15
16
|
import { homedir, hostname as osHostname } from "node:os";
|
|
@@ -30,7 +31,10 @@ const PLATFORM_PORT = process.env.PLATFORM_PORT;
|
|
|
30
31
|
if (!PLATFORM_PORT) {
|
|
31
32
|
throw new Error("PLATFORM_PORT environment variable is required — set by getMcpServers() in claude-agent.ts");
|
|
32
33
|
}
|
|
33
|
-
// Brand-aware config — reads configDir and
|
|
34
|
+
// Brand-aware config — reads configDir, productName, and commercialMode from
|
|
35
|
+
// brand.json stamped at install time. commercialMode (Task 831) gates the
|
|
36
|
+
// entitlement verifier: false (default) preserves personal-mode installs;
|
|
37
|
+
// true requires a Rubytech-signed entitlement.json or the install runs locked.
|
|
34
38
|
// No fallback: if brand.json is missing or incomplete, the platform wasn't properly installed.
|
|
35
39
|
function resolveBrandConfig() {
|
|
36
40
|
const brandPath = resolve(PLATFORM_ROOT, "config", "brand.json");
|
|
@@ -45,7 +49,11 @@ function resolveBrandConfig() {
|
|
|
45
49
|
if (!brand.productName) {
|
|
46
50
|
throw new Error(`brand.json at ${brandPath} is missing the productName field`);
|
|
47
51
|
}
|
|
48
|
-
return {
|
|
52
|
+
return {
|
|
53
|
+
configDir: brand.configDir,
|
|
54
|
+
productName: brand.productName,
|
|
55
|
+
commercialMode: brand.commercialMode === true,
|
|
56
|
+
};
|
|
49
57
|
}
|
|
50
58
|
catch (err) {
|
|
51
59
|
if (err instanceof SyntaxError) {
|
|
@@ -57,6 +65,27 @@ function resolveBrandConfig() {
|
|
|
57
65
|
const BRAND_CONFIG = resolveBrandConfig();
|
|
58
66
|
const CONFIG_DIR = resolve(homedir(), BRAND_CONFIG.configDir);
|
|
59
67
|
const BRAND_NAME = BRAND_CONFIG.productName;
|
|
68
|
+
// Entitlement input shape for the verifier. configDir is rooted under $HOME
|
|
69
|
+
// (where entitlement.json is delivered post-purchase). platformRoot is needed
|
|
70
|
+
// because the verifier runs in two bundle contexts (CJS dist for MCP, ESM tsup
|
|
71
|
+
// bundle for UI) — caller passes the root rather than the verifier guessing.
|
|
72
|
+
const ENTITLEMENT_BRAND = {
|
|
73
|
+
configDir: CONFIG_DIR,
|
|
74
|
+
platformRoot: PLATFORM_ROOT,
|
|
75
|
+
commercialMode: BRAND_CONFIG.commercialMode,
|
|
76
|
+
};
|
|
77
|
+
/** Resolve current effective entitlement. Memoized inside the verifier. */
|
|
78
|
+
async function currentEntitlement() {
|
|
79
|
+
const config = await readAccountConfig();
|
|
80
|
+
return resolveEntitlement(ENTITLEMENT_BRAND, {
|
|
81
|
+
accountId: typeof config.accountId === "string" ? config.accountId : "",
|
|
82
|
+
customerEmail: typeof config.customerEmail === "string" ? config.customerEmail : undefined,
|
|
83
|
+
tier: typeof config.tier === "string" ? config.tier : undefined,
|
|
84
|
+
purchasedPlugins: Array.isArray(config.purchasedPlugins)
|
|
85
|
+
? config.purchasedPlugins
|
|
86
|
+
: undefined,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
60
89
|
const REMOTE_PASSWORD_FILE = resolve(CONFIG_DIR, ".remote-password");
|
|
61
90
|
// Resolve account directory
|
|
62
91
|
function getAccountDir() {
|
|
@@ -542,6 +571,53 @@ server.tool("account-update", "Update a user-configurable setting in account.jso
|
|
|
542
571
|
};
|
|
543
572
|
}
|
|
544
573
|
});
|
|
574
|
+
// Plugin enable/disable: deterministic write to account.json.enabledPlugins.
|
|
575
|
+
// The pre-tool-use hook denies the agent's direct Edit on account.json (Task 831),
|
|
576
|
+
// so the agent can no longer toggle enablement by hand-editing the file. This
|
|
577
|
+
// tool is the legitimate path: validates the plugin name, refuses core plugins,
|
|
578
|
+
// confirms the plugin directory exists, and atomically updates the array.
|
|
579
|
+
// Entitlement-bearing fields (tier, purchasedPlugins) are NOT writable here.
|
|
580
|
+
server.tool("plugin-toggle-enabled", "Enable or disable a plugin in this account by adding/removing its name from account.json's enabledPlugins array. Validates the plugin exists under platform/plugins/, refuses to disable core plugins (admin, memory, docs, cloudflare, anthropic), and writes atomically. Takes effect on next session start. Does NOT change purchasedPlugins or tier — those derive from the signed entitlement payload.", {
|
|
581
|
+
pluginName: z.string().describe("Plugin slug (lowercase a-z0-9-)."),
|
|
582
|
+
action: z.enum(["enable", "disable"]).describe("enable adds to enabledPlugins; disable removes."),
|
|
583
|
+
}, async ({ pluginName, action }) => {
|
|
584
|
+
const TAG = "[admin:plugin-toggle-enabled]";
|
|
585
|
+
if (!VALID_PLUGIN_NAME.test(pluginName)) {
|
|
586
|
+
return { content: [{ type: "text", text: `${TAG} Invalid plugin name "${pluginName}". Must match ${VALID_PLUGIN_NAME}.` }], isError: true };
|
|
587
|
+
}
|
|
588
|
+
if (CORE_PLUGINS.includes(pluginName)) {
|
|
589
|
+
return { content: [{ type: "text", text: `${TAG} "${pluginName}" is a core plugin and cannot be toggled.` }], isError: true };
|
|
590
|
+
}
|
|
591
|
+
const pluginDir = resolve(PLUGINS_DIR, pluginName);
|
|
592
|
+
if (!existsSync(pluginDir) || !existsSync(join(pluginDir, "PLUGIN.md"))) {
|
|
593
|
+
return { content: [{ type: "text", text: `${TAG} Plugin "${pluginName}" not installed at ${pluginDir} (no PLUGIN.md). Install via premium-deliver or platform release.` }], isError: true };
|
|
594
|
+
}
|
|
595
|
+
try {
|
|
596
|
+
const configPath = join(getAccountDir(), "account.json");
|
|
597
|
+
const config = await readAccountConfig();
|
|
598
|
+
const current = Array.isArray(config.enabledPlugins) ? config.enabledPlugins : [];
|
|
599
|
+
let next;
|
|
600
|
+
if (action === "enable") {
|
|
601
|
+
if (current.includes(pluginName)) {
|
|
602
|
+
return { content: [{ type: "text", text: `${TAG} "${pluginName}" is already enabled.` }] };
|
|
603
|
+
}
|
|
604
|
+
next = [...current, pluginName];
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
if (!current.includes(pluginName)) {
|
|
608
|
+
return { content: [{ type: "text", text: `${TAG} "${pluginName}" is not enabled — nothing to disable.` }] };
|
|
609
|
+
}
|
|
610
|
+
next = current.filter((n) => n !== pluginName);
|
|
611
|
+
}
|
|
612
|
+
config.enabledPlugins = next;
|
|
613
|
+
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
614
|
+
console.error(`${TAG} ${action}d plugin=${pluginName} path=${configPath}`);
|
|
615
|
+
return { content: [{ type: "text", text: `Plugin "${pluginName}" ${action}d. Takes effect on next session.` }] };
|
|
616
|
+
}
|
|
617
|
+
catch (err) {
|
|
618
|
+
return { content: [{ type: "text", text: `${TAG} Failed: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
619
|
+
}
|
|
620
|
+
});
|
|
545
621
|
// ===================================================================
|
|
546
622
|
// Admin user management tools
|
|
547
623
|
// ===================================================================
|
|
@@ -560,11 +636,15 @@ server.tool("admin-add", "Add a new admin user to this account. Creates a device
|
|
|
560
636
|
catch (err) {
|
|
561
637
|
return { content: [{ type: "text", text: `${TAG} ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
562
638
|
}
|
|
563
|
-
// Enforce per-tier admin limit
|
|
639
|
+
// Enforce per-tier admin limit. Effective tier comes from the signed
|
|
640
|
+
// entitlement payload (Task 831) — editing account.json.tier directly
|
|
641
|
+
// has no effect; the verifier uses the signed value and emits a
|
|
642
|
+
// [entitlement] tampered: line if disk and signed diverge.
|
|
564
643
|
try {
|
|
565
644
|
const config = await readAccountConfig();
|
|
566
645
|
const currentAdmins = (config.admins ?? []);
|
|
567
|
-
const
|
|
646
|
+
const entitlement = await currentEntitlement();
|
|
647
|
+
const tier = entitlement.tier;
|
|
568
648
|
const maxAdmins = MAX_ADMINS_BY_TIER[tier] ?? MAX_ADMINS_DEFAULT;
|
|
569
649
|
if (currentAdmins.length >= maxAdmins) {
|
|
570
650
|
return { content: [{ type: "text", text: `${TAG} Admin limit reached (${maxAdmins} for ${tier || "this"} tier). Remove an existing admin before adding a new one.` }], isError: true };
|
|
@@ -623,20 +703,58 @@ server.tool("admin-add", "Add a new admin user to this account. Creates a device
|
|
|
623
703
|
console.error(`${TAG} account.json write failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
624
704
|
return { content: [{ type: "text", text: `${TAG} User created in users.json but failed to add to account: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
625
705
|
}
|
|
626
|
-
// 3. Write to Neo4j (graph-level)
|
|
706
|
+
// 3. Write to Neo4j (graph-level): AdminUser + Person + OWNS atomically,
|
|
707
|
+
// plus ADMIN_OF edge to the LocalBusiness for this account.
|
|
708
|
+
// Task 830 — deterministic identity creation: never delegate node
|
|
709
|
+
// creation to the LLM. Person reuse rule mirrors writeAdminUserAndPerson
|
|
710
|
+
// in platform/ui/app/lib/neo4j-store.ts (case-insensitive exact match
|
|
711
|
+
// on givenName + familyName; partial-name ambiguity does NOT match —
|
|
712
|
+
// rationalisation is a separate agent-mediated concern).
|
|
627
713
|
let neo4jWarning = "";
|
|
714
|
+
let personReused = false;
|
|
628
715
|
try {
|
|
629
716
|
const session = getSession();
|
|
630
717
|
try {
|
|
631
718
|
const createdAt = new Date().toISOString();
|
|
632
719
|
const trimmedName = name.trim();
|
|
633
|
-
|
|
720
|
+
const firstSpace = trimmedName.search(/\s/);
|
|
721
|
+
const givenName = firstSpace === -1 ? trimmedName : trimmedName.slice(0, firstSpace).trim();
|
|
722
|
+
const familyName = firstSpace === -1 ? null : (trimmedName.slice(firstSpace + 1).trim() || null);
|
|
723
|
+
const result = await session.run(`MERGE (au:AdminUser {userId: $userId})
|
|
634
724
|
ON CREATE SET au.name = $name, au.createdAt = $createdAt
|
|
635
|
-
ON MATCH SET au.name = $name
|
|
725
|
+
ON MATCH SET au.name = $name, au.updatedAt = $createdAt
|
|
636
726
|
WITH au
|
|
637
727
|
MATCH (b:LocalBusiness {accountId: $accountId})
|
|
638
728
|
MERGE (au)-[r:ADMIN_OF]->(b)
|
|
639
|
-
ON CREATE SET r.role = 'admin', r.grantedAt = $createdAt
|
|
729
|
+
ON CREATE SET r.role = 'admin', r.grantedAt = $createdAt
|
|
730
|
+
WITH au
|
|
731
|
+
OPTIONAL MATCH (existingPerson:Person {accountId: $accountId})
|
|
732
|
+
WHERE toLower(existingPerson.givenName) = toLower($givenName)
|
|
733
|
+
AND coalesce(toLower(existingPerson.familyName), '') = coalesce(toLower($familyName), '')
|
|
734
|
+
WITH au, existingPerson
|
|
735
|
+
CALL {
|
|
736
|
+
WITH au, existingPerson
|
|
737
|
+
WITH au, existingPerson WHERE existingPerson IS NOT NULL
|
|
738
|
+
MERGE (au)-[:OWNS]->(existingPerson)
|
|
739
|
+
RETURN true AS reused
|
|
740
|
+
UNION
|
|
741
|
+
WITH au, existingPerson
|
|
742
|
+
WITH au WHERE existingPerson IS NULL
|
|
743
|
+
CREATE (newPerson:Person {
|
|
744
|
+
accountId: $accountId,
|
|
745
|
+
givenName: $givenName,
|
|
746
|
+
familyName: $familyName,
|
|
747
|
+
role: 'admin-personal',
|
|
748
|
+
scope: 'admin',
|
|
749
|
+
createdAt: $createdAt
|
|
750
|
+
})
|
|
751
|
+
MERGE (au)-[:OWNS]->(newPerson)
|
|
752
|
+
RETURN false AS reused
|
|
753
|
+
}
|
|
754
|
+
RETURN reused`, { userId, name: trimmedName, createdAt, accountId: ACCOUNT_ID, givenName, familyName });
|
|
755
|
+
if (result.records.length > 0) {
|
|
756
|
+
personReused = result.records[0].get("reused");
|
|
757
|
+
}
|
|
640
758
|
}
|
|
641
759
|
finally {
|
|
642
760
|
await session.close();
|
|
@@ -647,6 +765,7 @@ server.tool("admin-add", "Add a new admin user to this account. Creates a device
|
|
|
647
765
|
console.error(`${TAG} Neo4j sync failed during admin-add: userId=${userId} error=${errMsg} — files written, graph pending`);
|
|
648
766
|
neo4jWarning = ` Note: Neo4j sync failed (${errMsg}) — the admin user is functional but the graph will need reconciliation on next seed.`;
|
|
649
767
|
}
|
|
768
|
+
console.error(`${TAG} [admin-identity] adminuser-bound userId=${userId.slice(0, 8)} name=${name.trim()} personReused=${personReused}`);
|
|
650
769
|
console.error(`${TAG} admin added: userId=${userId} userName=${name.trim()} accountId=${ACCOUNT_ID} role=admin addedBy=${callerUserId ?? "unknown"}`);
|
|
651
770
|
return {
|
|
652
771
|
content: [{
|
|
@@ -2140,7 +2259,9 @@ server.tool("premium-list", "List available premium plugins and their delivery s
|
|
|
2140
2259
|
};
|
|
2141
2260
|
}
|
|
2142
2261
|
const config = await readAccountConfig();
|
|
2143
|
-
|
|
2262
|
+
// Effective purchasedPlugins from signed entitlement (Task 831).
|
|
2263
|
+
const entitlement = await currentEntitlement();
|
|
2264
|
+
const purchased = entitlement.purchasedPlugins;
|
|
2144
2265
|
const enabled = Array.isArray(config.enabledPlugins) ? config.enabledPlugins : [];
|
|
2145
2266
|
const entries = readdirSync(STAGING_ROOT, { withFileTypes: true })
|
|
2146
2267
|
.filter(e => e.isDirectory())
|
|
@@ -2245,7 +2366,16 @@ server.tool("premium-deliver", "Deliver a purchased premium plugin. Copies plugi
|
|
|
2245
2366
|
isError: true,
|
|
2246
2367
|
};
|
|
2247
2368
|
}
|
|
2248
|
-
|
|
2369
|
+
// Effective purchasedPlugins from signed entitlement (Task 831). The
|
|
2370
|
+
// raw account.json value is ignored on commercial installs; verifier
|
|
2371
|
+
// returns the signed list (or [] on anonymous-fallback).
|
|
2372
|
+
const entitlement = await resolveEntitlement(ENTITLEMENT_BRAND, {
|
|
2373
|
+
accountId: typeof config.accountId === "string" ? config.accountId : "",
|
|
2374
|
+
customerEmail: typeof config.customerEmail === "string" ? config.customerEmail : undefined,
|
|
2375
|
+
tier: typeof config.tier === "string" ? config.tier : undefined,
|
|
2376
|
+
purchasedPlugins: Array.isArray(config.purchasedPlugins) ? config.purchasedPlugins : undefined,
|
|
2377
|
+
});
|
|
2378
|
+
const purchased = entitlement.purchasedPlugins;
|
|
2249
2379
|
// --- Check purchase status ---
|
|
2250
2380
|
if (!purchased.includes(pluginName)) {
|
|
2251
2381
|
return {
|