Compare commits

...

21 Commits

Author SHA1 Message Date
oliver 3649cedabe Update app.js 2026-06-22 18:46:40 -03:00
oliver 15dda4632f Update app.js 2026-06-22 18:38:27 -03:00
oliver 9ddfc00aa0 Update styles.css 2026-06-22 18:30:39 -03:00
oliver 901f43e497 Update app.js 2026-06-22 18:29:48 -03:00
oliver 09663e3dc8 Update app.js 2026-06-22 18:25:22 -03:00
oliver b6f1d7e15d l 2026-06-22 18:22:35 -03:00
oliver 07fc9f9050 k 2026-06-22 18:17:22 -03:00
oliver 365c69871e 🤝 2026-06-22 17:05:28 -03:00
oliver ad8e17e163 git integration 2026-06-22 17:02:31 -03:00
oliver 361ee5b49d update 2026-06-18 14:22:48 -03:00
oliver b692134c58 n8n skill 2026-06-18 06:05:31 -03:00
oliver f34ab02477 no follow no index 2026-06-15 09:12:06 -03:00
oliver d61c746818 Update app.js 2026-06-13 06:06:50 -03:00
oliver 5309db7e12 Update app.js 2026-06-13 06:06:03 -03:00
oliver 19b2edccf1 Update app.js 2026-06-12 15:02:13 -03:00
oliver a270f60311 general login 2026-06-12 11:06:15 -03:00
oliver f9c8f8fd8c Create favicon.ico 2026-06-11 12:34:36 -03:00
oliver 4042287957 c 2026-06-11 05:26:22 -03:00
oliver ad5d33e8e1 Update index.html 2026-06-11 05:24:49 -03:00
oliver 6dcfd6f17a Update app.js 2026-06-10 20:43:50 -03:00
oliver 592505ac21 Update app.js 2026-06-10 20:41:09 -03:00
12 changed files with 611 additions and 210 deletions
+160 -28
View File
@@ -24,6 +24,7 @@ const CREDITS_URL = `${API_BASE}/payment/credits`; // hidden form POST → 302 S
const INVOICES_URL = `${API_BASE}/invoices`; // GET ?email=
const INVOICE_EMAIL_URL = `${API_BASE}/user`; // POST {email, invoice_email}
const PORTAL_URL = `${API_BASE}/account/portal`; // GET ?email= → { url }
const AFFILIATE_LINKS_URL = `${API_BASE}/affiliate/link`; // GET → [{areas:[{area,text,link}]}]
const CONTRACT_EXTEND_URL = "https://derez.ai"; // ← replace with Stripe payment link
const CONTRACT_CANCEL_URL = "https://derez.ai"; // ← replace with Stripe cancellation link
@@ -500,7 +501,7 @@ function populateDemoShell() {
</div>
<div class="server-info-row">
<span class="server-info-label">SSH Access</span>
<span class="server-info-val"><a class="server-info-link" href="#" onclick="event.preventDefault();openBillingPortal()">Upgrade</a></span>
<span class="server-info-val">Upgrade</span>
</div>
<div class="server-info-row">
<span class="server-info-label">Created</span>
@@ -625,8 +626,7 @@ function selectAgent(uuid) {
hide("wizard-card");
loadKeys(uuid);
loadBackups(uuid);
loadAgentInfo(uuid);
loadAgentInfo(uuid).then(() => loadBackups(uuid));
loadContract(uuid);
}
@@ -745,16 +745,9 @@ function renderAgentInfo(info) {
const port = stripQuotes(info.ssh_port);
const sshCmd = `ssh root@${activeUUID}.derez.ai -p ${port}`;
// Check SSH flag from agents list
const agent = agents.find((a) => (a.UUID || a.uuid) === activeUUID);
const hasSSH =
agent &&
!(
agent.SSH === null ||
agent.SSH === undefined ||
agent.SSH === "null" ||
agent.SSH === false
);
// Check SSH flag from agent info response
const sshRaw = stripQuotes(info.ssh);
const hasSSH = sshRaw !== "—" && sshRaw !== "0" && sshRaw !== "false";
const rows = [
`<div class="server-info-row">
@@ -776,7 +769,7 @@ function renderAgentInfo(info) {
</div>`
: `<div class="server-info-row">
<span class="server-info-label">SSH Access</span>
<span class="server-info-val"><a class="server-info-link" href="#" onclick="event.preventDefault();openBillingPortal()">Upgrade</a></span>
<span class="server-info-val">Upgrade</span>
</div>`,
`<div class="server-info-row">
<span class="server-info-label">Created</span>
@@ -1014,7 +1007,7 @@ async function savePassword() {
const ok = await confirmDialog(
"Your Hermes & Root Password",
"This will immediately update the root password on your server. Make sure to save it somewhere safe. Continue?",
"This will immediately update the root password on your server (the dashboard user is <b>root</b>). Make sure to save it somewhere safe.\n\nNote: You will need to restart the server for the dashboard password to take effect. Continue?",
);
if (!ok) {
@@ -1124,6 +1117,17 @@ function closeUserPanel() {
if (codeEl) codeEl.textContent = "—";
const copyBtn = document.getElementById("acct-affiliate-copy-btn");
if (copyBtn) copyBtn.style.display = "none";
// reset tab to settings
const settingsTab = document.getElementById("acct-tab-settings");
const affiliateTab = document.getElementById("acct-tab-affiliate");
const settingsPane = document.getElementById("acct-pane-settings");
const affiliatePane = document.getElementById("acct-pane-affiliate");
if (settingsTab && affiliateTab && settingsPane && affiliatePane) {
affiliateTab.classList.remove("active");
settingsTab.classList.add("active");
affiliatePane.style.display = "none";
settingsPane.style.display = "flex";
}
}
function toggleAccountPwSave(value) {
@@ -1356,6 +1360,125 @@ function exportInvoicesCSV() {
URL.revokeObjectURL(url);
}
/* ─── Account Panel Tab Switching ─────────────────────────────── */
function switchAccountTab(tab) {
const settingsTab = document.getElementById("acct-tab-settings");
const affiliateTab = document.getElementById("acct-tab-affiliate");
const settingsPane = document.getElementById("acct-pane-settings");
const affiliatePane = document.getElementById("acct-pane-affiliate");
if (!settingsTab || !affiliateTab || !settingsPane || !affiliatePane) return;
if (tab === "affiliate") {
settingsTab.classList.remove("active");
affiliateTab.classList.add("active");
settingsPane.style.display = "none";
affiliatePane.style.display = "flex";
loadAffiliateLinks();
} else {
affiliateTab.classList.remove("active");
settingsTab.classList.add("active");
affiliatePane.style.display = "none";
settingsPane.style.display = "flex";
}
}
async function loadAffiliateLinks() {
const body = document.getElementById("affiliate-links-body");
if (!body) return;
body.innerHTML = '<div class="loading-row"><div class="spinner"></div></div>';
try {
const res = await apiFetch(AFFILIATE_LINKS_URL);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
dbg("\u2190 Affiliate links", data);
renderAffiliateLinks(data);
} catch (err) {
dbg("\u2190 Affiliate links error", err.message);
body.innerHTML =
'<p class="text-muted" style="font-size:13px;padding:4px 0;">Could not load affiliate links.</p>';
}
}
function renderAffiliateLinks(data) {
const body = document.getElementById("affiliate-links-body");
if (!body) return;
let areas = [];
if (data && Array.isArray(data.areas)) {
areas = data.areas;
} else if (data && Array.isArray(data.data)) {
const first = data.data[0];
if (first && Array.isArray(first.areas)) areas = first.areas;
} else if (Array.isArray(data)) {
const first = data[0];
if (first && Array.isArray(first.areas)) areas = first.areas;
}
body.innerHTML = `
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;">
<span class="text-muted" style="font-size:11px;text-transform:uppercase;letter-spacing:0.06em;">${areas.length} areas</span>
<button type="button" id="save-affiliate-btn" class="btn btn-primary btn-sm" onclick="saveAffiliateLinks()" style="display:none;">
Save Changes
</button>
</div>
<div class="affiliate-areas-list">
${areas
.map(
(a) => `
<div class="affiliate-area-card">
<div class="affiliate-area-name">${escHtml(a.area)}</div>
<div class="form-row" style="margin:0;">
<div class="form-group" style="margin:0;">
<label>Text</label>
<input type="text" class="affiliate-input" data-area="${escAttr(a.area)}" data-field="text" value="${escAttr(a.text || "")}" oninput="affiliateAreaChanged()" placeholder="Affiliate text…" />
</div>
</div>
<div class="form-row" style="margin:0;">
<div class="form-group" style="margin:0;">
<label>Link</label>
<input type="text" class="affiliate-input" data-area="${escAttr(a.area)}" data-field="link" value="${escAttr(a.link || "")}" oninput="affiliateAreaChanged()" placeholder="https://…" />
</div>
</div>
</div>`,
)
.join("")}
</div>`;
}
function affiliateAreaChanged() {
const btn = document.getElementById("save-affiliate-btn");
if (btn) btn.style.display = "inline-flex";
}
async function saveAffiliateLinks() {
const btn = document.getElementById("save-affiliate-btn");
if (!btn) return;
btnLoad(btn, "Saving…");
try {
const inputs = document.querySelectorAll(".affiliate-input");
const map = {};
for (const inp of inputs) {
const area = inp.dataset.area;
const field = inp.dataset.field;
if (!map[area]) map[area] = { area, text: "", link: "" };
map[area][field] = inp.value;
}
const areas = Object.values(map);
const res = await apiFetch(AFFILIATE_LINKS_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ areas }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
toast("Affiliate links saved.", "success");
btn.style.display = "none";
loadAffiliateLinks();
} catch (err) {
toast(err.message, "error");
} finally {
btnReset(btn);
}
}
/* ═══════════════════════════════════════════════════════════════
RESTART
═══════════════════════════════════════════════════════════════ */
@@ -1408,18 +1531,27 @@ async function loadBackups(uuid) {
const body = document.getElementById("backups-body");
body.innerHTML = '<div class="loading-row"><div class="spinner"></div></div>';
// Check BACKUP flag from agents list
const agent = agents.find((a) => (a.UUID || a.uuid) === uuid);
// Check BACKUP flag from agent info response
const backupRaw = activeAgentInfo ? stripQuotes(activeAgentInfo.backup) : "—";
const hasBackup =
agent &&
!(
agent.BACKUP === null ||
agent.BACKUP === undefined ||
agent.BACKUP === "null" ||
agent.BACKUP === false
);
backupRaw !== "—" && backupRaw !== "0" && backupRaw !== "false";
const btn = document.getElementById("make-backup-btn");
if (btn) btn.style.display = hasBackup ? "" : "none";
if (btn) {
if (hasBackup) {
btn.style.display = "";
btn.innerHTML = "+";
btn.onclick = makeBackup;
btn.title = "Make Backup";
} else {
btn.style.display = "";
btn.innerHTML = "+";
btn.onclick = openBillingPortal;
btn.title = "Upgrade to enable backups";
btn.style.fontSize = "";
btn.style.lineHeight = "";
btn.style.padding = "";
}
}
try {
const _backupsUrl = `${BACKUP_URL}?uuid=${encodeURIComponent(uuid)}`;
@@ -2008,7 +2140,7 @@ let _confirmResolve = null;
function confirmDialog(title, message, okLabel = "Confirm") {
document.getElementById("confirm-title").textContent = title;
document.getElementById("confirm-message").textContent = message;
document.getElementById("confirm-message").innerHTML = message;
document.getElementById("confirm-ok-btn").textContent = okLabel;
document.getElementById("confirm-backdrop").classList.add("open");
return new Promise((resolve) => {
@@ -2045,7 +2177,7 @@ function toast(msg, type = "info", duration = 4000) {
/* ─── Cookie helpers ───────────────────────────────────────── */
function setCookie(name, value, days) {
const expires = new Date(Date.now() + days * 864e5).toUTCString();
document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/; SameSite=Strict`;
document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/; domain=.derez.ai; SameSite=Lax`;
}
function getCookie(name) {
return document.cookie.split("; ").reduce((acc, c) => {
@@ -2054,7 +2186,7 @@ function getCookie(name) {
}, null);
}
function deleteCookie(name) {
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; SameSite=Strict`;
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.derez.ai; SameSite=Lax`;
}
/* ─── Authenticated fetch ───────────────────────────────────── */
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

+232 -158
View File
@@ -3,6 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="robots" content="noindex, nofollow" />
<title>derez.ai — My Portal</title>
<link rel="stylesheet" href="styles.css" />
</head>
@@ -811,7 +812,24 @@
>
<div class="modal account-modal">
<div class="modal-header">
<span class="modal-title">Account Settings</span>
<div class="auth-switch">
<button
type="button"
id="acct-tab-settings"
class="auth-switch-btn active"
onclick="switchAccountTab('settings')"
>
Account Settings
</button>
<button
type="button"
id="acct-tab-affiliate"
class="auth-switch-btn"
onclick="switchAccountTab('affiliate')"
>
Affiliate Settings
</button>
</div>
<button
type="button"
class="modal-close-btn"
@@ -821,165 +839,221 @@
</button>
</div>
<div class="account-modal-body">
<!-- ── Left: settings ──────────────────────── -->
<div class="account-col account-col--settings">
<div class="acct-section">
<div class="acct-field-label">Email</div>
<div class="acct-field-val" id="acct-email-display">
<!-- ── Pane: Settings ──────────────────────── -->
<div
id="acct-pane-settings"
class="account-pane"
style="display: flex"
>
<div class="account-col account-col--settings">
<div class="acct-section">
<div class="acct-field-label">Email</div>
<div
class="acct-field-val"
id="acct-email-display"
>
</div>
</div>
<div class="acct-divider"></div>
<div class="acct-section">
<div class="form-group">
<label>Change Password</label>
<div class="pw-input-wrap">
<input
type="password"
id="acct-pw-input"
placeholder="New password"
autocomplete="new-password"
oninput="
toggleAccountPwSave(this.value)
"
onkeydown="
if (event.key === 'Enter')
saveAccountPassword();
"
/>
<button
type="button"
id="acct-pw-save-btn"
class="pw-save-btn"
onclick="saveAccountPassword()"
title="Save password"
style="display: none"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline
points="20 6 9 17 4 12"
/>
</svg>
</button>
</div>
<p
class="ssh-hint"
style="margin-bottom: 0"
>
Min. 8 characters.
</p>
</div>
</div>
<div class="acct-divider"></div>
<div class="acct-section">
<div class="form-group">
<label>Invoice Email</label>
<div class="pw-input-wrap">
<input
type="email"
id="invoice-email-input"
placeholder="invoices@example.com"
autocomplete="email"
oninput="
toggleInvoiceEmailSave(
this.value,
)
"
onkeydown="
if (event.key === 'Enter')
saveInvoiceEmail();
"
/>
<button
type="button"
id="invoice-email-save-btn"
class="pw-save-btn"
onclick="saveInvoiceEmail()"
title="Save invoice email"
style="display: none"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline
points="20 6 9 17 4 12"
/>
</svg>
</button>
</div>
</div>
</div>
<div class="acct-divider"></div>
<div class="acct-section">
<div class="acct-field-label">
Manage Subscriptions
</div>
<div
class="acct-affiliate-row"
style="justify-content: flex-end"
>
<button
type="button"
id="portal-btn"
class="btn btn-ghost btn-sm"
onclick="openBillingPortal()"
>
Portal &rarr;
</button>
</div>
</div>
<div class="acct-divider"></div>
</div>
<div class="acct-divider"></div>
<div class="acct-section">
<div class="form-group">
<label>Change Password</label>
<div class="pw-input-wrap">
<!-- ── Vertical divider ──────────────────────── -->
<div class="account-col-divider"></div>
<!-- ── Right: invoices ──────────────────────── -->
<div class="account-col account-col--invoices">
<div class="acct-invoices-toolbar">
<div class="acct-invoices-title">Invoices</div>
<div class="acct-invoices-actions">
<input
type="password"
id="acct-pw-input"
placeholder="New password"
autocomplete="new-password"
oninput="
toggleAccountPwSave(this.value)
"
onkeydown="
if (event.key === 'Enter')
saveAccountPassword();
"
type="text"
id="invoice-filter"
class="invoice-filter-input"
placeholder="Filter invoices…"
oninput="applyInvoiceFilter()"
/>
<button
type="button"
id="acct-pw-save-btn"
class="pw-save-btn"
onclick="saveAccountPassword()"
title="Save password"
style="display: none"
class="btn btn-ghost btn-sm"
onclick="exportInvoicesCSV()"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="20 6 9 17 4 12" />
</svg>
Export CSV
</button>
</div>
<p class="ssh-hint" style="margin-bottom: 0">
Min. 8 characters.
</div>
<div id="invoices-body"></div>
</div>
</div>
<!-- ── Pane: Affiliate ─────────────────────── -->
<div
id="acct-pane-affiliate"
class="account-pane"
style="display: none"
>
<div
class="account-col"
style="
flex: 1;
padding: 18px 20px;
overflow-y: auto;
"
>
<!-- Affiliate Code -->
<div class="acct-section">
<div class="acct-field-label">
Affiliate Code
</div>
<div class="acct-affiliate-row">
<span
class="acct-affiliate-code"
id="acct-affiliate-code"
></span
>
<button
type="button"
id="acct-affiliate-copy-btn"
class="btn btn-ghost btn-sm"
onclick="copyAffiliateCode()"
style="display: none"
>
Copy Affiliate Link
</button>
</div>
</div>
<div class="acct-divider"></div>
<!-- Affiliate Links Table -->
<div
class="acct-invoices-toolbar"
style="margin-top: 2px"
>
<div class="acct-invoices-title">
Affiliate Links
</div>
</div>
<div id="affiliate-links-body">
<p
class="text-muted"
style="font-size: 13px; padding: 4px 0"
>
Switch to this tab to see available
affiliate links.
</p>
</div>
</div>
<div class="acct-divider"></div>
<div class="acct-section">
<div class="form-group">
<label>Invoice Email</label>
<div class="pw-input-wrap">
<input
type="email"
id="invoice-email-input"
placeholder="invoices@example.com"
autocomplete="email"
oninput="
toggleInvoiceEmailSave(this.value)
"
onkeydown="
if (event.key === 'Enter')
saveInvoiceEmail();
"
/>
<button
type="button"
id="invoice-email-save-btn"
class="pw-save-btn"
onclick="saveInvoiceEmail()"
title="Save invoice email"
style="display: none"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="20 6 9 17 4 12" />
</svg>
</button>
</div>
</div>
</div>
<div class="acct-divider"></div>
<div class="acct-section">
<div class="acct-field-label">
Manage Subscriptions
</div>
<div
class="acct-affiliate-row"
style="justify-content: flex-end"
>
<button
type="button"
id="portal-btn"
class="btn btn-ghost btn-sm"
onclick="openBillingPortal()"
>
Portal &rarr;
</button>
</div>
</div>
<div class="acct-divider"></div>
<div class="acct-section">
<div class="acct-field-label">Affiliate Code</div>
<div class="acct-affiliate-row">
<span
class="acct-affiliate-code"
id="acct-affiliate-code"
></span
>
<button
type="button"
id="acct-affiliate-copy-btn"
class="btn btn-ghost btn-sm"
onclick="copyAffiliateCode()"
style="display: none"
>
Copy Affiliate Link
</button>
</div>
</div>
</div>
<!-- ── Vertical divider ──────────────────────── -->
<div class="account-col-divider"></div>
<!-- ── Right: invoices ──────────────────────── -->
<div class="account-col account-col--invoices">
<div class="acct-invoices-toolbar">
<div class="acct-invoices-title">Invoices</div>
<div class="acct-invoices-actions">
<input
type="text"
id="invoice-filter"
class="invoice-filter-input"
placeholder="Filter invoices…"
oninput="applyInvoiceFilter()"
/>
<button
type="button"
class="btn btn-ghost btn-sm"
onclick="exportInvoicesCSV()"
>
Export CSV
</button>
</div>
</div>
<div id="invoices-body"></div>
</div>
</div>
</div>
@@ -999,16 +1073,6 @@
<span id="credits-key-name"></span>
</p>
<div class="credits-options">
<label class="credits-option">
<input
type="radio"
name="credits-amount"
value="5.50"
/>
<span class="credits-option-label"
>&euro;&thinsp;5.50</span
>
</label>
<label class="credits-option">
<input
type="radio"
@@ -1039,6 +1103,16 @@
>&euro;&thinsp;55.00</span
>
</label>
<label class="credits-option">
<input
type="radio"
name="credits-amount"
value="110.00"
/>
<span class="credits-option-label"
>&euro;&thinsp;110.00</span
>
</label>
</div>
<p class="credits-fee-note">
A 10% handling fee is added on top of the OpenRouter
+27
View File
@@ -0,0 +1,27 @@
{
"name": "GitHub Repo",
"description": "Set up and sync with a GitHub repository — clone, commit, push, and manage code for your Hermes agent.",
"fields": [
{
"label": "Repository URL",
"key": "repo_url",
"type": "text",
"placeholder": "https://github.com/your-username/your-repo.git"
},
{
"label": "GitHub Username",
"key": "git_user",
"type": "text",
"placeholder": "your-username"
},
{
"label": "GitHub Password / Personal Access Token",
"key": "git_pass",
"type": "password",
"placeholder": "ghp_xxxxxxxxxxxx"
}
],
"signup_url": "https://semrush.com",
"signup_label": "🤝 Need to drive traffic to your site? Create a Semrush account",
"prompt": "Create a Hermes agent skill named \"github_repo_setup\".\n\n## Purpose\nSet up a working GitHub repository that the Hermes agent can use to read, write, and manage code, content, or configuration files via git. The repo acts as a persistent file store and version-controlled workspace.\n\n## Credentials\n\nREPO_URL={repo_url}\nGIT_USER={git_user}\nGIT_PASS={git_pass}\n\n## Startup behaviour\n\n1. On first run, check if git is installed. If not, install it:\n apt-get update && apt-get install -y git\n Or the equivalent for the system's package manager.\n\n2. Check if the repo directory (~/repo) already exists and is a git repo.\n cd ~/repo && git status\n\n3. If ~/repo does not exist or is not a git repo:\n - Extract the repo name from REPO_URL (the part before .git).\n - If a remote git URL is provided, authenticate using GIT_USER and GIT_PASS in the URL:\n AUTH_URL=$(echo \"$REPO_URL\" | sed \"s|https://|https://$GIT_USER:$GIT_PASS@|\")\n - Clone the repo:\n git clone \"$AUTH_URL\" ~/repo\n - Set the remote origin to the AUTH_URL for future push/pull:\n git remote set-url origin \"$AUTH_URL\"\n\n4. If ~/repo exists but is not a git repo, print an error and halt.\n\n5. If ~/repo is already a valid git repo and has a remote origin:\n - Re-authenticate the remote with the provided credentials:\n AUTH_URL=$(echo \"$REPO_URL\" | sed \"s|https://|https://$GIT_USER:$GIT_PASS@|\")\n git remote set-url origin \"$AUTH_URL\"\n - Pull the latest changes:\n git pull --ff-only\n - Print the status: \"GitHub repo already set up. Latest changes pulled.\"\n\n6. After clone or pull, print the current git log (last 5 commits):\n git --no-pager log --oneline -5\n\n## Daily usage\n\nWhenever the agent needs to read, write, or edit files:\n\n### Read a file\n cat ~/repo/<path>\n\n### Edit / write a file\n Write the new content, then:\n cd ~/repo\n git add <path>\n git commit -m \"<descriptive message>\"\n git push\n\n### Commit message format\n Always use conventional commits:\n - feat: <description>\n - fix: <description>\n - docs: <description>\n - chore: <description>\n - refactor: <description>\n\n### Push discipline\n- Always pull before push:\n git pull --ff-only && git push\n- If pull fails due to divergent branches, fetch and rebase:\n git fetch origin && git rebase origin/$(git branch --show-current) && git push\n- If rebase has conflicts, abort and notify the user:\n git rebase --abort\n echo \"ERROR: Merge conflict — needs manual resolution.\"\n\n### Create a new branch\n git checkout -b <branch-name>\n git push -u origin <branch-name>\n\n### List branches\n git branch -a\n\n### Working directory discipline\n- All work goes inside ~/repo. Never write files outside ~/repo unless explicitly instructed.\n- After every batch of changes, commit and push.\n\n## Solved cases log\n\nAfter every successfully completed task that involved the GitHub repo, append to ~/repo_solutions.md:\n\n ## <short title> (<date>)\n Goal: <one sentence>\n Commands: <the git commands that worked>\n Gotchas: <anything non-obvious>\n\nRead ~/repo_solutions.md at the start of every task — if a matching case exists, reuse it directly."
}
+31 -6
View File
@@ -2,13 +2,38 @@
"name": "IMAP / SMTP Email",
"description": "Read, send, and manage email from any standard mailbox.",
"fields": [
{"label": "Email Address", "key": "email", "type": "text", "placeholder": "you@yourdomain.com"},
{"label": "Your Name", "key": "display_name", "type": "text", "placeholder": "Jane Smith"},
{"label": "IMAP Host", "key": "imap_host", "type": "text", "placeholder": "imap.hostinger.com"},
{"label": "SMTP Host", "key": "smtp_host", "type": "text", "placeholder": "smtp.hostinger.com"},
{"label": "Password", "key": "password", "type": "password", "placeholder": ""}
{
"label": "Email Address",
"key": "email",
"type": "text",
"placeholder": "you@yourdomain.com"
},
{
"label": "Your Name",
"key": "display_name",
"type": "text",
"placeholder": "Jane Smith"
},
{
"label": "IMAP Host",
"key": "imap_host",
"type": "text",
"placeholder": "imap.hostinger.com"
},
{
"label": "SMTP Host",
"key": "smtp_host",
"type": "text",
"placeholder": "smtp.hostinger.com"
},
{
"label": "Password",
"key": "password",
"type": "password",
"placeholder": ""
}
],
"signup_url": "https://hostinger.com",
"signup_label": "Need a domain or email? Sign up at Hostinger",
"signup_label": "🤝 Need a domain or email? Sign up at Hostinger",
"prompt": "Create a skill for IMAP / SMTP Email.\n\nUse the himalaya CLI to read, send, and manage emails.\n\n## Install himalaya (if not already installed)\n\ncurl -sSL https://raw.githubusercontent.com/pimalaya/himalaya/master/install.sh | PREFIX=~/.local sh\nexport PATH=\"$HOME/.local/bin:$PATH\" # add to ~/.bashrc if not present\nhimalaya --version\n\n## Write the config file\n\nCreate ~/.config/himalaya/config.toml with exactly this content:\n\n[accounts.default]\ndefault = true\nemail = \"{email}\"\ndisplay-name = \"{display_name}\"\n\nfolder.aliases.inbox = \"INBOX\"\nfolder.aliases.sent = \"Sent\"\nfolder.aliases.drafts = \"Drafts\"\nfolder.aliases.trash = \"Trash\"\n\nbackend.type = \"imap\"\nbackend.host = \"{imap_host}\"\nbackend.port = 993\nbackend.encryption.type = \"tls\"\nbackend.login = \"{email}\"\nbackend.auth.type = \"password\"\nbackend.auth.raw = \"{password}\"\n\nmessage.send.backend.type = \"smtp\"\nmessage.send.backend.host = \"{smtp_host}\"\nmessage.send.backend.port = 587\nmessage.send.backend.encryption.type = \"start-tls\"\nmessage.send.backend.login = \"{email}\"\nmessage.send.backend.auth.type = \"password\"\nmessage.send.backend.auth.raw = \"{password}\"\n\n## Key commands\n\nhimalaya folder list\nhimalaya envelope list\nhimalaya envelope list -f Sent\nhimalaya message read <id>\nhimalaya message reply <id>\nhimalaya message forward <id>\nhimalaya message delete <id>\nhimalaya --output json envelope list\n\n## Sending email\n\nprintf 'From: {display_name} <{email}>\\nTo: recipient@example.com\\nSubject: Hello\\n\\nBody text here.' | himalaya message send\n\n## Solved cases log\n\nEvery time you successfully solve an email task, append a short entry to ~/email_solutions.md:\n\n ## <short title> (<date>)\n Goal: <one sentence>\n Key commands: <the himalaya commands or shell pipeline that worked>\n Gotchas: <anything non-obvious>\n\nRead ~/email_solutions.md at the start of each task — if a matching solved case exists, reuse it directly."
}
+3 -1
View File
@@ -2,5 +2,7 @@
"odoo-community.json",
"imap-smtp-email.json",
"n8n-crm.json",
"nextcloud-sync.json"
"nextcloud-sync.json",
"n8n-skills.json",
"github-repo.json"
]
+25 -5
View File
@@ -2,12 +2,32 @@
"name": "N8N CRM",
"description": "Manage contacts and deal stages via your n8n webhook CRM.",
"fields": [
{"label": "Webhook Base URL", "key": "webhook_url", "type": "text", "placeholder": "https://your-n8n.example.com/webhook/crm"},
{"label": "Auth Header Name", "key": "auth_header_name", "type": "text", "placeholder": "Authorization"},
{"label": "Auth Header Value", "key": "auth_header_value", "type": "password", "placeholder": ""},
{"label": "Example Contact JSON (paste one record from your CRM)", "key": "example_json", "type": "textarea", "placeholder": "{\n \"email\": \"alice@example.com\",\n \"name\": \"Alice Smith\",\n \"company\": \"Acme Corp\",\n \"stage\": \"Cold\",\n \"followup\": false,\n \"notes\": \"\"\n}"}
{
"label": "Webhook Base URL",
"key": "webhook_url",
"type": "text",
"placeholder": "https://your-n8n.example.com/webhook/crm"
},
{
"label": "Auth Header Name",
"key": "auth_header_name",
"type": "text",
"placeholder": "Authorization"
},
{
"label": "Auth Header Value",
"key": "auth_header_value",
"type": "password",
"placeholder": ""
},
{
"label": "Example Contact JSON (paste one record from your CRM)",
"key": "example_json",
"type": "textarea",
"placeholder": "{\n \"email\": \"alice@example.com\",\n \"name\": \"Alice Smith\",\n \"company\": \"Acme Corp\",\n \"stage\": \"Cold\",\n \"followup\": false,\n \"notes\": \"\"\n}"
}
],
"signup_url": "https://n8n.io",
"signup_label": "Need a webhook backend? Try n8n — self-host or cloud",
"signup_label": "🤝 Need a webhook backend? Try n8n — self-host or cloud",
"prompt": "Create a skill for an N8N CRM backed by a webhook.\n\n## Configuration\n\nWEBHOOK_URL={webhook_url}\nAUTH_HEADER_NAME={auth_header_name}\nAUTH_HEADER_VALUE={auth_header_value}\n\n## Example record schema (auto-detected from user input)\n\n{example_json}\n\nParse the keys of the JSON above to discover the available columns. Always use exactly those column names when building payloads or displaying data. The column named \"email\" is the unique identifier for every contact.\n\n## Stages\n\n| Stage | Meaning |\n|------------|--------------------------------|\n| Cold | No contact so far |\n| Contacted | First message / email sent |\n| Warm | Response received |\n| Won | Deal closed successfully |\n| Lost | Deal closed unsuccessfully |\n\n## API contract\n\n### List contacts\n\n curl -s -H \"$AUTH_HEADER_NAME: $AUTH_HEADER_VALUE\" \"$WEBHOOK_URL\"\n curl -s -H \"$AUTH_HEADER_NAME: $AUTH_HEADER_VALUE\" \"$WEBHOOK_URL?stage=Warm\"\n curl -s -H \"$AUTH_HEADER_NAME: $AUTH_HEADER_VALUE\" \"$WEBHOOK_URL?email=alice@example.com\"\n curl -s -H \"$AUTH_HEADER_NAME: $AUTH_HEADER_VALUE\" \"$WEBHOOK_URL?followup=true\"\n\n### Create or update a contact (upsert by email)\n\n curl -s -X POST \"$WEBHOOK_URL\" \\\n -H \"$AUTH_HEADER_NAME: $AUTH_HEADER_VALUE\" \\\n -H 'Content-Type: application/json' \\\n -d '{\"email\":\"alice@example.com\",\"name\":\"Alice Smith\",\"stage\":\"Contacted\",\"followup\":false}'\n\n### Delete a contact\n\n curl -s -X DELETE \"$WEBHOOK_URL\" \\\n -H \"$AUTH_HEADER_NAME: $AUTH_HEADER_VALUE\" \\\n -H 'Content-Type: application/json' \\\n -d '{\"email\":\"alice@example.com\"}'\n\n## Solved cases log\n\nAfter every successful CRM task, append an entry to ~/crm_solutions.md:\n\n ## <short title> (<date>)\n Goal: <one sentence>\n Key commands: <the curl | jq pipelines that worked>\n Gotchas: <anything non-obvious>\n\nRead ~/crm_solutions.md at task start — if a matching case exists, reuse it directly."
}
+21
View File
@@ -0,0 +1,21 @@
{
"name": "N8N Skills",
"description": "Load and execute skills from n8n's official skill library via the n8n REST API.",
"fields": [
{
"label": "n8n URL",
"key": "n8n_url",
"type": "text",
"placeholder": "https://your-n8n.example.com \u2014 API endpoint is usually at <domain>/api/v1"
},
{
"label": "n8n API Key",
"key": "n8n_api_key",
"type": "password",
"placeholder": ""
}
],
"signup_url": "https://n8n.io/?via=derez",
"signup_label": "🤝 Need n8n? Sign up via our affiliate link",
"prompt": "Create a skill that integrates with n8n's skill library and REST API.\n\n## Configuration\n\nN8N_URL={n8n_url}\nN8N_API_KEY={n8n_api_key}\n\n## Skill library\n\nYou have access to n8n's official skill library at https://github.com/n8n-io/skills\n\nAt the start of every task, clone or pull the latest version of this repo to discover available skills.\n\n if [ ! -d /tmp/n8n-skills ]; then\n git clone --depth 1 https://github.com/n8n-io/skills /tmp/n8n-skills\n else\n cd /tmp/n8n-skills && git pull --ff-only\n fi\n\nRead the README.md and INDEX.md files in that repo to understand what skills are available. Each skill has a dedicated directory with a README.md describing its purpose and usage. Choose the most relevant skill(s) for the user's request.\n\n## API basics\n\nAll calls go to {n8n_url}/rest/... with the header:\n X-N8N-API-KEY: {n8n_api_key}\n\nThe n8n REST API documentation is available at {n8n_url}/api/v1/openapi.json — fetch it if you need to discover available endpoints.\n\n### Common operations\n\nList workflows:\n curl -s -H 'X-N8N-API-KEY: {n8n_api_key}' '{n8n_url}/rest/workflows'\n\nGet a workflow by ID:\n curl -s -H 'X-N8N-API-KEY: {n8n_api_key}' '{n8n_url}/rest/workflows/<id>'\n\nList executions:\n curl -s -H 'X-N8N-API-KEY: {n8n_api_key}' '{n8n_url}/rest/executions'\n\nList credentials:\n curl -s -H 'X-N8N-API-KEY: {n8n_api_key}' '{n8n_url}/rest/credentials'\n\nList tags:\n curl -s -H 'X-N8N-API-KEY: {n8n_api_key}' '{n8n_url}/rest/tags'\n\n## Workflow skill execution pattern\n\nWhen a skill from the library maps to a workflow to run:\n\n1. Read the skill's README.md to understand the inputs, outputs, and workflow structure.\n2. Check if a matching workflow already exists in n8n (use the list endpoint).\n3. If it does, create an execution:\n\n curl -s -X POST '{n8n_url}/rest/workflows/<id>/run' \\\n -H 'X-N8N-API-KEY: {n8n_api_key}' \\\n -H 'Content-Type: application/json' \\\n -d '{\"data\": <input_data>}'\n\n4. If it doesn't, create a new workflow using the skill's specification:\n\n curl -s -X POST '{n8n_url}/rest/workflows' \\\n -H 'X-N8N-API-KEY: {n8n_api_key}' \\\n -H 'Content-Type: application/json' \\\n -d '{\"name\": \"<skill-name>\", \"nodes\": [...], \"connections\": {...}}'\n\n5. Poll execution status:\n curl -s -H 'X-N8N-API-KEY: {n8n_api_key}' '{n8n_url}/rest/executions/<id>' | jq '.data'\n\n## Solved cases log\n\nAfter every successful n8n task, append to ~/n8n_solutions.md:\n\n ## <short title> (<date>)\n Goal: <one sentence>\n Skill used: <skill path in the library>\n Key commands: <the curl | jq pipelines that worked>\n Gotchas: <anything non-obvious>\n\nRead ~/n8n_solutions.md at task start — if a matching case exists, reuse it directly."
}
+26 -6
View File
@@ -2,12 +2,32 @@
"name": "Nextcloud Sync",
"description": "Connect to a Nextcloud instance and sync folders via WebDAV — list, download, upload, and mirror files automatically.",
"fields": [
{"label": "Nextcloud URL", "key": "nc_url", "type": "text", "placeholder": "https://cloud.example.com"},
{"label": "Username", "key": "nc_user", "type": "text", "placeholder": "admin"},
{"label": "Password / App Token", "key": "nc_pass", "type": "password", "placeholder": ""},
{"label": "Directory to sync (optional — leave blank for all)", "key": "nc_dir", "type": "text", "placeholder": "/Documents"}
{
"label": "Nextcloud URL",
"key": "nc_url",
"type": "text",
"placeholder": "https://cloud.example.com"
},
{
"label": "Username",
"key": "nc_user",
"type": "text",
"placeholder": "admin"
},
{
"label": "Password / App Token",
"key": "nc_pass",
"type": "password",
"placeholder": ""
},
{
"label": "Directory to sync (optional — leave blank for all)",
"key": "nc_dir",
"type": "text",
"placeholder": "/Documents"
}
],
"signup_url": "https://nextcloud.com/de/",
"signup_label": "Don't have Nextcloud yet? Get started at nextcloud.com",
"prompt": "You are a Nextcloud file-sync agent. Your only tools are curl, jq, and standard POSIX shell utilities. Never write Python or Node scripts. Every task must be solved with shell one-liners or short shell scripts.\n\n## Credentials\n\nNC_URL={nc_url}\nNC_USER={nc_user}\nNC_PASS={nc_pass}\nNC_DIR={nc_dir}\n\n## WebDAV base path\n\nAll WebDAV operations use:\n WEBDAV=\"$NC_URL/remote.php/dav/files/$NC_USER\"\n\nIf NC_DIR is set and non-empty, the sync root is:\n SYNC_ROOT=\"$WEBDAV$NC_DIR\"\nOtherwise the sync root is the full remote home:\n SYNC_ROOT=\"$WEBDAV\"\n\n## Common operations\n\n### List folder contents (one level)\n\n curl -s -u \"$NC_USER:$NC_PASS\" \\\n -X PROPFIND \\\n -H 'Depth: 1' \\\n -H 'Content-Type: application/xml' \\\n --data '<?xml version=\"1.0\"?><d:propfind xmlns:d=\"DAV:\"><d:prop><d:displayname/><d:getcontentlength/><d:getlastmodified/><d:resourcetype/></d:prop></d:propfind>' \\\n \"$SYNC_ROOT/\" \\\n | grep -oP '(?<=<d:href>)[^<]+'\n\n### Recursively list all remote paths\n\n curl -s -u \"$NC_USER:$NC_PASS\" \\\n -X PROPFIND \\\n -H 'Depth: infinity' \\\n -H 'Content-Type: application/xml' \\\n --data '<?xml version=\"1.0\"?><d:propfind xmlns:d=\"DAV:\"><d:prop><d:href/><d:resourcetype/></d:prop></d:propfind>' \\\n \"$SYNC_ROOT/\" \\\n | grep -oP '(?<=<d:href>)[^<]+'\n\n### Download a single file\n\n curl -s -u \"$NC_USER:$NC_PASS\" \\\n -o \"/local/path/filename.ext\" \\\n \"$NC_URL/remote.php/dav/files/$NC_USER/path/to/filename.ext\"\n\n### Upload a single file\n\n curl -s -u \"$NC_USER:$NC_PASS\" \\\n -T \"/local/path/filename.ext\" \\\n \"$NC_URL/remote.php/dav/files/$NC_USER/path/to/filename.ext\"\n\n### Create a remote directory\n\n curl -s -u \"$NC_USER:$NC_PASS\" \\\n -X MKCOL \\\n \"$NC_URL/remote.php/dav/files/$NC_USER/path/to/new_folder\"\n\n### Delete a remote file or folder\n\n curl -s -u \"$NC_USER:$NC_PASS\" \\\n -X DELETE \\\n \"$NC_URL/remote.php/dav/files/$NC_USER/path/to/item\"\n\n## Sync strategy\n\nWhen asked to sync a folder:\n1. List all remote paths under SYNC_ROOT recursively.\n2. Mirror the directory tree locally, creating subdirectories as needed.\n3. For each remote file, compare the Last-Modified header against the local mtime (use `date -r` or `stat -c %Y`). Download only if remote is newer or file is absent locally.\n4. For uploads, scan the local tree and PUT any file that is newer locally than the remote copy or does not yet exist remotely.\n5. Never delete files on either side without explicit user confirmation.\n\n## App tokens (recommended over plain password)\n\nGenerate an app token at: $NC_URL/settings/user/security\nUse the token in place of the password — it can be revoked without changing the account password.\n\n## Solved cases log\n\nAfter every successful sync or file operation, append to ~/nextcloud_solutions.md:\n\n ## <short title> (<date>)\n Goal: <one sentence>\n Remote path: <SYNC_ROOT value used>\n Key commands: <the curl pipelines that worked>\n Gotchas: <anything non-obvious>\n\nRead ~/nextcloud_solutions.md at task start — if a matching case exists, reuse it directly."
"signup_label": "🤝 Don't have Nextcloud yet? Get started at nextcloud.com",
"prompt": "\n Create a Hermes agent skill named \"nextcloud_exchange_bisync\".\n \n Purpose:\n Create a skill that synchronizes data bidirectionally between a user's Nextcloud instance and a Nextcloud directory named \"exchange\" using rclone.\n \n Skill requirements:\n \n 1. Startup behavior:\n - When the skill starts, print a clear status message:\n \"Starting Nextcloud exchange synchronization...\"\n - Check whether rclone is installed.\n - If rclone is missing, install it automatically using the official rclone installation method.\n - Verify that rclone is available before continuing.\n \n 2. \"Credentials\"\n Your credentials are below. store them for future use\n Credentials\n NC_URL={nc_url}\nNC_USER={nc_user}\nNC_PASS={nc_pass}\n \n 3. rclone configuration:\n - Automatically create rclone WebDAV remotes for Nextcloud.\n - Configure Nextcloud as the WebDAV provider.\n - Use:\n vendor=nextcloud\n - Store configuration in the standard rclone config location.\n - Reuse existing configuration if already present.\n\n \n 4. Synchronization:\n rclone bisync /root/exchange/ nextcloud:/{nc_dir} --resync\n you need to start the sync manually. do this before every access to the directory and after any changes you want to sync.\n \n 5. Safety:\n - Enable checks before syncing.\n - Detect access problems before starting.\n - Preserve timestamps.\n - Preserve empty directories.\n - Avoid accidental deletion unless confirmed by bisync logic.\n - Produce readable logs.\n \n 6. Completion:\n When finished:\n - Print:\n \"Nextcloud exchange synchronization completed.\"\n - Provide a short summary:\n - files checked\n - changes transferred\n - errors if any\n \n 7. Error handling:\n - Fail cleanly.\n - Explain missing credentials.\n - Explain connection failures.\n - Explain rclone errors.\n - Return non-zero status on failure.\n \n Generate:\n - The complete Hermes skill definition.\n - The executable script.\n - Configuration schema.\n - Required environment variables.\n - Usage example."
}
+25 -5
View File
@@ -2,12 +2,32 @@
"name": "ODOO Community",
"description": "Automate ERP tasks — sales, accounting, and inventory — via REST API.",
"fields": [
{"label": "Site URL", "key": "site_url", "type": "text", "placeholder": "https://ODOO4projects.com"},
{"label": "Username", "key": "username", "type": "text", "placeholder": "admin"},
{"label": "Password / API Key", "key": "api_key", "type": "password", "placeholder": ""},
{"label": "Database Name", "key": "db_name", "type": "text", "placeholder": "mycompany"}
{
"label": "Site URL",
"key": "site_url",
"type": "text",
"placeholder": "https://ODOO4projects.com"
},
{
"label": "Username",
"key": "username",
"type": "text",
"placeholder": "admin"
},
{
"label": "Password / API Key",
"key": "api_key",
"type": "password",
"placeholder": ""
},
{
"label": "Database Name",
"key": "db_name",
"type": "text",
"placeholder": "mycompany"
}
],
"signup_url": "https://ODOO4projects.com",
"signup_label": "Need ODOO Community hosting? Sign up here",
"signup_label": "🤝 Need ODOO Community hosting? Sign up here",
"prompt": "create a skill for ODOO community. You are an Odoo automation agent. Your only tools are curl and jq. Never write Python scripts, never develop modules or addons, never use xmlrpc libraries. Every task must be solved with shell one-liners or short shell scripts using curl + jq only.\n\n## Credentials\n\nSITE_URL={site_url}\nUSER={username}\nAPI_KEY={api_key}\nDB={db_name}\n\n## API call pattern\n\nEvery call is a POST to {site_url}/json/2/<model>/<method>\nRequired headers on every request:\n Authorization: Bearer {api_key}\n X-Openerp-Database: {db_name}\n Content-Type: application/json\n\nBody: a FLAT JSON object of the method arguments (NOT JSON-RPC — no \"params\" wrapper).\nResponse: the raw return value directly (no \"result\" wrapper).\n\n## Always request only the fields you need\n\nNever call search_read without a \"fields\" list. Fetching all fields is wasteful and slow.\nDecide upfront which fields the task actually requires, then pass exactly those.\n\n curl -s -X POST '{site_url}/json/2/sale.order/search_read' \\\n -H 'Authorization: Bearer {api_key}' \\\n -H 'X-Openerp-Database: {db_name}' \\\n -H 'Content-Type: application/json' \\\n -d '{\"domain\": [[\"state\",\"=\",\"sale\"]], \"fields\": [\"name\",\"partner_id\",\"amount_total\"], \"limit\": 20}' \\\n | jq '.[] | {order: .name, customer: .partner_id[1], total: .amount_total}'\n\nUse fields_get only when you genuinely do not know a model's field names:\n curl -s -X POST '{site_url}/json/2/account.move/fields_get' \\\n -H 'Authorization: Bearer {api_key}' \\\n -H 'X-Openerp-Database: {db_name}' \\\n -H 'Content-Type: application/json' \\\n -d '{\"attributes\": [\"string\",\"type\"]}' \\\n | jq 'to_entries[] | {field: .key, label: .value.string, type: .value.type}'\n\n## First-run: build your API digest\n\nIf ~/odoo_digest.md does NOT exist yet, run this once and write the output to the file:\n\n curl -s '{site_url}/json/2/doc' \\\n -H 'Authorization: Bearer {api_key}' \\\n -H 'X-Openerp-Database: {db_name}' \\\n | jq '[to_entries[] | {model: .key, label: .value.string}]' > /tmp/odoo_models.json\n\nFrom /tmp/odoo_models.json, write ~/odoo_digest.md containing:\n - The standard ORM methods available on every model\n - A compact table of installed business models with their technical name and human label\n - The exact one-liner curl template for POST calls\n - Any non-standard or custom routes found in /doc\n\nOn every future task, read ~/odoo_digest.md first.\n\n## Solved cases log\n\nAfter successfully solving any business task, append to ~/odoo_solutions.md:\n\n ## <short title> (<date>)\n Goal: <one sentence>\n Models: <models used>\n Fields queried: <only the fields that were actually needed>\n Key calls: <the curl | jq pipelines that worked>\n Gotchas: <anything non-obvious>\n\nRead ~/odoo_solutions.md at task start — if a matching case exists, reuse it directly."
}
+1
View File
@@ -3,6 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="robots" content="noindex, nofollow" />
<title>derez.ai — Reset Password</title>
<style>
/* ─── Reset ─────────────────────────────────────────── */
+60 -1
View File
@@ -1453,6 +1453,28 @@ body::after {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 18px;
}
.account-modal .modal-header .auth-switch {
flex: 1;
margin: 0;
background: transparent;
border: none;
padding: 0;
display: flex;
gap: 0;
}
.account-modal .modal-header .auth-switch-btn {
white-space: nowrap;
padding: 8px 12px;
}
.account-modal .modal-header .auth-switch-btn.active {
background: var(--bg-inner);
border: 1px solid var(--border);
box-shadow: none;
}
.modal-close-btn {
@@ -1472,7 +1494,12 @@ body::after {
.account-modal-body {
display: flex;
min-height: 320px;
height: 420px;
}
.account-pane {
display: flex;
width: 100%;
}
.account-col {
@@ -2224,6 +2251,38 @@ body.auth-layout #signin-btn {
border-color: var(--danger);
}
/* ─── Affiliate Area Cards ───────────────────────────────────── */
.affiliate-areas-list {
display: flex;
flex-direction: column;
gap: 8px;
max-height: 300px;
overflow-y: auto;
}
.affiliate-area-card {
background: var(--bg-inner);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 12px 14px;
}
.affiliate-area-card .form-group input {
font-size: 12px;
padding: 5px 8px;
}
.affiliate-area-name {
font-size: 11px;
font-weight: 600;
color: var(--accent);
text-transform: uppercase;
letter-spacing: 0.06em;
margin-bottom: 8px;
padding-bottom: 6px;
border-bottom: 1px solid var(--border);
}
/* ─── Wizard (Integrations setup) ─────────────────────────────── */
#wizard-card {
position: absolute;