diff --git a/Dockerfile b/Dockerfile
index 8684c28..840a1d2 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -34,6 +34,11 @@ COPY assets/custom.css /app/code/librechat/client/dist/assets/custom.css
# rebuild trigger
COPY assets/custom2.js /app/code/librechat/client/dist/assets/custom2.js
+# Co-op UI strings: rename the "MCP Servers" button to "Tools" and replace
+# the LibreChat footer tagline with the co-op footer. Non-fatal at build.
+COPY patch_ui_strings.py /app/code/patch_ui_strings.py
+RUN python3 /app/code/patch_ui_strings.py
+
# Inject branding into built HTML (dist) before image is packaged
# rebuild marker 20261019-$(date +%s)
RUN sed -i 's|
LibreChat|Inference Co‑op|' /app/code/librechat/client/dist/index.html && \
diff --git a/assets/custom.css b/assets/custom.css
index 82b6c7d..6547adf 100644
--- a/assets/custom.css
+++ b/assets/custom.css
@@ -10,10 +10,10 @@
============================================================ */
html.light {
- --surface-primary: 250 248 245; /* --cream #faf8f5 */
- --surface-secondary: 238 244 234; /* --wash #eef4ea */
- --surface-tertiary: 232 226 216; /* --border #e8e2d8 */
- --surface-hover: 232 245 232; /* #e8f5e8 (homepage wash hover) */
+ --surface-primary: 234 243 234; /* #eaf3ea — dashboard app green background */
+ --surface-secondary: 223 237 222; /* deeper step of the dashboard green */
+ --surface-tertiary: 213 228 211;
+ --surface-hover: 223 237 222;
--surface-submit: 91 140 90; /* --green-deep #5b8c5a */
--surface-submit-hover: 107 168 107; /* --green-mid #6ba86b */
--accent-primary: 91 140 90; /* --green-deep */
@@ -22,7 +22,7 @@ html.light {
--link: 91 140 90; /* --green-deep */
--text-primary: 45 51 39; /* --ink #2d3327 */
--text-secondary: 107 122 98; /* --muted #6b7a62 */
- --border-light: 232 226 216; /* --border #e8e2d8 */
+ --border-light: 205 224 203; /* green-tinted border */
}
html.dark {
@@ -43,7 +43,7 @@ html.dark {
/* ---- Loading screen: brand the pre-React flash instead of white/black ---- */
#loading-container {
- background-color: rgb(250, 248, 245); /* cream */
+ background-color: rgb(234, 243, 234); /* dashboard green */
}
@media (prefers-color-scheme: dark) {
#loading-container {
@@ -63,16 +63,9 @@ a[href*="signup"] {
display: none !important;
}
-/* ---- Footer: simple centered link to the co-op homepage ---- */
+/* ---- Footer: the LibreChat tagline is replaced at build time
+ (patch_ui_strings.py) with the co-op footer text; CSS just centers it ---- */
footer {
text-align: center;
padding: 1rem;
}
-footer::before {
- content: "© Inference Co-op – ";
-}
-footer::after {
- content: "inference.coop";
- color: rgb(var(--accent-primary));
- cursor: pointer;
-}
diff --git a/patch_ui_strings.py b/patch_ui_strings.py
new file mode 100644
index 0000000..c9a31c7
--- /dev/null
+++ b/patch_ui_strings.py
@@ -0,0 +1,59 @@
+"""Replace LibreChat's default UI strings with Inference Co-op wording.
+
+Run at BUILD time (the runtime rootfs is read-only under Cloudron). Non-fatal:
+walks the built client assets, replaces exact strings, and deletes the stale
+precompressed (.gz/.br) siblings of any edited file so the server never serves
+an old compressed copy. Idempotent: skips files already patched.
+"""
+import sys
+import glob
+import os
+
+REPLACEMENTS = [
+ # "MCP Servers" button -> "Tools" (more generic / accessible)
+ ("MCP Servers", "Tools"),
+ # LibreChat footer tagline -> co-op footer
+ ("Every AI for Everyone.", "Inference Cooperative · Our AI, governed together · inference.coop"),
+]
+
+assets_dir = "/app/code/librechat/client/dist/assets"
+files = glob.glob(os.path.join(assets_dir, "*.js")) + glob.glob(
+ os.path.join(assets_dir, "*.json")
+)
+
+changed_any = False
+for path in files:
+ try:
+ with open(path, encoding="utf-8") as f:
+ src = f.read()
+ except (UnicodeDecodeError, OSError):
+ continue
+
+ new = src
+ applied = []
+ for old, replacement in REPLACEMENTS:
+ if old in new:
+ new = new.replace(old, replacement)
+ applied.append(old)
+
+ if not applied:
+ continue
+
+ with open(path, "w", encoding="utf-8") as f:
+ f.write(new)
+
+ # Stale precompressed copies of an edited file must go, or the server
+ # may keep serving the old compressed content.
+ for ext in (".gz", ".br"):
+ stale = path + ext
+ if os.path.exists(stale):
+ os.remove(stale)
+
+ print(f"INFO: patched {os.path.basename(path)}: {applied}")
+ changed_any = True
+
+if not changed_any:
+ print("WARN: no UI strings patched (files may already be patched) — non-fatal",
+ file=sys.stderr)
+
+sys.exit(0)