@rubytech/create-realagent-code 0.1.510 → 0.1.512
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/package.json +3 -0
- package/payload/platform/plugins/admin/skills/deck-pages/SKILL.md +2 -2
- package/payload/platform/plugins/admin/skills/skill-builder/SKILL.md +1 -1
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +10 -0
- package/payload/platform/plugins/business-assistant/skills/e-sign/SKILL.md +15 -34
- package/payload/platform/plugins/business-assistant/skills/e-sign/stamp.mjs +269 -0
- package/payload/platform/plugins/linkedin-import/skills/linkedin-import/references/profile.md +5 -5
- package/payload/platform/plugins/memory/skills/conversation-archive-enrich/SKILL.md +2 -2
- package/payload/platform/plugins/memory/skills/document-ingest/SKILL.md +1 -1
- package/payload/platform/plugins/x-import/skills/x-import/SKILL.md +1 -1
- package/payload/platform/scripts/check-no-esm-require.mjs +6 -0
- package/payload/platform/scripts/logs-read.sh +117 -36
- package/payload/platform/scripts/logs-read.test.sh +234 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +13 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/session-sidecar.d.ts +7 -3
- package/payload/platform/services/claude-session-manager/dist/session-sidecar.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/session-sidecar.js +17 -11
- package/payload/platform/services/claude-session-manager/dist/session-sidecar.js.map +1 -1
- package/payload/premium-plugins/real-agent/plugins/brochures/skills/brand-design/SKILL.md +1 -1
- package/payload/premium-plugins/real-agent/plugins/brochures/skills/property-brochure/references/copy.md +2 -2
- package/payload/premium-plugins/real-agent/plugins/brochures/skills/property-brochure/references/images.md +2 -2
- package/payload/premium-plugins/real-agent/plugins/brochures/skills/property-brochure/references/index-landing.md +2 -2
- package/payload/premium-plugins/real-agent/plugins/brochures/skills/property-brochure/references/index.html +3 -3
- package/payload/premium-plugins/real-agent/plugins/brochures/skills/property-brochure/references/placeholders.md +16 -16
- package/payload/premium-plugins/real-agent/plugins/brochures/skills/property-brochure/references/registers.md +1 -1
- package/payload/premium-plugins/real-agent/plugins/brochures/skills/property-brochure/references/seller-brief.md +3 -3
- package/payload/premium-plugins/real-agent/plugins/brochures/skills/property-brochure/references/structure.md +3 -3
- package/payload/premium-plugins/real-agent/plugins/brochures/skills/property-brochure/references/template.html +5 -6
- package/payload/premium-plugins/real-agent/plugins/brochures/skills/property-extract/SKILL.md +1 -1
- package/payload/premium-plugins/real-agent/plugins/estate-business/skills/exp-partnership/SKILL.md +1 -1
- package/payload/premium-plugins/real-agent/plugins/estate-business/skills/exp-partnership/references/12-reasons.md +1 -1
- package/payload/premium-plugins/real-agent/plugins/estate-business/skills/exp-partnership/references/business-partnership.md +1 -1
- package/payload/premium-plugins/real-agent/plugins/estate-coaching/skills/bespoke-coaching/references/performance-framework.md +1 -2
- package/payload/premium-plugins/real-agent/plugins/estate-teaching/skills/content-directory/SKILL.md +1 -1
- package/payload/premium-plugins/real-agent/plugins/estate-teaching/skills/content-directory/references/module-delivery.md +1 -1
- package/payload/premium-plugins/real-agent/plugins/leads/skills/prospecting/references/database-matching.md +1 -1
- package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/SKILL.md +3 -3
- package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/references/render.py +1 -1
- package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/references/template-inputs.schema.json +1 -1
- package/payload/premium-plugins/real-agent/plugins/vendors/skills/vendor-communication/references/seller-engagement.md +4 -4
- package/payload/premium-plugins/real-agent/plugins/vendors/skills/vendor-communication/references/vendor-updates.md +2 -2
- package/payload/server/maxy-edge.js +6 -2
- package/payload/server/public/assets/admin-6QN-k3zQ.js +1 -0
- package/payload/server/public/assets/admin-6QN-k3zQ.js.br +0 -0
- package/payload/server/public/assets/admin-6QN-k3zQ.js.gz +0 -0
- package/payload/server/public/index.html +1 -1
- package/payload/server/server.js +777 -349
- package/payload/premium-plugins/real-agent/plugins/preval/skills/property-preval/references/__tests__/adherence.test.py +0 -373
- package/payload/server/public/assets/admin-DGr1HuPg.js +0 -1
- package/payload/server/public/assets/admin-DGr1HuPg.js.br +0 -0
- package/payload/server/public/assets/admin-DGr1HuPg.js.gz +0 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
This file is the substitution contract — the exhaustive list of every token used across both templates, what fills it, what format it takes, and what voice the copy tokens need. Style and composition guidance lives in `copy.md` (and is referenced inline from each `<!-- REPLACE: ... -->` comment in the templates). The two files are complementary: this one says **what** each token is; `copy.md` says **how** the copy should read.
|
|
6
6
|
|
|
7
|
-
Worked examples below
|
|
7
|
+
Worked examples below use illustrative values, not any one agent's data. When a placeholder's intent is unclear, open a brochure already rendered under `~/estate-agents/<brand-slug>/properties/<property-slug>/output/brochure.html` on this install and read the token in place.
|
|
8
8
|
|
|
9
9
|
## How to use this file
|
|
10
10
|
|
|
@@ -19,9 +19,9 @@ These tokens are used everywhere — cover, opener, folio strips, footer, alt te
|
|
|
19
19
|
|
|
20
20
|
| Token | Source | Format / length | Notes |
|
|
21
21
|
|---|---|---|---|
|
|
22
|
-
| `{{ property_name }}` | `property.json -> property_name`, or `address.line_1` if name is null | Title Case, ~10-30 chars | Distinct house name preferred ("
|
|
23
|
-
| `{{ property_slug }}` | `property.json -> source.url` (slug part) | lowercase-kebab | Used in every image filename pattern: `images/{{ property_slug }}-NN.webp`. Examples: `
|
|
24
|
-
| `{{ address_line_1 }}` | `property.json -> address.line_1` | Title Case | Street or first address line ("
|
|
22
|
+
| `{{ property_name }}` | `property.json -> property_name`, or `address.line_1` if name is null | Title Case, ~10-30 chars | Distinct house name preferred ("Elmfield House", "Wrenbury Hall"); falls back to the street ("12 Elmfield Road") when the property has no separate name. CSS renders it ALL-CAPS on the cover — source is Title Case. |
|
|
23
|
+
| `{{ property_slug }}` | `property.json -> source.url` (slug part) | lowercase-kebab | Used in every image filename pattern: `images/{{ property_slug }}-NN.webp`. Examples: `elmfield-house`, `wrenbury-hall`. |
|
|
24
|
+
| `{{ address_line_1 }}` | `property.json -> address.line_1` | Title Case | Street or first address line ("Elmfield Road", "Wrenbury Lane"). |
|
|
25
25
|
| `{{ locality }}` | `property.json -> address.locality` | Title Case | Village / town district. |
|
|
26
26
|
| `{{ town }}` | `property.json -> address.town` | Title Case | Nearest town. |
|
|
27
27
|
| `{{ county }}` | `property.json -> address.county` | Title Case | County. |
|
|
@@ -30,11 +30,11 @@ These tokens are used everywhere — cover, opener, folio strips, footer, alt te
|
|
|
30
30
|
| `{{ folio_strip }}` | Composed | `{{ property_name }} · {{ locality }}` | Repeats on every interior page's left folio slot. |
|
|
31
31
|
| `{{ folio_mid_NN }}` | Per-page chapter title | "Contents" / "Of place & pedigree" / "A home at ease" / etc. | Used in `<em>` of folio-mid. Match the chapter the page belongs to (intermissions inherit their parent chapter's mid-label). |
|
|
32
32
|
|
|
33
|
-
Worked example (
|
|
34
|
-
- `property_name` = "
|
|
35
|
-
- `property_slug` = "
|
|
36
|
-
- `address_subtitle` = `
|
|
37
|
-
- `folio_strip` = "
|
|
33
|
+
Worked example (Elmfield House):
|
|
34
|
+
- `property_name` = "Elmfield House"
|
|
35
|
+
- `property_slug` = "elmfield-house"
|
|
36
|
+
- `address_subtitle` = `Elmfield Road <em>·</em> Marlow <em>·</em> Buckinghamshire <em>·</em> SL7 1AA`
|
|
37
|
+
- `folio_strip` = "Elmfield House · Marlow"
|
|
38
38
|
|
|
39
39
|
## Category 2 — Brand pack
|
|
40
40
|
|
|
@@ -43,15 +43,15 @@ From the brand pack at `<brand_dir>/{description.md, DESIGN.md}` and `property.j
|
|
|
43
43
|
| Token | Source | Format | Notes |
|
|
44
44
|
|---|---|---|---|
|
|
45
45
|
| `{{ brand_slug }}` | `<brand_dir>` basename | lowercase-kebab | Used in logo filenames: `images/{{ brand_slug }}-logo-light.png` (dark surfaces) and `images/{{ brand_slug }}-logo-dark.png` (light surfaces). Naming convention is the **colour of the art**, not the surface — see `images.md` → "Brand logo". |
|
|
46
|
-
| `{{ brand_name }}` | `description.md` first line | Title Case |
|
|
46
|
+
| `{{ brand_name }}` | `description.md` first line | Title Case | The agent's trading name, e.g. "Hallow & Crest". |
|
|
47
47
|
| `{{ brand_town }}` | `description.md` officials block | Title Case | Branch town. |
|
|
48
48
|
| `{{ brand_county }}` | `description.md` officials block | Title Case | |
|
|
49
49
|
| `{{ brand_year }}` | Current year | YYYY | Used in the index.html footer copyright. |
|
|
50
50
|
| `{{ office_address_line_1 }}` | `description.md` registered office line | "25 Bell Street" | Street, first line of the postal address. |
|
|
51
|
-
| `{{ office_address_line_2 }}` | `description.md` registered office line | "
|
|
52
|
-
| `{{ office_phone }}` | `property.json -> agent.phone` | Display format | "
|
|
53
|
-
| `{{ office_phone_tel }}` | Composed | `tel:` link format | "+
|
|
54
|
-
| `{{ office_email }}` | `property.json -> agent.email` | lowercase | "sales@
|
|
51
|
+
| `{{ office_address_line_2 }}` | `description.md` registered office line | "Marlow, Buckinghamshire SL7 1AA" | Locality + county + postcode. |
|
|
52
|
+
| `{{ office_phone }}` | `property.json -> agent.phone` | Display format | "01628 000 000". |
|
|
53
|
+
| `{{ office_phone_tel }}` | Composed | `tel:` link format | "+441628000000" (no spaces, with country code). |
|
|
54
|
+
| `{{ office_email }}` | `property.json -> agent.email` | lowercase | "sales@example.com". |
|
|
55
55
|
| `{{ email_subject_enquiry }}` | Composed | `{{ property_name }} — enquiry` | Used in mailto: subject param (URL-encode spaces if needed). |
|
|
56
56
|
| `{{ email_subject_viewing }}` | Composed | `{{ property_name }} — viewing request` | Same as above. |
|
|
57
57
|
|
|
@@ -64,7 +64,7 @@ From `property.json -> agent`.
|
|
|
64
64
|
| `{{ agent_first_name }}` | First name | Title Case | Goes in `<em>` on the back page contact cell. |
|
|
65
65
|
| `{{ agent_surname }}` | Last name (possibly hyphenated) | Title Case | Plain weight after the first name. |
|
|
66
66
|
|
|
67
|
-
Worked example (
|
|
67
|
+
Worked example (Elmfield House): `{{ agent_first_name }}` = "Jordan", `{{ agent_surname }}` = "Hale".
|
|
68
68
|
|
|
69
69
|
## Category 4 — Listing references
|
|
70
70
|
|
|
@@ -74,7 +74,7 @@ From `property.json -> source` and `property.json -> media`.
|
|
|
74
74
|
|---|---|---|---|
|
|
75
75
|
| `{{ listing_url }}` | `source.url` | full URL | Used in QR + listing CTAs. |
|
|
76
76
|
| `{{ listing_id }}` | `source.listing_id` | numeric | |
|
|
77
|
-
| `{{ listing_display }}` | Composed | "{{ brand domain }} · listing {{ listing_id }}" | Short, scannable text for QR caption (e.g. "
|
|
77
|
+
| `{{ listing_display }}` | Composed | "{{ brand domain }} · listing {{ listing_id }}" | Short, scannable text for QR caption (e.g. "example.co.uk · listing 100000"). |
|
|
78
78
|
| `{{ video_url }}` | `media.video_tours[0]` | full URL | Empty string if no video; in that case drop the video QR per the back-page rules. |
|
|
79
79
|
| `{{ video_short_url }}` | derived | "youtu.be/<id>" or equivalent | Visible URL under the video QR. |
|
|
80
80
|
|
|
@@ -25,7 +25,7 @@ The signal is the user's wording, not the property. Don't infer premium from a h
|
|
|
25
25
|
|
|
26
26
|
## Type-role overrides
|
|
27
27
|
|
|
28
|
-
The branded register's font-family and palette swap alone produces a folio painted in the brand's colours but still wearing the template's editorial conventions — italic display, small-caps decks, gold hairlines under every heading, dropcap, italic emphasis on `<em>`. For brands whose live identity does not use those conventions (
|
|
28
|
+
The branded register's font-family and palette swap alone produces a folio painted in the brand's colours but still wearing the template's editorial conventions — italic display, small-caps decks, gold hairlines under every heading, dropcap, italic emphasis on `<em>`. For brands whose live identity does not use those conventions (an upright serif such as TrajanPro, a geometric sans such as Montserrat, prestige-country vs corporate-residential), that's a mismatch. The brochure reads as a country-house folio with brand paint, not as a brand document.
|
|
29
29
|
|
|
30
30
|
To close the gap without leaving branded territory, the brand pack's `DESIGN.md` may declare a `## Type roles` block. Each row maps a named brochure type-role to a brand styling decision. The build pipeline reads these and emits a single override CSS block — see `build.md → Type-role overlay generation` for the recipe map.
|
|
31
31
|
|
|
@@ -22,7 +22,7 @@ The 20 questions cluster into four uses: **voice setters** (Q5, Q19, Q20 — set
|
|
|
22
22
|
| **Q10.** Design details others notice | Strip-cell captions (page 4); attr-grid features (page 7 feature page) |
|
|
23
23
|
| **Q11.** What you love about the local area | Garden & ground page text panels (rear garden, front & approach, ancillary) |
|
|
24
24
|
| **Q12.** Hidden gems nearby | Garden & ground secondary panel; possibly a footnote on the back page |
|
|
25
|
-
| **Q13.** Commute / transport | Opener spec line (e.g. "
|
|
25
|
+
| **Q13.** Commute / transport | Opener spec line (e.g. "*<county> · <postcode> · 50 minutes from <terminus>*") if commutable |
|
|
26
26
|
| **Q14.** Community vibe | Garden & ground tone; back-page tagline supporting line |
|
|
27
27
|
| **Q15.** Who this home suits best | Buyer-avatar tone (reinforces Q7) |
|
|
28
28
|
| **Q16.** Hoped-for next buyer | Tone of the back-page CTA ("Arrange a viewing of …") |
|
|
@@ -37,9 +37,9 @@ Sellers' answers are conversational. The brochure's register is editorial. Most
|
|
|
37
37
|
|
|
38
38
|
`[VERBATIM]` is a contract: the answer is preserved exactly, in italic, as a pull-quote on a chapter page or as the back-page tagline. Use it sparingly — at most one or two per brochure. The Q20 answer is the most common candidate; Q4 and Q5 occasionally.
|
|
39
39
|
|
|
40
|
-
## Worked example —
|
|
40
|
+
## Worked example — Elmfield House
|
|
41
41
|
|
|
42
|
-
(Hypothetical —
|
|
42
|
+
(Hypothetical — Elmfield House shipped without a brief; a real example would source from the seller.)
|
|
43
43
|
|
|
44
44
|
> **Q5.** A buyer should feel that the home is *settled*. Six hundred years of beam and stone with a kitchen that wants to be cooked in.
|
|
45
45
|
>
|
|
@@ -89,7 +89,7 @@ If a brochure has multiple floor plans (per-floor breakdowns), tune the layout t
|
|
|
89
89
|
|
|
90
90
|
Property listings in this register often have several distinct outdoor or auxiliary buildings, each photographed separately. The brochure must keep them distinct in copy *and* match each photo to the right structure. The recurring failure mode is collapsing them into a single noun ("the cedar pavilion", "the garden complex") that means different things on different pages.
|
|
91
91
|
|
|
92
|
-
For
|
|
92
|
+
For period country homes the typical glossary is:
|
|
93
93
|
|
|
94
94
|
| Feature | Description | Photo cue |
|
|
95
95
|
|---|---|---|
|
|
@@ -142,7 +142,7 @@ The back page includes two QR codes, one for the video tour and one for the list
|
|
|
142
142
|
|
|
143
143
|
1. The QR image itself (for print scanning).
|
|
144
144
|
2. A short label above describing what it links to ("Watch the *video tour*", "View the *full listing*").
|
|
145
|
-
3. **A visible, underlined URL beneath the label** — short and human-readable (e.g. `youtu.be
|
|
145
|
+
3. **A visible, underlined URL beneath the label** — short and human-readable (e.g. `youtu.be/<video-id>`, `example.co.uk · listing 100000`).
|
|
146
146
|
4. **The whole stack wrapped in an `<a>` tag with `target="_blank" rel="noopener"`** so the QR image, label and URL are all clickable in the digital PDF and on the web.
|
|
147
147
|
|
|
148
148
|
Markup:
|
|
@@ -194,7 +194,7 @@ The brochure includes a location map **only when the source listing supplies one
|
|
|
194
194
|
|
|
195
195
|
### How the conditional works
|
|
196
196
|
|
|
197
|
-
`property-extract` screenshots whatever map element exists on the listing — provider-agnostic by design. Google Maps iframes (Loop CRM
|
|
197
|
+
`property-extract` screenshots whatever map element exists on the listing — provider-agnostic by design. Google Maps iframes (Loop CRM tenants), Mapbox or Leaflet div+canvas builds, static-map `<img>` tags from Rightmove-style backends, Apple/Bing embeds — they all land as `<property_dir>/images/map.webp` and `media.map: "map.webp"` in `property.json`. When no map element is detected on the listing, both `media.map` and (typically) `address.geo` are `null`. See **Map — extract or omit** in the property-extract SKILL for the heuristic detector and the screenshot contract.
|
|
198
198
|
|
|
199
199
|
The template ships with a marker-bounded conditional block on page 10 (Plan & particulars), in the `.spec-side` column above the EPC bars:
|
|
200
200
|
|
|
@@ -36,8 +36,7 @@
|
|
|
36
36
|
references/placeholders.md
|
|
37
37
|
Each <!-- REPLACE: ... --> inline comment in this file gives the
|
|
38
38
|
short version (what fills the token, how long, what voice). The
|
|
39
|
-
placeholders.md file gives the long version with worked examples
|
|
40
|
-
from the validated muvin Sparrows Farm reference brochure.
|
|
39
|
+
placeholders.md file gives the long version with worked examples.
|
|
41
40
|
|
|
42
41
|
Re-skin: agents override the design tokens in :root from their
|
|
43
42
|
brand DESIGN.md per the property-brochure SKILL ("registers" — see
|
|
@@ -45,7 +44,7 @@
|
|
|
45
44
|
|
|
46
45
|
Image filenames follow the pattern {{ property_slug }}-NN.webp,
|
|
47
46
|
where {{ property_slug }} is the literal slug from the property
|
|
48
|
-
directory (e.g. "
|
|
47
|
+
directory (e.g. "elmfield-house" or "wrenbury-hall") and NN is
|
|
49
48
|
the zero-padded image index per references/images.md.
|
|
50
49
|
═══════════════════════════════════════════════════ */
|
|
51
50
|
:root {
|
|
@@ -1422,9 +1421,9 @@
|
|
|
1422
1421
|
<div class="cover-title-line">
|
|
1423
1422
|
<span class="flank"></span>
|
|
1424
1423
|
<!-- REPLACE {{ property_name }}: the property's distinct house name
|
|
1425
|
-
(e.g. "
|
|
1424
|
+
(e.g. "Elmfield House", "Wrenbury Hall") OR — if the property has
|
|
1426
1425
|
no separate name — the street as a tracked-caps proper noun
|
|
1427
|
-
(e.g. "12
|
|
1426
|
+
(e.g. "12 Elmfield Road"). All-caps display via CSS; the source
|
|
1428
1427
|
should be Title Case. Capped at ~28 characters to fit between
|
|
1429
1428
|
the gold flank rules. See placeholders.md for the picking rule. -->
|
|
1430
1429
|
<h1 class="cover-title">{{ property_name }}</h1>
|
|
@@ -2354,7 +2353,7 @@
|
|
|
2354
2353
|
|
|
2355
2354
|
<!-- REPLACE the agent + office cells. {{ agent_first_name }} sits in
|
|
2356
2355
|
<em>; {{ agent_surname }} follows in normal weight. {{ office_phone }}
|
|
2357
|
-
is the display format ("
|
|
2356
|
+
is the display format ("01628 000 000"); {{ office_email }} the
|
|
2358
2357
|
contact email; {{ office_address_line_1 }} and
|
|
2359
2358
|
{{ office_address_line_2 }} the postal lines. -->
|
|
2360
2359
|
<div class="back-contact-grid">
|
package/payload/premium-plugins/real-agent/plugins/brochures/skills/property-extract/SKILL.md
CHANGED
|
@@ -15,7 +15,7 @@ allowed-tools:
|
|
|
15
15
|
|
|
16
16
|
Turn one publicly-accessible property listing URL into a self-contained property assets directory that `property-brochure` can consume without re-visiting the source site.
|
|
17
17
|
|
|
18
|
-
The contract is **portal-agnostic**: the output shape is the same whether the source is an agent's own site (
|
|
18
|
+
The contract is **portal-agnostic**: the output shape is the same whether the source is an agent's own site (Loop CRM tenants, custom builds) or a portal (Rightmove, Zoopla, OnTheMarket, OpenRent). The static-HTML probe is the cheap default; when it returns no asset URLs or the data is tab-bound, fall back to a headless browser. See **Process — high level**.
|
|
19
19
|
|
|
20
20
|
## Outcome contract
|
|
21
21
|
|
package/payload/premium-plugins/real-agent/plugins/estate-business/skills/exp-partnership/SKILL.md
CHANGED
|
@@ -13,7 +13,7 @@ Real Agency Club operates within the eXp UK framework. Members benefit from unde
|
|
|
13
13
|
|
|
14
14
|
2. **Lead with the problem.** Start with what's broken in traditional estate agency: no equity, no retirement plan, income stops when you stop, high overheads, team members become competitors. Then show how eXp addresses each one.
|
|
15
15
|
|
|
16
|
-
3. **Always defer commercial specifics to
|
|
16
|
+
3. **Always defer commercial specifics to a human.** You can explain the model, share general figures, and point to resources. But specific commission negotiations, partnership terms, and formal presentations should always go through the account's own partnership lead or the eXp UK team.
|
|
17
17
|
|
|
18
18
|
## What You Should Know
|
|
19
19
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 12 Reasons a Business Should Consider Partnering With eXp
|
|
2
2
|
|
|
3
|
-
Adapted from
|
|
3
|
+
Adapted from a "12 Days of Christmas" partnership guide.
|
|
4
4
|
|
|
5
5
|
## 1. You Keep Your Brand
|
|
6
6
|
Partnering doesn't mean losing your identity. Your brand, listings, data, and reputation stay yours. eXp is the platform underneath, not the face of your business.
|
|
@@ -89,4 +89,4 @@ You can leave with 2 weeks' notice and go back to how you operated previously. N
|
|
|
89
89
|
|
|
90
90
|
## Important
|
|
91
91
|
|
|
92
|
-
All commercial terms, partnership specifics, and formal presentations should go through
|
|
92
|
+
All commercial terms, partnership specifics, and formal presentations should go through the account's own partnership lead and the eXp UK team. This reference is for education — not for making commitments.
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
# Performance Coaching Framework
|
|
2
|
-
*Source: BackleyBlack (Steve Backley OBE & Roger Black MBE) + Adam Mackay & Jamie Fisher*
|
|
3
2
|
|
|
4
3
|
## 45 Core Behavioural Characteristics of Top Estate Agents
|
|
5
4
|
|
|
@@ -106,4 +105,4 @@ When coaching a member:
|
|
|
106
105
|
- Use the **45 characteristics** as a diagnostic — ask them to pick their top 3 strengths and top 3 development areas
|
|
107
106
|
- Use the **10 PCDEs** for a structured self-assessment (monthly check-in)
|
|
108
107
|
- Use the **Mind-Setting phases** as a progressive programme — members work through at their own pace
|
|
109
|
-
- Always attribute
|
|
108
|
+
- Always attribute the framework to whoever the install's own content directory credits for it; never present it as the agent's own invention
|
package/payload/premium-plugins/real-agent/plugins/estate-teaching/skills/content-directory/SKILL.md
CHANGED
|
@@ -34,6 +34,6 @@ A member asks about available content, wants to learn something, requests a spec
|
|
|
34
34
|
| Agency Blueprint | Setting up and structuring an agency | New agents, career changers |
|
|
35
35
|
| Real Marketing | Social media, content creation, personal brand | Brand builders, content creators |
|
|
36
36
|
| Real Business | Systems, structure, profit, scaling | Growth-stage agents |
|
|
37
|
-
| Real Coaching | Mindset, performance
|
|
37
|
+
| Real Coaching | Mindset, performance | Anyone wanting mental edge |
|
|
38
38
|
| Podcast | Conversations with working agents | Inspiration, peer learning |
|
|
39
39
|
| Resources | Templates, scripts, SOPs, tools | Practical application |
|
|
@@ -34,7 +34,7 @@ If the member asks for a topic without specifying a module, search across all pi
|
|
|
34
34
|
|
|
35
35
|
1. **Introduce** — What the module covers, who it's from, why it matters for this member specifically. Reference their profile context.
|
|
36
36
|
|
|
37
|
-
2. **Teach the core concept** — Present the key insight or framework. Use the contributor's voice and perspective
|
|
37
|
+
2. **Teach the core concept** — Present the key insight or framework. Use the contributor's voice and perspective, naming them as the module's own metadata credits them: "<contributor>'s approach to this is..."
|
|
38
38
|
|
|
39
39
|
3. **Check understanding** — Ask the member to reflect. "How does this compare to how you currently handle...?" or "What would that look like for your patch?"
|
|
40
40
|
|
|
@@ -13,7 +13,7 @@ When a new property goes live, proactively match it against registered buyers. A
|
|
|
13
13
|
|
|
14
14
|
## Outreach Message Principles
|
|
15
15
|
|
|
16
|
-
- **Personal** — reference their requirements: "I know you mentioned you were looking for a 3-bed with a garden in the
|
|
16
|
+
- **Personal** — reference their requirements: "I know you mentioned you were looking for a 3-bed with a garden in the <town> area..."
|
|
17
17
|
- **Enthusiastic but honest** — share what makes this property a good match for them specifically
|
|
18
18
|
- **Low pressure** — "I thought this might be worth a look" not "You need to see this before it's gone"
|
|
19
19
|
- **Call to action** — offer a viewing with specific time options
|
|
@@ -36,8 +36,8 @@ Schema requires `brand.font_display` to be set for `on-brand` to engage — a mi
|
|
|
36
36
|
|
|
37
37
|
## Inputs (never invented)
|
|
38
38
|
|
|
39
|
-
- **`address`** — full UK address ending in a valid postcode (e.g. `
|
|
40
|
-
- **`brand`** — agent design tokens, same shape as property-market-report: `name`, `tagline`, hex tokens `primary` / `primary_dark` / `accent` / `paper` / `paper_banded` / `rule`. Optional `logo_light` / `logo_dark` filesystem paths. Resolved from the agent's `DESIGN.md` (
|
|
39
|
+
- **`address`** — full UK address ending in a valid postcode (e.g. `12 Elmfield Road, Marlow, SL7 1AA`). Skill derives the postcode by matching the UK postcode regex on the final token group; stops if no match.
|
|
40
|
+
- **`brand`** — agent design tokens, same shape as property-market-report: `name`, `tagline`, hex tokens `primary` / `primary_dark` / `accent` / `paper` / `paper_banded` / `rule`. Optional `logo_light` / `logo_dark` filesystem paths. Resolved from the agent's `DESIGN.md` (`~/estate-agents/<brand-slug>/DESIGN.md`); read the canonical hex values directly, never substitute "close" colours. Only colour tokens are consumed; typography/spacing/radius tokens in the agent's `DESIGN.md` are ignored by design.
|
|
41
41
|
- **`subject`** — paste-in details about the property being valued:
|
|
42
42
|
- `cover_hero` (filesystem path or URL to a photo of the subject — required for the cover).
|
|
43
43
|
- Optional `beds`, `type`, `tenure`, `condition` (well / average / needs work).
|
|
@@ -76,7 +76,7 @@ EPC certificate fields are operator paste-in — see `subject.total_floor_area_m
|
|
|
76
76
|
|
|
77
77
|
## Output
|
|
78
78
|
|
|
79
|
-
Directory under `~/estate-agents/<brand-slug>/preval/<address-slug>/` (e.g. `~/estate-agents
|
|
79
|
+
Directory under `~/estate-agents/<brand-slug>/preval/<address-slug>/` (e.g. `~/estate-agents/<brand-slug>/preval/12-elmfield-road-sl7-1aa/`) containing:
|
|
80
80
|
|
|
81
81
|
- `index.html` — single self-contained file.
|
|
82
82
|
- `img/` — copied logos and the subject hero image.
|
|
@@ -312,7 +312,7 @@ def _street_token(address: str) -> str | None:
|
|
|
312
312
|
"""First non-stopword token from the address's street component.
|
|
313
313
|
|
|
314
314
|
Used to detect cover_hero photos sourced from a sister property folder.
|
|
315
|
-
"
|
|
315
|
+
"12 Elmfield Road, Marlow, SL7 1AA" → "elmfield".
|
|
316
316
|
"""
|
|
317
317
|
if not address:
|
|
318
318
|
return None
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"out_dir": { "type": "string", "description": "Absolute path to the output directory (created if missing)." },
|
|
13
13
|
"filename_stem": { "type": "string", "description": "PDF filename stem; .pdf appended by the renderer." },
|
|
14
14
|
"address": { "type": "string", "description": "Full UK address as the operator gave it." },
|
|
15
|
-
"postcode": { "type": "string", "description": "Postcode derived from the address, e.g. '
|
|
15
|
+
"postcode": { "type": "string", "description": "Postcode derived from the address, e.g. 'SL7 1AA'." },
|
|
16
16
|
"generated": { "type": "string", "description": "Human date, e.g. '19 May 2026'." },
|
|
17
17
|
"variant": { "type": "string", "enum": ["premium", "on-brand"], "description": "Render variant. 'premium' (default) keeps the editorial Cormorant/Lora/Inter typography lock and only applies the brand's colours + logo. 'on-brand' overrides the type system with brand.font_display + brand.font_body — the pack follows the agent's identity end-to-end. Decision rule: if the operator says 'premium' use premium; if brand.font_display + brand.font_body are both set and the operator did not say premium, default to 'on-brand'; otherwise premium." },
|
|
18
18
|
|
|
@@ -15,16 +15,16 @@ Convert a seller enquiry into a booked market appraisal with Alex. Understand th
|
|
|
15
15
|
- Bereavement — handle with extra sensitivity
|
|
16
16
|
3. **Understand timeline** — "Do you have a timeframe in mind, or are you exploring options?"
|
|
17
17
|
4. **Understand their position** — "Will you be buying as well, or just selling?"
|
|
18
|
-
5. **Book the appraisal** — "
|
|
18
|
+
5. **Book the appraisal** — "We'd love to come and see the property. You'll get an honest assessment of the market, a pricing strategy, and see exactly how we'd market your home. When works best for you?"
|
|
19
19
|
|
|
20
20
|
## Switching Agents
|
|
21
21
|
|
|
22
|
-
Many
|
|
22
|
+
Many clients come after frustration with another agent. Handle this carefully:
|
|
23
23
|
|
|
24
24
|
1. **Listen first** — let them vent if they need to. Acknowledge the frustration without fuelling it
|
|
25
25
|
2. **Never name or criticise the other agent** — "That sounds really frustrating" is enough
|
|
26
|
-
3. **Focus forward** — explain what
|
|
27
|
-
- One point of contact
|
|
26
|
+
3. **Focus forward** — explain what we do differently:
|
|
27
|
+
- One point of contact, not a rotating team
|
|
28
28
|
- Quality marketing for every property (photography, video, drone, social media)
|
|
29
29
|
- Proactive communication (you'll never have to chase)
|
|
30
30
|
- Track record of turning around stale listings
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
Keep vendors informed and confident throughout the marketing period. The number one complaint about estate agents is silence —
|
|
5
|
+
Keep vendors informed and confident throughout the marketing period. The number one complaint about estate agents is silence — we don't do silence.
|
|
6
6
|
|
|
7
7
|
## Update Cadence
|
|
8
8
|
|
|
9
|
-
- **Weekly minimum** — every vendor should hear from
|
|
9
|
+
- **Weekly minimum** — every vendor should hear from us at least once a week
|
|
10
10
|
- **After every viewing** — feedback relayed within 24-48 hours
|
|
11
11
|
- **After any portal activity spike** — if a listing gets a burst of saves or clicks, share it
|
|
12
12
|
- **When there's nothing to report** — say so. "No viewings this week, but here's what we're doing next" is infinitely better than silence
|
|
@@ -577,6 +577,10 @@ function classifyUpstreamError(err) {
|
|
|
577
577
|
function formatUpstreamErrorLine(args) {
|
|
578
578
|
return `[edge] ts=${(/* @__PURE__ */ new Date()).toISOString()} upstream http error path=${proxyPathname(args.url)} err=${args.errMessage} err-class=${args.errClass} upstream=${args.upstream}`;
|
|
579
579
|
}
|
|
580
|
+
function formatUpstreamUpgradeLine(args) {
|
|
581
|
+
const err = args.errMessage === void 0 ? "" : ` err=${args.errMessage}`;
|
|
582
|
+
return `[edge] ts=${(/* @__PURE__ */ new Date()).toISOString()} upstream upgrade ${args.kind} path=${proxyPathname(args.url)}${err}`;
|
|
583
|
+
}
|
|
580
584
|
var HTML_ESCAPES = { "<": "<", ">": ">", "&": "&", '"': """, "'": "'" };
|
|
581
585
|
function escapeHtml(s) {
|
|
582
586
|
return String(s).replace(/[<>&"']/g, (c) => HTML_ESCAPES[c] ?? c);
|
|
@@ -792,7 +796,7 @@ function forwardUpgrade(req, clientSocket, head) {
|
|
|
792
796
|
upstream.once("close", () => teardown("upstream-close"));
|
|
793
797
|
clientSocket.once("error", () => teardown("client-error"));
|
|
794
798
|
upstream.once("error", (err) => {
|
|
795
|
-
console.error(
|
|
799
|
+
console.error(formatUpstreamUpgradeLine({ kind: "error", url: req.url ?? "", errMessage: err.message }));
|
|
796
800
|
try {
|
|
797
801
|
clientSocket.write("HTTP/1.1 502 Bad Gateway\r\nConnection: close\r\nContent-Length: 0\r\n\r\n");
|
|
798
802
|
} catch {
|
|
@@ -800,7 +804,7 @@ function forwardUpgrade(req, clientSocket, head) {
|
|
|
800
804
|
teardown("upstream-error");
|
|
801
805
|
});
|
|
802
806
|
upstream.once("timeout", () => {
|
|
803
|
-
console.error(
|
|
807
|
+
console.error(formatUpstreamUpgradeLine({ kind: "timeout", url: req.url ?? "" }));
|
|
804
808
|
try {
|
|
805
809
|
clientSocket.write("HTTP/1.1 504 Gateway Timeout\r\nConnection: close\r\nContent-Length: 0\r\n\r\n");
|
|
806
810
|
} catch {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{o as e}from"./chunk-CltuBf4Z.js";import{B as t,D as n,K as r,V as i,W as a,a as o,l as s,m as c,p as l,s as u,v as ee}from"./useSubAccountSwitcher-CJKjEPNN.js";import{n as d,t as f}from"./AdminLoginScreens-bksN0XPe.js";import{s as p}from"./useVoiceRecorder-ChnBrzbZ.js";import{a as m,b as h,f as te,t as g,y as ne}from"./AdminShell-BMPEWNZk.js";import{a as _,i as v,n as y,r as re}from"./page-BWFHRIAH.js";import{t as b}from"./chevron-left-DqYv3oFh.js";import{t as x}from"./chevron-right-CQfPGsFb.js";import{t as S}from"./file-text-DMKw3nIk.js";import"./graph-labels-BZgd0L6n.js";var C=a(),w=e(r(),1),T=t();function E({onOpen:e}){return(0,T.jsx)(`footer`,{className:`admin-footer`,children:(0,T.jsxs)(`div`,{className:`powered-by`,role:`button`,tabIndex:0,onClick:e,onKeyDown:t=>{(t.key===`Enter`||t.key===` `)&&(t.preventDefault(),e())},"aria-label":`Powered by Claude Code — show details`,children:[(0,T.jsx)(`img`,{src:`/brand/claude.png`,alt:`Claude`,className:`powered-by-icon`}),(0,T.jsx)(`span`,{children:`Powered by Claude Code`})]})})}function D({sessionId:e,projectDir:t,sessionKey:n,contactName:r,onBack:i}){return(0,T.jsxs)(`div`,{className:`wa-web`,children:[(0,T.jsxs)(`header`,{className:`wa-web-header`,children:[i&&(0,T.jsx)(`button`,{type:`button`,className:`wa-web-back`,onClick:i,"aria-label":`Back`,children:(0,T.jsx)(b,{size:20})}),(0,T.jsx)(`span`,{className:`wa-web-avatar`,"aria-hidden":!0,children:(0,T.jsx)(_,{size:20})}),(0,T.jsx)(`div`,{className:`wa-web-titles`,children:(0,T.jsx)(`span`,{className:`wa-web-name`,children:r})})]}),(0,T.jsx)(`div`,{className:`wa-web-body`,children:(0,T.jsx)(p,{sessionId:e,projectDir:t,sessionKey:n,forceDeliveredOnly:!0})})]})}var O=6e4,k=60*O,A=24*k,j=O,M=[`Jan`,`Feb`,`Mar`,`Apr`,`May`,`Jun`,`Jul`,`Aug`,`Sep`,`Oct`,`Nov`,`Dec`],N=[`Sun`,`Mon`,`Tue`,`Wed`,`Thu`,`Fri`,`Sat`];function P(e){return e<10?`0${e}`:String(e)}function F(e){return new Date(e.getFullYear(),e.getMonth(),e.getDate()).getTime()}function I(e,t){if(!Number.isFinite(e)||e<=0||!Number.isFinite(t))return`—`;let n=t-e;if(n<-j)return`—`;let r=n<0?0:n;if(r<O)return`just now`;if(r<k)return`${Math.floor(r/O)}m`;if(r<A)return`${Math.floor(r/k)}h`;let i=new Date(t),a=new Date(e),o=Math.round((F(i)-F(a))/A);if(o===1)return`yesterday`;if(o>=2&&o<=6)return N[a.getDay()];let s=P(a.getDate()),c=M[a.getMonth()];return a.getFullYear()===i.getFullYear()?`${s} ${c}`:`${s} ${c} ${a.getFullYear()}`}var ie={whatsapp:`WhatsApp`,telegram:`Telegram`,webchat:`Web chat`};function L({variant:e=`row`,count:t=1}){return(0,T.jsx)(T.Fragment,{children:Array.from({length:t},(t,n)=>(0,T.jsx)(`span`,{className:`dashboard-skeleton dashboard-skeleton--${e}`,"data-testid":`dashboard-skeleton`,"aria-hidden":`true`},n))})}var ae=6e4;function oe(e){return e<12?`morning`:e<18?`afternoon`:`evening`}function R(e){return e&&e.trim().split(/\s+/)[0]||`there`}function se(e){if(!e)return`everything is running normally.`;let t=e.cpuPct,n=e.memUsedPct;return t!==null&&t>=.98||n!==null&&n>=.98?`system load is critical.`:t!==null&&t>=.9||n!==null&&n>=.9?`system load is high.`:`everything is running normally.`}function z(e){return e==null?`—`:`${Math.round(e*100)}%`}function B(e){return e.operatorName??e.whatsappName??e.senderId??e.title}function V(e,t){if(!e)return!1;let n=Date.parse(e);if(!Number.isFinite(n))return!1;let r=new Date(n),i=new Date(t);return r.getFullYear()===i.getFullYear()&&r.getMonth()===i.getMonth()&&r.getDate()===i.getDate()}function H(e){return e?e.replace(/^claude-/,``):null}var U=new Set([`sessions`,`conversations`,`tasks`,`artefacts`,`skills`,`stats`]),W=!1;function G(e,t,n,r){let i=e=>t.get(e)??null,a=r?r.getEntriesByType(`navigation`)[0]:void 0,o=r?r.getEntriesByType(`resource`).filter(e=>new URL(e.name,`http://x`).pathname.startsWith(`/assets/`)):[],s=e=>e===void 0||!Number.isFinite(e)?null:Math.round(e),c=e=>e?s(e):null;return{batchMs:e,sessionsMs:i(`sessions`),conversationsMs:i(`conversations`),tasksMs:i(`tasks`),artefactsMs:i(`artefacts`),skillsMs:i(`skills`),statsMs:i(`stats`),failed:[...U].filter(e=>n.has(e)).join(`,`)||`none`,ttfbMs:s(a?.responseStart),domContentLoadedMs:c(a?.domContentLoadedEventEnd),loadMs:c(a?.loadEventEnd),assetCount:o.length,assetBytes:o.reduce((e,t)=>e+(t.transferSize||0),0),assetMs:o.length?s(Math.max(...o.map(e=>e.responseEnd))):null}}function K({cacheKey:e,userName:t,onSelectWhatsappConversation:r}){let a=c(e),[l,d]=(0,w.useState)(null),[f,p]=(0,w.useState)(null),[m,g]=(0,w.useState)(null),_=(0,w.useRef)(!1),[v,y]=(0,w.useState)(null),[b,C]=(0,w.useState)(null),[E,D]=(0,w.useState)(null),[O,k]=(0,w.useState)(null),[A,j]=(0,w.useState)(new Set),[M,N]=(0,w.useState)(()=>new Set),P=(0,w.useCallback)(e=>{N(t=>t.has(e)?t:new Set(t).add(e))},[]),F=Date.now(),U=(0,w.useRef)(!1),K=(0,w.useRef)(new Map),q=(0,w.useRef)(new Set),J=(0,w.useRef)(null);(0,w.useEffect)(()=>{if(!e)return;let t=!1;async function n(e,n,r,i){let o=Date.now(),s=typeof n==`string`?n:`unresolved`;try{typeof n!=`string`&&(s=await n());let c=await a(s);if(!c.ok)throw Error(`status ${c.status}`);let l=await c.json();t||i(r(l)),console.info(`[admin-ui] op=dashboard-fetch slice=${e} url=${s} outcome=ok ms=${Date.now()-o}`)}catch(t){q.current.add(e),console.error(`[admin-ui] dashboard-fetch-failed slice=${e} url=${s} ms=${Date.now()-o} reason=${t instanceof Error?t.message:String(t)}`)}finally{K.current.set(e,Date.now()-o),P(e)}}async function r(){if(!J.current){let e=await a(`/api/admin/accounts`);if(!e.ok)throw Error(`accounts status ${e.status}`);let t=await e.json(),n=t.accounts??[],r=t.activeAccountId??null,i=n.find(e=>e.accountId===r);if(!i)throw Error(`accounts: active ${r??`none`} absent from list of ${n.length}`);let o=i.isHouse||n.length===1;J.current={accountId:i.accountId,isHouse:o}}let{accountId:e,isHouse:t}=J.current;return console.info(`[admin-ui] op=dashboard-channels-source source=${t?`all`:`scoped`} account=${e.slice(0,8)}`),t?`/api/whatsapp-reader/conversations-all`:`/api/whatsapp-reader/conversations`}let o={"sidebar-sessions":()=>n(`sessions`,`/api/admin/sidebar-sessions`,e=>e.sessions??[],d),"conversations-all":()=>n(`conversations`,r,e=>e.conversations??[],p),"tasks-list":()=>n(`tasks`,`/api/admin/tasks-list`,e=>e,C),"sidebar-artefacts":()=>n(`artefacts`,`/api/admin/sidebar-artefacts`,e=>e.artefacts??[],D),skills:()=>n(`skills`,`/api/admin/skills`,e=>e.skills??[],k)};async function s(){if(U.current)return;U.current=!0;let e=Date.now();try{await Promise.all([...Object.values(o).map(e=>e()),n(`stats`,`/api/admin/system-stats`,e=>e,y)])}finally{U.current=!1;let t=Date.now()-e;if(console.info(`[admin-ui] op=dashboard-refresh-settled ms=${t}`),!W){W=!0;try{let e=G(t,K.current,q.current,typeof performance>`u`?null:performance);fetch(`/api/_client-error`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({kind:`event`,source:`dashboard-load`,...e}),keepalive:!0}).catch(()=>{})}catch{}}}}s();let c=null;try{c=new EventSource(i(`/api/admin/events`,e)),c.onmessage=e=>{if(document.visibilityState===`visible`)try{let{resource:t}=JSON.parse(e.data),n=t?o[t]:void 0;n&&n()}catch{}},c.onerror=()=>{console.error(`[admin-ui] dashboard-events stream error`)}}catch(e){console.error(`[admin-ui] dashboard-events unavailable reason=${e instanceof Error?e.message:String(e)}`)}let l=setInterval(()=>{document.visibilityState===`visible`&&s()},ae),u=()=>{document.visibilityState===`visible`&&s()};return document.addEventListener(`visibilitychange`,u),()=>{t=!0,U.current=!1,clearInterval(l),c?.close(),document.removeEventListener(`visibilitychange`,u)}},[e,a]);let Y=(0,w.useMemo)(()=>(l??[]).filter(e=>!e.isSubagent),[l]),X=(0,w.useMemo)(()=>Y.filter(e=>e.live),[Y]),Z=(0,w.useMemo)(()=>(f??[]).filter(e=>V(e.lastMessageAt,F)).length,[f,F]),ce=b?.open.length??0,Q=(0,w.useMemo)(()=>{let e=new Map;for(let t of f??[]){let n=e.get(t.channel)??[];n.push(t),e.set(t.channel,n)}return[...e.entries()].map(([e,t])=>{let n=t.reduce((e,t)=>t.lastMessageAt&&(!e||Date.parse(t.lastMessageAt)>Date.parse(e.lastMessageAt??``))?t:e,null);return{channel:e,count:t.length,preview:n?B(n):``,rows:t}})},[f]);(0,w.useEffect)(()=>{_.current||Q.length!==0&&(_.current=!0,g(Q[0].channel))},[Q]);let le=(0,w.useCallback)(async e=>{if(!A.has(e)){j(t=>new Set(t).add(e));try{let t=await a(`/api/admin/session-stop`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({sessionId:e})});if(!t.ok){console.error(`[admin-ui] dashboard-session-stop-failed sessionId=${e} status=${t.status}`);return}console.info(`[admin-ui] dashboard-session-stop sessionId=${e}`),d(t=>(t??[]).map(t=>t.sessionId===e?{...t,live:!1}:t))}catch(t){console.error(`[admin-ui] dashboard-session-stop-failed sessionId=${e} reason=${t instanceof Error?t.message:String(t)}`)}finally{j(t=>{let n=new Set(t);return n.delete(e),n})}}},[a,A]),ue=(0,w.useCallback)(t=>{if(!e||!t.downloadPath){console.error(`[admin-ui] dashboard-artefact-download-blocked id=${t.id} reason=${e?`not-downloadable`:`no-cache-key`}`);return}re(e,t.downloadPath,t.downloadRoot??`data`)},[e]),$=oe(new Date(F).getHours());return(0,T.jsxs)(`div`,{className:`dashboard-home`,children:[(0,T.jsx)(`p`,{className:`dashboard-greeting`,children:M.has(`stats`)?`Good ${$}, ${R(t)} — ${se(v)}`:`Good ${$}, ${R(t)}`}),(0,T.jsxs)(`div`,{className:`dashboard-stats`,children:[(0,T.jsxs)(`div`,{className:`admin-stat-card dashboard-stat-card`,children:[(0,T.jsx)(`span`,{className:`dashboard-stat-label`,children:`Live sessions`}),M.has(`sessions`)?(0,T.jsxs)(T.Fragment,{children:[(0,T.jsx)(`span`,{className:`dashboard-stat-value`,children:X.length}),(0,T.jsxs)(`span`,{className:`dashboard-stat-delta`,children:[`of `,Y.length,` total`]})]}):(0,T.jsx)(L,{variant:`value`})]}),(0,T.jsxs)(`div`,{className:`admin-stat-card dashboard-stat-card`,children:[(0,T.jsx)(`span`,{className:`dashboard-stat-label`,children:`Messages today`}),M.has(`conversations`)?(0,T.jsx)(`span`,{className:`dashboard-stat-value`,children:Z}):(0,T.jsx)(L,{variant:`value`})]}),(0,T.jsxs)(`a`,{className:`admin-stat-card dashboard-stat-card dashboard-stat-card--link`,href:`/tasks`,children:[(0,T.jsx)(`span`,{className:`dashboard-stat-label`,children:`Open tasks`}),M.has(`tasks`)?(0,T.jsx)(`span`,{className:`dashboard-stat-value`,children:ce}):(0,T.jsx)(L,{variant:`value`})]}),(0,T.jsxs)(`div`,{className:`admin-stat-card dashboard-stat-card`,children:[(0,T.jsx)(`span`,{className:`dashboard-stat-label`,children:`Host CPU`}),M.has(`stats`)?(0,T.jsxs)(T.Fragment,{children:[(0,T.jsx)(`span`,{className:`dashboard-stat-value`,children:z(v?.cpuPct??null)}),(0,T.jsxs)(`span`,{className:`dashboard-stat-delta`,children:[`RAM `,z(v?.memUsedPct??null)]})]}):(0,T.jsx)(L,{variant:`value`})]})]}),(0,T.jsxs)(`div`,{className:`dashboard-columns`,children:[(0,T.jsxs)(`div`,{className:`dashboard-col`,children:[(0,T.jsx)(`span`,{className:`admin-section-label dashboard-col-label`,children:`Live now`}),M.has(`sessions`)?X.length===0?(0,T.jsx)(`div`,{className:`admin-card dashboard-empty`,children:`No live sessions right now.`}):X.map(e=>{let t=[e.sessionId.slice(0,8),H(e.model),I(Date.parse(e.startedAt),F)].filter(Boolean).join(` · `);return(0,T.jsxs)(`div`,{className:`admin-card dashboard-live-card`,children:[(0,T.jsx)(`span`,{className:`admin-status-dot is-live`}),(0,T.jsxs)(`div`,{className:`dashboard-live-main`,children:[(0,T.jsx)(`span`,{className:`dashboard-live-name`,children:e.title}),(0,T.jsx)(`span`,{className:`dashboard-live-meta`,children:t})]}),(0,T.jsx)(`a`,{className:`admin-btn-cta dashboard-open-btn`,href:`/chat?session=${encodeURIComponent(e.sessionId)}`,children:`Open`}),(0,T.jsx)(`button`,{type:`button`,className:`dashboard-stop-btn`,"aria-label":`Stop ${e.title}`,disabled:A.has(e.sessionId),onClick:()=>void le(e.sessionId),children:(0,T.jsx)(ee,{size:12})})]},e.sessionId)}):(0,T.jsx)(L,{variant:`card`,count:2}),(0,T.jsx)(`span`,{className:`admin-section-label dashboard-col-label`,children:`Recent artefacts`}),(0,T.jsx)(`div`,{className:`admin-card dashboard-artefacts`,children:M.has(`artefacts`)?(E??[]).length===0?(0,T.jsx)(`div`,{className:`dashboard-empty`,children:`No recent artefacts.`}):(E??[]).slice(0,6).map(e=>(0,T.jsxs)(`button`,{type:`button`,className:`admin-row dashboard-artefact-row`,onClick:()=>ue(e),children:[(0,T.jsx)(`span`,{className:`dashboard-artefact-icon`,children:e.kind===`agent-template`?(0,T.jsx)(h,{size:16}):(0,T.jsx)(S,{size:16})}),(0,T.jsx)(`div`,{className:`dashboard-artefact-body`,children:(0,T.jsx)(`span`,{className:`dashboard-artefact-name`,children:e.name})}),(0,T.jsx)(`span`,{className:`dashboard-artefact-time`,children:I(Date.parse(e.updatedAt),F)})]},e.id)):(0,T.jsx)(L,{count:3})}),(0,T.jsx)(`a`,{className:`admin-section-label dashboard-col-label dashboard-skills-label`,href:`/skills`,children:`Skills`}),(0,T.jsx)(`div`,{className:`admin-card dashboard-skills`,children:M.has(`skills`)?(O??[]).length===0?(0,T.jsx)(`div`,{className:`dashboard-empty`,children:`No skills yet.`}):(O??[]).map(e=>(0,T.jsxs)(`a`,{className:`admin-row dashboard-skill-row`,href:`/skills`,children:[(0,T.jsx)(`span`,{className:`dashboard-skill-icon`,children:(0,T.jsx)(te,{size:16})}),(0,T.jsxs)(`div`,{className:`dashboard-skill-body`,children:[(0,T.jsx)(`span`,{className:`dashboard-skill-name`,children:e.skill}),(0,T.jsx)(`span`,{className:`dashboard-skill-plugin`,children:e.plugin})]})]},`${e.plugin}/${e.skill}`)):(0,T.jsx)(L,{count:2})})]}),(0,T.jsxs)(`div`,{className:`dashboard-col`,children:[(0,T.jsx)(`span`,{className:`admin-section-label dashboard-col-label`,children:`Channels`}),M.has(`conversations`)?Q.length===0?(0,T.jsx)(`div`,{className:`admin-card dashboard-empty`,children:`No channel activity.`}):Q.map(e=>{let t=m===e.channel;return(0,T.jsxs)(`div`,{className:`admin-card dashboard-channel-card`,children:[(0,T.jsxs)(`button`,{type:`button`,className:`dashboard-channel-head`,"aria-expanded":t,onClick:()=>g(t=>t===e.channel?null:e.channel),children:[(0,T.jsx)(`span`,{className:`dashboard-channel-glyph`,children:e.channel===`whatsapp`||e.channel===`telegram`?(0,T.jsx)(s,{channel:e.channel,size:16}):(0,T.jsx)(n,{size:16})}),(0,T.jsx)(`span`,{className:`dashboard-channel-name`,children:ie[e.channel]??e.channel}),(0,T.jsx)(`span`,{className:`dashboard-channel-badge`,children:e.count}),(0,T.jsx)(`span`,{className:`dashboard-channel-chevron`,children:t?(0,T.jsx)(ne,{size:14}):(0,T.jsx)(x,{size:14})})]}),t?(0,T.jsx)(`div`,{className:`dashboard-channel-convs`,children:e.rows.map(e=>(0,T.jsxs)(`button`,{type:`button`,className:`admin-row dashboard-channel-conv-row`,onClick:()=>r(e),title:e.title,children:[(0,T.jsx)(`span`,{className:`dashboard-channel-conv-name`,children:o(e)}),(0,T.jsx)(`span`,{className:`dashboard-channel-conv-tag`,children:e.isHouse?`House`:`Client`}),e.lastMessageAt&&(0,T.jsx)(`span`,{className:`dashboard-channel-conv-time`,children:I(Date.parse(e.lastMessageAt),F)})]},u(e)))}):e.preview&&(0,T.jsx)(`span`,{className:`dashboard-channel-preview`,children:e.preview})]},e.channel)}):(0,T.jsx)(L,{variant:`card`,count:2})]})]})]})}function q(){let e=d(),[t,n]=(0,w.useState)(!1);(0,w.useEffect)(()=>{if(!t)return;let e=e=>{e.key===`Escape`&&n(!1)};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[t]);let r=(0,w.useCallback)(async()=>{let t=e.claudeInfo!=null;if(n(!0),t){console.info(`[admin-ui] claude-info-open cached=true ms=0`);return}let r=Date.now();if(!e.cacheKey){console.error(`[admin-ui] claude-info-fetch-skipped reason=no-session-key`);return}try{let t=await fetch(`/api/admin/claude-info?session_key=${encodeURIComponent(e.cacheKey)}`);if(t.ok){let n=await t.json();e.setClaudeInfo(n)}console.info(`[admin-ui] claude-info-open cached=false ms=${Date.now()-r}`)}catch(e){console.error(`[admin-ui] claude-info-fetch-failed ms=${Date.now()-r} error="${e instanceof Error?e.message:String(e)}"`)}},[e]),i=(0,w.useCallback)(({code:t,path:n})=>{console.warn(`[admin-auth] outcome=chat-tab-logout-on-401 code=${t} path=${n}`),e.handleLogout()},[e]);return e.appState===`chat`?(0,T.jsx)(l,{cacheKey:e.cacheKey,onSessionExpired:i,surface:`chat`,children:(0,T.jsx)(g,{cacheKey:e.cacheKey,businessName:e.businessName,sessionId:e.sessionId,onLogout:e.handleLogout,onDisconnect:e.handleDisconnect,disconnecting:e.disconnecting,claudeConnected:e.claudeConnected,onConnectClaude:()=>e.setAppState(`connect-claude`),userName:e.userName,userAvatar:e.userAvatar,role:e.role,footer:(0,T.jsxs)(T.Fragment,{children:[(0,T.jsx)(E,{onOpen:r}),(0,T.jsx)(m,{show:t,onClose:()=>n(!1),claudeInfo:e.claudeInfo,messages:[],sessionElapsed:0,sessionId:e.sessionId,cacheKey:e.cacheKey})]}),children:t=>(0,T.jsx)(T.Fragment,{children:t.dataOpen&&e.cacheKey?(0,T.jsx)(y,{cacheKey:e.cacheKey}):t.selectedWhatsapp&&e.cacheKey?t.selectedWhatsapp.source===`store`&&t.selectedWhatsapp.remoteJid&&t.selectedWhatsapp.accountId?(0,T.jsx)(v,{accountId:t.selectedWhatsapp.accountId,remoteJid:t.selectedWhatsapp.remoteJid,contactName:o(t.selectedWhatsapp),sessionKey:e.cacheKey,onBack:t.onClearWhatsapp},u(t.selectedWhatsapp)):(0,T.jsx)(D,{sessionId:t.selectedWhatsapp.sessionId,projectDir:t.selectedWhatsapp.projectDir,sessionKey:e.cacheKey,contactName:o(t.selectedWhatsapp),onBack:t.onClearWhatsapp},u(t.selectedWhatsapp)):(0,T.jsx)(K,{cacheKey:e.cacheKey,userName:e.userName,onSelectWhatsappConversation:t.onSelectWhatsappConversation})})})}):(0,T.jsx)(f,{auth:e})}(0,C.createRoot)(document.getElementById(`root`)).render((0,T.jsx)(q,{}));
|
|
Binary file
|
|
Binary file
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Real Agent</title>
|
|
7
7
|
<link rel="icon" href="/favicon.ico">
|
|
8
|
-
<script type="module" crossorigin src="/assets/admin-
|
|
8
|
+
<script type="module" crossorigin src="/assets/admin-6QN-k3zQ.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/chunk-CltuBf4Z.js">
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/useSubAccountSwitcher-CJKjEPNN.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/graph-labels-BZgd0L6n.js">
|