@rubytech/create-realagent-code 0.1.105 → 0.1.107

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 CHANGED
@@ -59,7 +59,7 @@ function claudePluginEnv() {
59
59
  * evidence. When a third brand is added under `brands/`, append its hostname
60
60
  * here AND in the matching constant in `uninstall.ts` (intentional duplication
61
61
  * per `uninstall.ts:` "Shell helpers (duplicated from index.ts ...)" policy). */
62
- const KNOWN_BRAND_HOSTNAMES = ["maxy", "realagent", "maxy-2", "maxy-3", "maxy-4"];
62
+ const KNOWN_BRAND_HOSTNAMES = ["maxy", "maxy-code", "realagent", "realagent-code", "maxy-2", "maxy-3", "maxy-4"];
63
63
  // The device's actual hostname — may differ from BRAND.hostname if the user customized it.
64
64
  // Updated by installSystemDeps() after hostname setup; used for user-facing URLs.
65
65
  let DEVICE_HOSTNAME = BRAND.hostname;
package/dist/uninstall.js CHANGED
@@ -103,7 +103,7 @@ export function isMaxyInstalled() {
103
103
  * `.service` file — stale units, gnome-keyring disable markers, and unrelated
104
104
  * user services are not peer evidence. When a third brand is
105
105
  * added under `brands/`, append its hostname here. */
106
- const KNOWN_BRAND_HOSTNAMES = ["maxy", "realagent", "maxy-2", "maxy-3", "maxy-4"];
106
+ const KNOWN_BRAND_HOSTNAMES = ["maxy", "maxy-code", "realagent", "realagent-code", "maxy-2", "maxy-3", "maxy-4"];
107
107
  /** Detect whether another brand is installed on this device.
108
108
  * device-wide steps (apt package purge, Ollama binary removal, apt
109
109
  * repo cleanup, ~/.claude / ~/.ollama wipes) must skip when a peer brand is
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-realagent-code",
3
- "version": "0.1.105",
3
+ "version": "0.1.107",
4
4
  "description": "Install Real Agent — Built for agents. By agents.",
5
5
  "bin": {
6
6
  "create-realagent-code": "./dist/index.js"
@@ -14,6 +14,7 @@
14
14
  "cdpPort": 9223,
15
15
  "claudeSessionManagerPort": 19401,
16
16
  "commercialMode": false,
17
+ "shipsPremiumBundles": true,
17
18
  "vertical": "schema-estate-agent",
18
19
 
19
20
  "defaultColors": {
@@ -176,7 +176,11 @@ When any field above is unresolved after consulting its primary source, **stop b
176
176
  - **Web bundle ships the PDF, not per-page JPGs.** The bundle does not include `cover-print.jpg … backpage-print.jpg` — those duplicate the brochure content already embedded in `<slug>-brochure.pdf`. The web copy of `brochure.html` has its `.print-img` src attributes cleared so the print stylesheet drops to the live-DOM fallback; operators who want a printable PDF download the bundled PDF directly. See `references/build.md → Clear .print-img src in the web copy`. Shipping both was the historical bundle-bloat defect.
177
177
  - **Bottom chrome on `.page` is absolutely-positioned and stacks deterministically.** Three classes of bottom element exist: (1) the `.folio` page-number strip (`bottom: 8mm`, ~8mm tall → occupies bottom ~16mm of every page); (2) `.back-disclaimer` on the back page (`bottom: 16mm`, wraps to up to 5 lines × 3.7mm → occupies bottom ~34.5mm); (3) `.mi-foot` on the Material Information page (`bottom: 22mm`, ~7mm tall → occupies bottom ~22–29mm). All are `position: absolute`, anchored to their nearest positioned ancestor (`.page` which is `position: relative`). In-flow content above (e.g. `.mi-grid` inside `.mi-page`, `.backpage-inner` centred content) is a SIBLING in the stacking flow — nothing in CSS structure enforces clearance from the absolute layer. The fix is to either (a) **make the relevant footer absolute** (the approach used for `.mi-foot` to prevent oversized `.mi-grid` from pushing the disclaimer into the folio), or (b) **pull the in-flow container up via its parent's `padding-bottom`** when the footer is small and predictable (e.g. `.backpage-inner` uses 42mm to clear folio + disclaimer with a 4mm gap). Never use `margin-*` on a `position: absolute` element whose anchor is `bottom` — it has no positional effect.
178
178
  - **Folio is plain text on whitespace or absent — never chrome over imagery.** The `.folio > span` carries no background, blur, padding, border-radius, or shadow. It is plain text in `--teal-900` that sits on the clean whitespace reserved at the page bottom (see the previous rule on padding-bottom clearance). The folio is **hidden entirely** on full-bleed-bottom pages: `.cover`, `.page.intermission`, `.page.folio-dark`, and any `.page` whose body wraps a `.feature-page` (photo+panel layout where the photo bleeds to the page bottom). Never reintroduce a folio-pill background to "make it readable over the image" — that's competing chrome on the page; suppress the folio on that page instead. Any new full-bleed-bottom page class must be added to the `.folio` `display: none` selector in `references/template.html`.
179
- - **Page 13's `{{ siteplan_src }}` is extract-or-omit-or-ask — NEVER fabricated.** Page 13 (the site-plan / plot page, `.page siteplan-page`, "Continued, Chapter VI · The plot") expects a real plot drawing, real site plan, or real aerial/exterior photograph — an image that EXISTS on disk because the listing supplied it, the operator staged it, or `property-extract` captured it. **The agent must never fabricate this image.** No mock plan drawn from coloured rectangles + text labels (e.g. "THE LODGE / DRIVEWAY · PARKING / SOUTH-WEST FACING LAWN" on a green background — a real-world failure observed on Plate VIII of a recent brochure), no SVG diagram synthesised from `property.json` dimensions, no AI-generated site sketch, no postcode-centroid satellite render, no Mapbox/Google static-map call with our API key, no `<canvas>` hand-drawing, no HTML/CSS box-and-label illustration. ALL of these are forbidden — the same "extract or omit" doctrine that governs `property-extract → Map detection` applies here. **Before any "no image" fallback** (asking the operator, dropping to the 12-page layout, or — forbidden — fabricating), the agent MUST first **scan `<property_dir>/images/` and enumerate candidates**. Any photo that shows the property from height (drone shot, upper-floor view down the grounds), the plot from a distance, the front elevation framing the home in its setting, a paddock/grounds wide, or an aerial perspective IS a candidate for `siteplan_src`. Visual scan via the contact-sheet montage technique already required elsewhere in this SKILL ("Never assign an image to a slot from filename alone"). Real-world failure observed today: a brochure shipped with a fabricated mock plan on page 13 even though THREE perfectly good aerial photos sat in `<property_dir>/images/` unused. The agent skipped the directory scan, jumped to fabrication, and shipped. If candidates exist, the operator confirmation prompt lists them by filename + one-line description (e.g. "siteplan_src: 3 candidates found in images/ — <slug>-12.webp (drone, S-facing), <slug>-18.webp (wide exterior), <slug>-21.webp (aerial through trees). Pick one, or supply a different image.") — and the agent renders only after the operator picks. Only when zero candidates exist on disk does the prompt request a fresh image; only when the operator declines does the brochure drop to the 12-page reduced layout. The order is **scan-disk first, ask second, drop-to-12 third** fabricate, never. The brochure must NEVER ship with a fabricated illustration where a real plot/aerial belongs; this is a ship-stopper defect that misrepresents the property to a buyer. Also forbidden by the same doctrine: silently falling back to the floorplan (duplicates page 14). If the operator cannot supply a real image, the brochure drops to the 12-page reduced layout (no page 13) — never to a 16-page layout with fabricated or duplicated content on page 13.
179
+ - **Page 13's `{{ siteplan_src }}` is extract-or-omit-or-ask — NEVER fabricated.** Page 13 (the site-plan / plot page, `.page siteplan-page`, "Continued, Chapter VI · The plot") expects a real plot drawing, real site plan, or real aerial/exterior photograph — an image that EXISTS on disk because the listing supplied it, the operator staged it, or `property-extract` captured it. **The agent must never fabricate this image.** No mock plan drawn from coloured rectangles + text labels (e.g. "THE LODGE / DRIVEWAY · PARKING / SOUTH-WEST FACING LAWN" on a green background — a real-world failure observed on Plate VIII of a recent brochure), no SVG diagram synthesised from `property.json` dimensions, no AI-generated site sketch, no postcode-centroid satellite render, no Mapbox/Google static-map call with our API key, no `<canvas>` hand-drawing, no HTML/CSS box-and-label illustration. ALL of these are forbidden — the same "extract or omit" doctrine that governs `property-extract → Map detection` applies here. **The acceptable image set for `siteplan_src` is a closed list: (1) aerial / drone shot (taken from height, looking down on the property and its grounds), (2) site plan / plot drawing (line-art, supplied by the listing or staged by the operator), (3) local-area shot (street view, neighbourhood context, immediate surrounding setting that frames the plot's relationship to its area). Nothing else.** Ground-level exteriors of the house itself are NOT siteplan candidates those belong to the cover, opener, intermission, or feature slots. Interior shots (kitchen, garden, terrace, lounge) are NOT candidates. The floorplan is NOT a candidate (page 14's content; banned above). A fabricated mock illustration is NOT a candidate (banned above). The hero photo is NOT a candidate (used on the cover).
180
+
181
+ **Before any "no image" fallback** (asking the operator, dropping to the 12-page layout, or — forbidden — fabricating), the agent MUST first **scan `<property_dir>/images/` and enumerate candidates that match the closed list above**. Visual scan via the contact-sheet montage technique already required elsewhere in this SKILL ("Never assign an image to a slot from filename alone") — read each candidate's pixel content, not the filename. Real-world failure observed today: a brochure shipped with a fabricated mock plan on page 13 even though THREE perfectly good aerial photos sat in `<property_dir>/images/` unused; the agent skipped the directory scan, jumped to fabrication, and shipped. A separate failure mode also observed: agent picked an inappropriate image (e.g. ground-level garden shot, interior moment) for the slot because it scanned but didn't filter against the closed list. Both fail the same test — the slot must be filled with an image that ACTUALLY conveys the plot's geography, not just any image that happens to be on disk.
182
+
183
+ If one or more candidates from the closed list exist, the operator confirmation prompt lists them by filename + one-line description (e.g. *"siteplan_src: 3 candidates found in images/ — <slug>-12.webp (drone, S-facing), <slug>-18.webp (street-view from the lane), <slug>-21.webp (aerial through trees). Pick one, or supply a different image."*) and the agent renders only after the operator picks. Only when zero candidates from the closed list exist on disk does the prompt request a fresh image; only when the operator declines does the brochure drop to the 12-page reduced layout. The order is **scan-and-filter first, ask second, drop-to-12 third** — fabricate, never; pick-an-inappropriate-image-just-because-it's-there, never. The brochure must NEVER ship with a fabricated illustration where a real plot/aerial belongs; this is a ship-stopper defect that misrepresents the property to a buyer. Also forbidden by the same doctrine: silently falling back to the floorplan (duplicates page 14). If the operator cannot supply a real image, the brochure drops to the 12-page reduced layout (no page 13) — never to a 16-page layout with fabricated or duplicated content on page 13.
180
184
  - **Verification covers every page in the deliverable — sampling is a defect, not a shortcut.** `scrollHeight` fit checks, computed-style collision probes (`.mi-foot` ↔ `.folio` gap, `.back-disclaimer` ↔ in-flow content, any future absolute-bottom element vs in-flow neighbour), and visual review before declaring done all walk **every page** in the 16-page folio (or 12-page reduced layout). "Sample-checked 3 of 12 pages" or any variant ("spot-checked the spreads I changed", "skimmed a few representative pages") is not authorised — the skill explicitly says "verify every page" in `references/build.md → Live editing workflow` step 8 and in `references/structure.md`. The deliverable is the brochure as a whole; a defect on an unchecked page ships exactly as readily as a defect on a checked one. If a probe is expensive enough that walking 16 pages feels burdensome, the cost is the probe's, not the skill's — propose a faster probe rather than narrowing the coverage.
181
185
  - **`scrollHeight > viewport budget` is ALWAYS a defect — never "negligible".** Landscape budget is **794px** (`min-height: 210mm` at 96dpi); portrait is **1123px**. Every `.page`'s `scrollHeight` must be `≤` the budget — strict, not "approximately equal". A 4px overflow IS a defect: `overflow: hidden` silently clips that excess, and on pages with absolute-positioned bottom chrome (`.folio`, `.back-disclaimer`) the clipped content paints over chrome that lives in the absolute layer. "It's only 4 pixels" is the rationalisation that ships defects — the rule does not have a tolerance band. If a page reports `scrollHeight > budget` even by 1px, the build halts until the operator addresses the content-volume mismatch (trim copy, shrink an image, reduce a row count, or split into a continuation page). Never paper over with `overflow: hidden`-as-clip; the warning sign is the deliverable's only signal that the content doesn't fit.
182
186
  - **Material Information table has a hard row capacity — overflow is a content-volume defect, not a render bug.** At the default `.mi-row` sizing (9.5/11.5px fonts, 6px vertical padding, 18px `.mi-grid` row-gap), one column in the three-column MI grid fits **~13 rows** within the 22mm-clearance budget. A property with an unusually full table (e.g. 17+ rows in the tallest column, observed on a recent run) overruns by ~25mm / ~85px and clips into the folio zone. When MI's content volume exceeds the budget, the operator confirmation prompt MUST surface `mi.<column>.row_count` as an overflow item and ask the operator to either (a) trim the column to ≤ 13 rows by consolidating related disclosures (e.g. merge "easements" + "water/sewerage" + "rights of way" into one "Other matters" row that points at the seller's solicitor), or (b) opt into a continuation page (15a / 15b) — a separate skill-level decision that breaks the default 16-page contract and must be operator-confirmed. The build never silently truncates rows, never shrinks fonts to "make it fit", and never accepts a scrollHeight overflow as "negligible". The default `.mi-page` 22mm bottom-padding is sized for the typical property; properties outside the typical range MUST resolve via operator confirmation, not via the agent's judgement about which rows are "less important".
@@ -2632,6 +2632,7 @@ import { resolve, join } from "path";
2632
2632
  import { existsSync, readFileSync } from "fs";
2633
2633
  var configDirName = ".maxy";
2634
2634
  var commercialMode = false;
2635
+ var shipsPremiumBundlesFlag = false;
2635
2636
  var vncDisplayNum = 99;
2636
2637
  var rfbPortNum = 5900;
2637
2638
  var websockifyPortNum = 6080;
@@ -2651,6 +2652,7 @@ if (platformRoot) {
2651
2652
  }
2652
2653
  if (typeof brand.configDir === "string") configDirName = brand.configDir;
2653
2654
  if (brand.commercialMode === true) commercialMode = true;
2655
+ if (brand.shipsPremiumBundles === true) shipsPremiumBundlesFlag = true;
2654
2656
  if (typeof brand.vncDisplay === "number") vncDisplayNum = brand.vncDisplay;
2655
2657
  const brandLabel = configDirName.replace(/^\./, "");
2656
2658
  const required = [
@@ -2674,6 +2676,7 @@ if (platformRoot) {
2674
2676
  var MAXY_DIR = resolve(homedir(), configDirName);
2675
2677
  var BRAND_NAME = configDirName.replace(/^\./, "");
2676
2678
  var COMMERCIAL_MODE = commercialMode;
2679
+ var SHIPS_PREMIUM_BUNDLES = shipsPremiumBundlesFlag;
2677
2680
  var VNC_DISPLAY = `:${vncDisplayNum}`;
2678
2681
  var RFB_PORT = rfbPortNum;
2679
2682
  var WEBSOCKIFY_PORT = websockifyPortNum;
@@ -3429,6 +3432,38 @@ if (!existsSync4(PLATFORM_ROOT2)) {
3429
3432
  Set the MAXY_PLATFORM_ROOT environment variable to the absolute path of the platform directory.`
3430
3433
  );
3431
3434
  }
3435
+ var _stubDirsLoggedThisBoot = /* @__PURE__ */ new Set();
3436
+ function listValidAccounts() {
3437
+ if (!existsSync4(ACCOUNTS_DIR)) return [];
3438
+ const out = [];
3439
+ const entries = readdirSync(ACCOUNTS_DIR, { withFileTypes: true });
3440
+ for (const entry of entries) {
3441
+ if (!entry.isDirectory()) continue;
3442
+ if (entry.name.startsWith(".")) continue;
3443
+ const accountDir = resolve3(ACCOUNTS_DIR, entry.name);
3444
+ const configPath = resolve3(accountDir, "account.json");
3445
+ if (!existsSync4(configPath)) {
3446
+ if (!_stubDirsLoggedThisBoot.has(entry.name)) {
3447
+ console.error(
3448
+ `[platform] accounts-state STUB-DIR id=${entry.name} \u2014 onboarding leak; remove or repair`
3449
+ );
3450
+ _stubDirsLoggedThisBoot.add(entry.name);
3451
+ }
3452
+ continue;
3453
+ }
3454
+ let config;
3455
+ try {
3456
+ config = JSON.parse(readFileSync4(configPath, "utf-8"));
3457
+ } catch {
3458
+ console.error(
3459
+ `[platform] accounts-state CORRUPT-JSON id=${entry.name} \u2014 account.json failed to parse; treating as stub`
3460
+ );
3461
+ continue;
3462
+ }
3463
+ out.push({ accountId: config.accountId, accountDir, config });
3464
+ }
3465
+ return out;
3466
+ }
3432
3467
  function resolveAccount() {
3433
3468
  if (!existsSync4(ACCOUNTS_DIR)) return null;
3434
3469
  let usersJsonUserId = null;
@@ -3622,7 +3657,10 @@ function readBundleSubPlugins(bundlePath) {
3622
3657
  return subs;
3623
3658
  }
3624
3659
  function walkPremiumBundles() {
3625
- if (BRAND_NAME === "maxy") return [];
3660
+ if (!SHIPS_PREMIUM_BUNDLES) return [];
3661
+ return walkPremiumBundlesOnDisk();
3662
+ }
3663
+ function walkPremiumBundlesOnDisk() {
3626
3664
  const stagingRoot = resolve4(PLATFORM_ROOT2, "../premium-plugins");
3627
3665
  if (!existsSync5(stagingRoot)) return [];
3628
3666
  let entries;
@@ -3735,6 +3773,36 @@ function reconcileEnabledPlugins(accountDir, config) {
3735
3773
  console.error(`${TAG} enabled-stamp write failed: ${err instanceof Error ? err.message : String(err)}`);
3736
3774
  }
3737
3775
  }
3776
+ function cleanupLeakedPremiumSubs(accountDir, config, accountId) {
3777
+ const TAG = "[premium-auto-deliver]";
3778
+ if (!accountDir || !config) return;
3779
+ if (SHIPS_PREMIUM_BUNDLES) return;
3780
+ const current = Array.isArray(config.enabledPlugins) ? config.enabledPlugins : [];
3781
+ if (current.length === 0) return;
3782
+ const leakedNames = /* @__PURE__ */ new Set();
3783
+ for (const { subs } of walkPremiumBundlesOnDisk()) {
3784
+ for (const sub of subs) leakedNames.add(sub);
3785
+ }
3786
+ if (leakedNames.size === 0) return;
3787
+ const removed = [];
3788
+ const kept = [];
3789
+ for (const name of current) {
3790
+ if (leakedNames.has(name)) removed.push(name);
3791
+ else kept.push(name);
3792
+ }
3793
+ if (removed.length === 0) return;
3794
+ const configPath = resolve4(accountDir, "account.json");
3795
+ try {
3796
+ const raw2 = readFileSync5(configPath, "utf-8");
3797
+ const parsed = JSON.parse(raw2);
3798
+ parsed.enabledPlugins = kept;
3799
+ writeFileSync2(configPath, JSON.stringify(parsed, null, 2) + "\n");
3800
+ config.enabledPlugins = kept;
3801
+ console.log(`${TAG} cleanup accountId=${accountId ?? "unknown"} removed=[${removed.join(",")}]`);
3802
+ } catch (err) {
3803
+ console.error(`${TAG} cleanup write failed accountId=${accountId ?? "unknown"}: ${err instanceof Error ? err.message : String(err)}`);
3804
+ }
3805
+ }
3738
3806
 
3739
3807
  // app/lib/claude-agent/logging.ts
3740
3808
  var emitMissingOnResolve = (..._args) => null;
@@ -4414,6 +4482,7 @@ export {
4414
4482
  parseCookieValue,
4415
4483
  PLATFORM_ROOT2 as PLATFORM_ROOT,
4416
4484
  ACCOUNTS_DIR,
4485
+ listValidAccounts,
4417
4486
  resolveAccount,
4418
4487
  resolveUserAccounts,
4419
4488
  fingerprintSessionKey,
@@ -4432,6 +4501,7 @@ export {
4432
4501
  walkPremiumBundles,
4433
4502
  autoDeliverPremiumPlugins,
4434
4503
  reconcileEnabledPlugins,
4504
+ cleanupLeakedPremiumSubs,
4435
4505
  emitMissingOnResolve,
4436
4506
  stripAttachmentMetaSuffix,
4437
4507
  requirePortEnv,
@@ -19,7 +19,7 @@ import {
19
19
  streamActionEvents,
20
20
  vncLog,
21
21
  websockifyLog
22
- } from "./chunk-BVDLDM6O.js";
22
+ } from "./chunk-TZD23CX6.js";
23
23
  import "./chunk-JRBCOVA4.js";
24
24
  import "./chunk-JSBRDJBE.js";
25
25
 
@@ -18,6 +18,7 @@ import {
18
18
  browserViewerLog,
19
19
  canAccessAdmin,
20
20
  checkRateLimit,
21
+ cleanupLeakedPremiumSubs,
21
22
  clearRateLimit,
22
23
  clientIpMiddleware,
23
24
  createRemoteSession,
@@ -35,6 +36,7 @@ import {
35
36
  isPasswordValid,
36
37
  isRemoteAuthConfigured,
37
38
  launchAction,
39
+ listValidAccounts,
38
40
  reconcileEnabledPlugins,
39
41
  recordFailedAttempt,
40
42
  registerSession,
@@ -59,7 +61,7 @@ import {
59
61
  vncLog,
60
62
  waitForExit,
61
63
  walkPremiumBundles
62
- } from "./chunk-BVDLDM6O.js";
64
+ } from "./chunk-TZD23CX6.js";
63
65
  import {
64
66
  CLOUDFLARE_TASK_DIAGNOSTICS,
65
67
  appendCloudflareSteps,
@@ -14368,6 +14370,9 @@ var bootEntitlement = bootAccountConfig ? resolveEntitlement(
14368
14370
  ) : null;
14369
14371
  autoDeliverPremiumPlugins();
14370
14372
  reconcileEnabledPlugins(bootAccount?.accountDir, bootAccount?.config);
14373
+ for (const acct of listValidAccounts()) {
14374
+ cleanupLeakedPremiumSubs(acct.accountDir, acct.config, acct.accountId);
14375
+ }
14371
14376
  (async () => {
14372
14377
  if (!bootAccount) return;
14373
14378
  try {