@rubytech/create-realagent 1.0.773 → 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.
Files changed (40) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/config/brand.json +1 -0
  3. package/payload/platform/lib/entitlement/PUBKEY-HASH.txt +1 -0
  4. package/payload/platform/lib/entitlement/dist/canonicalize.d.ts +26 -0
  5. package/payload/platform/lib/entitlement/dist/canonicalize.d.ts.map +1 -0
  6. package/payload/platform/lib/entitlement/dist/canonicalize.js +54 -0
  7. package/payload/platform/lib/entitlement/dist/canonicalize.js.map +1 -0
  8. package/payload/platform/lib/entitlement/dist/index.d.ts +76 -0
  9. package/payload/platform/lib/entitlement/dist/index.d.ts.map +1 -0
  10. package/payload/platform/lib/entitlement/dist/index.js +293 -0
  11. package/payload/platform/lib/entitlement/dist/index.js.map +1 -0
  12. package/payload/platform/lib/entitlement/rubytech-pubkey.pem +3 -0
  13. package/payload/platform/package.json +2 -2
  14. package/payload/platform/plugins/admin/hooks/pre-tool-use.sh +32 -0
  15. package/payload/platform/plugins/admin/mcp/dist/index.js +97 -6
  16. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  17. package/payload/platform/plugins/admin/skills/plugin-management/SKILL.md +10 -5
  18. package/payload/platform/scripts/generate-entitlement-fixture.mjs +152 -0
  19. package/payload/server/chunk-MIP54X7Q.js +3244 -0
  20. package/payload/server/chunk-TM3EQSID.js +9800 -0
  21. package/payload/server/client-pool-4MZN42GG.js +28 -0
  22. package/payload/server/maxy-edge.js +2 -2
  23. package/payload/server/public/assets/{Checkbox-DEE8t2QO.js → Checkbox-C_KxaLc-.js} +1 -1
  24. package/payload/server/public/assets/{admin-C4CTVtBu.js → admin-xbKPR6ZI.js} +30 -30
  25. package/payload/server/public/assets/data-D23IzpJ2.js +1 -0
  26. package/payload/server/public/assets/graph-D2AS9zFS.js +1 -0
  27. package/payload/server/public/assets/{jsx-runtime-DSbkOE76.css → jsx-runtime-BZtBxBng.css} +1 -1
  28. package/payload/server/public/assets/{page-YUT5e7hL.js → page-CjTfZ3O6.js} +2 -2
  29. package/payload/server/public/assets/{page-ZATk95ZG.js → page-DEWgk_nR.js} +1 -1
  30. package/payload/server/public/assets/{public-BLi3J8KU.js → public-CehiL-qZ.js} +1 -1
  31. package/payload/server/public/assets/{share-2-DS7Pnkkq.js → share-2-BG1VXt3z.js} +1 -1
  32. package/payload/server/public/assets/{useVoiceRecorder-pEHqS1ib.js → useVoiceRecorder-1Dvb-yHn.js} +1 -1
  33. package/payload/server/public/data.html +5 -5
  34. package/payload/server/public/graph.html +6 -6
  35. package/payload/server/public/index.html +8 -8
  36. package/payload/server/public/public.html +5 -5
  37. package/payload/server/server.js +31 -16
  38. package/payload/server/public/assets/data-ryPag-T-.js +0 -1
  39. package/payload/server/public/assets/graph-BPnH-UZB.js +0 -1
  40. /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 productName from brand.json stamped at install time.
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 { configDir: brand.configDir, productName: brand.productName };
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 tier = typeof config.tier === "string" ? config.tier : "";
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 };
@@ -2179,7 +2259,9 @@ server.tool("premium-list", "List available premium plugins and their delivery s
2179
2259
  };
2180
2260
  }
2181
2261
  const config = await readAccountConfig();
2182
- const purchased = Array.isArray(config.purchasedPlugins) ? config.purchasedPlugins : [];
2262
+ // Effective purchasedPlugins from signed entitlement (Task 831).
2263
+ const entitlement = await currentEntitlement();
2264
+ const purchased = entitlement.purchasedPlugins;
2183
2265
  const enabled = Array.isArray(config.enabledPlugins) ? config.enabledPlugins : [];
2184
2266
  const entries = readdirSync(STAGING_ROOT, { withFileTypes: true })
2185
2267
  .filter(e => e.isDirectory())
@@ -2284,7 +2366,16 @@ server.tool("premium-deliver", "Deliver a purchased premium plugin. Copies plugi
2284
2366
  isError: true,
2285
2367
  };
2286
2368
  }
2287
- const purchased = Array.isArray(config.purchasedPlugins) ? config.purchasedPlugins : [];
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;
2288
2379
  // --- Check purchase status ---
2289
2380
  if (!purchased.includes(pluginName)) {
2290
2381
  return {