
    import {
      initNDKPage,
      getPubkey,
      injectHeaderAvatar,
      subscribe,
      publishEvent,
      disconnect,
      getVersion,
      updateVersionDisplay,
      getUserSettings,
      patchUserSettings,
      onUserSettings
    } from './js/init-ndk.mjs';
    import { HamburgerMorphing } from "./hamburger_morphing/hamburger.mjs";
    import { initFooterRelayStatus, updateFooterRelayStatus, initSidenavRelaySection, updateSidenavRelaySection, setRelayActivityState } from './js/relay-ui.mjs';
    import { initBlossomSection, updateBlossomSection } from './js/blossom-ui.mjs';
    import {
      initAiSectionWithLocalConfig,
      getDefaultAiConfig,
      loadAiConfigLocal,
      saveAiConfigLocal,
      getProviderEntries,
      mergeAiConfigFromSettings,
      normalizeAiConfig,
      normalizeBaseUrl
    } from './js/ai-ui.mjs';

    const DIDACTYL_TOOLS = [
      ['nostr_post', 'Publish a Nostr event to connected relays'],
      ['nostr_delete', 'Request deletion of one or more previously published events'],
      ['nostr_react', 'React to a Nostr event with like/dislike/emoji'],
      ['nostr_query', 'Query events from relays using a Nostr filter'],
      ['nostr_my_events', 'Query recent events authored by this agent'],
      ['nostr_dm_send', 'Send a NIP-04 encrypted DM'],
      ['nostr_dm_send_nip17', 'Send a private DM using NIP-17 gift wrap protocol'],
      ['nostr_profile_get', 'Look up a Nostr profile by pubkey'],
      ['nostr_nip05_lookup', 'Look up or verify a NIP-05 identifier'],
      ['nostr_encode', 'Encode a Nostr entity into nostr: URI'],
      ['nostr_decode', 'Decode a Nostr bech32/nostr: URI'],
      ['nostr_relay_status', 'Get connection status and statistics for all relays'],
      ['nostr_relay_info', 'Fetch NIP-11 relay information document'],
      ['nostr_subscription_status', 'List currently managed runtime Nostr subscriptions'],
      ['nostr_subscription_set', 'Update one managed runtime subscription by name'],
      ['nostr_encrypt', 'Encrypt plaintext using NIP-44 for a recipient'],
      ['nostr_decrypt', 'Decrypt NIP-44 ciphertext from a sender'],
      ['nostr_list_manage', 'Add/remove tag tuples in replaceable list events'],
      ['nostr_block_list', 'View blocked pubkeys, event IDs, or hashtags'],
      ['nostr_block_edit', 'Add or remove blocked tuples in kind-10000 block list'],
      ['nostr_pubkey', 'Return this agent pubkey in hex format'],
      ['nostr_npub', 'Return this agent pubkey encoded as npub'],
      ['my_pubkey', 'Alias for nostr_pubkey'],
      ['my_npub', 'Alias for nostr_npub'],
      ['agent_identity', 'Build agent identity context block'],
      ['admin_identity', 'Build admin identity context block'],
      ['nostr_admin_profile', 'Build admin profile context block'],
      ['nostr_admin_contacts', 'Build admin contacts context block'],
      ['nostr_admin_relays', 'Build admin relay context block'],
      ['nostr_admin_notes', 'Build admin notes context block'],
      ['nostr_agent_profile', 'Build agent profile context block'],
      ['nostr_agent_contacts', 'Build agent contacts context block'],
      ['nostr_agent_relays', 'Build agent relay context block'],
      ['nostr_agent_notes', 'Build agent notes context block'],
      ['my_kind0_profile', 'Alias for nostr_agent_profile'],
      ['my_contacts', 'Alias for nostr_agent_contacts'],
      ['my_relays', 'Alias for nostr_agent_relays'],
      ['my_notes', 'Alias for nostr_agent_notes'],
      ['skill_create', 'Create or update a skill definition'],
      ['skill_edit', 'Edit an existing self skill by d tag'],
      ['skill_list', 'List available skills discovered online'],
      ['skill_adopt', 'Adopt a skill by adding its address to kind 10123'],
      ['skill_remove', 'Remove a skill address from kind 10123'],
      ['skill_search', 'Search public skills by query/author'],
      ['trigger_list', 'List active triggered skills and runtime status'],
      ['task_list', 'Build current task list context block'],
      ['task_manage', 'Manage short-term task memory'],
      ['memory_save', 'Prepend a new entry to encrypted agent memory'],
      ['memory_recall', 'Recall encrypted agent memory'],
      ['model_get', 'Get current active LLM runtime configuration'],
      ['model_set', 'Update active LLM configuration'],
      ['model_list', 'List available model IDs'],
      ['config_store', 'Encrypt and publish agent config'],
      ['config_recall', 'Fetch and decrypt agent config'],
      ['agent_version', 'Return current Didactyl version'],
      ['local_http_fetch', 'Fetch HTTP(S) resources'],
      ['local_shell_exec', 'Execute a shell command'],
      ['local_file_read', 'Read a local file as text'],
      ['local_file_write', 'Write text content to a local file'],
      ['tool_list', 'List available tools with schemas'],
      ['cashu_wallet_balance', 'Return wallet balances by mint and unit'],
      ['cashu_wallet_info', 'Fetch mint info'],
      ['cashu_wallet_mint_quote', 'Request a mint quote'],
      ['cashu_wallet_mint_check', 'Check whether a mint quote is paid/issued'],
      ['cashu_wallet_mint_claim', 'Claim minted proofs'],
      ['cashu_wallet_melt_quote', 'Request a melt quote'],
      ['cashu_wallet_melt_pay', 'Pay a melt quote'],
      ['cashu_wallet_check_proofs', 'Ask mint for current proof states'],
      ['cashu_wallet_receive_token', 'Receive an ecash token string'],
      ['cashu_wallet_send_token', 'Create an outbound ecash token'],
      ['cashu_wallet_mints_set', 'Set wallet/public mints'],
      ['blossom_upload', 'Upload a local file to a Blossom server'],
      ['blossom_download', 'Download a Blossom blob to local path'],
      ['blossom_head', 'Fetch Blossom blob metadata by SHA-256'],
      ['blossom_delete', 'Delete a Blossom blob by SHA-256'],
      ['blossom_list', 'List Blossom blobs for a pubkey'],
      ['nostr_post_readme', 'Publish README.md as kind 30023'],
      ['nostr_file_md_to_longform_post', 'Read markdown and publish as kind 30023']
    ].map(([name, description]) => ({ name, description }));

    const TOOL_NAME_SET = new Set(DIDACTYL_TOOLS.map(t => t.name));

    const versionInfo = await getVersion();
    const VERSION = versionInfo.VERSION;
    console.log(`[skills-edit.html ${VERSION}] Loading...`);

    const AI_STORAGE_KEY = 'ai_chat_openai_config_v1';

    let updateIntervalId = null;
    let currentPubkey = null;
    let isAuthenticated = false;
    let authMode = 'required';
    let authedPageInitialized = false;
    let relayActivityListenersBound = false;

    let hamburgerInstance = null;
    let isNavOpen = false;
    let logoutHamburger = null;
    let themeToggleHamburger = null;
    let isDarkMode = false;
    let pageSettings = {};
    let unsubscribeUserSettings = null;

    let activeTab = 'my';
    let selectedSkillAddress = null;
    let skillsByAddress = new Map();
    let mySkillSub = null;
    let mySkillSubId = null;
    let publicSkillSub = null;
    let publicSkillSubId = null;
    let adoptionSub = null;
    let adoptionSubId = null;
    let publicLoaded = false;
    let adoptedAddresses = [];
    let adoptionEventId = null;

    let requiresTools = [];
    let optionalTools = [];
    let requiresSkills = [];

    let aiConfig = loadAiConfigLocal(getDefaultAiConfig(), AI_STORAGE_KEY);
    let fetchedModels = [];

    const divBody = document.getElementById('divBody');
    const divSideNav = document.getElementById('divSideNav');
    const divFooterCenter = document.getElementById('divFooterCenter');
    const divFooterRight = document.getElementById('divFooterRight');

    const inpSkillSearch = document.getElementById('inpSkillSearch');
    const btnTabMy = document.getElementById('btnTabMy');
    const btnTabPublic = document.getElementById('btnTabPublic');
    const btnTabAdopted = document.getElementById('btnTabAdopted');
    const btnRefreshSkills = document.getElementById('btnRefreshSkills');
    const btnNewSkill = document.getElementById('btnNewSkill');
    const divSkillsList = document.getElementById('divSkillsList');
    const divAdoptedList = document.getElementById('divAdoptedList');

    const selSkillKind = document.getElementById('selSkillKind');
    const inpSkillSlug = document.getElementById('inpSkillSlug');
    const inpSkillDescription = document.getElementById('inpSkillDescription');
    const selSkillTrigger = document.getElementById('selSkillTrigger');
    const inpSkillFilter = document.getElementById('inpSkillFilter');
    const inpSkillLlm = document.getElementById('inpSkillLlm');
    const inpSkillTemperature = document.getElementById('inpSkillTemperature');
    const inpSkillMaxTokens = document.getElementById('inpSkillMaxTokens');
    const inpSkillSeed = document.getElementById('inpSkillSeed');
    const inpRequiresSkill = document.getElementById('inpRequiresSkill');
    const btnAddRequiresSkill = document.getElementById('btnAddRequiresSkill');

    const preSkillHighlight = document.getElementById('preSkillHighlight');
    const taSkillContent = document.getElementById('taSkillContent');
    const btnSavePublic = document.getElementById('btnSavePublic');
    const btnSavePrivate = document.getElementById('btnSavePrivate');
    const btnCopyFork = document.getElementById('btnCopyFork');
    const btnDeleteSkill = document.getElementById('btnDeleteSkill');
    const btnAdoptCurrent = document.getElementById('btnAdoptCurrent');
    const btnRemoveAdoptCurrent = document.getElementById('btnRemoveAdoptCurrent');
    const btnPublishAdoption = document.getElementById('btnPublishAdoption');
    const divEditorStatus = document.getElementById('divEditorStatus');

    const inpToolSearch = document.getElementById('inpToolSearch');
    const divToolsList = document.getElementById('divToolsList');
    const divTriggerFilterHelper = document.getElementById('divTriggerFilterHelper');

    const selLlmProvider = document.getElementById('selLlmProvider');
    const inpLlmModelFilter = document.getElementById('inpLlmModelFilter');
    const selLlmModel = document.getElementById('selLlmModel');
    const btnLlmFavorite = document.getElementById('btnLlmFavorite');
    const btnLlmApply = document.getElementById('btnLlmApply');
    const divLlmHelperStatus = document.getElementById('divLlmHelperStatus');

    const CRON_PRESETS = [
      ['Every 5 min', '*/5 * * * *'],
      ['Hourly (:00)', '0 * * * *'],
      ['Daily noon', '0 12 * * *'],
      ['Daily midnight', '0 0 * * *'],
      ['Weekdays 9am', '0 9 * * 1-5'],
      ['Mondays 9am', '0 9 * * 1']
    ];

    const NOSTR_FILTER_PRESETS = [
      ['Mentions admin (kind 1)', '{"#p":["<admin_pubkey>"],"kinds":[1]}'],
      ['Notes only (kind 1, limit 20)', '{"kinds":[1],"limit":20}'],
      ['Profile updates (kind 0)', '{"kinds":[0]}']
    ];

    const WEBHOOK_FILTER_PRESETS = [
      ['Match all', '{}'],
      ['POST only', '{"method":"POST"}'],
      ['Path /webhook', '{"path":"/webhook"}']
    ];

    function setStatus(msg) {
      divEditorStatus.textContent = String(msg || '');
    }

    function setLlmHelperStatus(msg) {
      if (divLlmHelperStatus) divLlmHelperStatus.textContent = String(msg || '');
    }

    function shortPubkey(pubkey) {
      const p = String(pubkey || '');
      if (p.length < 14) return p;
      return `${p.slice(0, 6)}...${p.slice(-4)}`;
    }

    function normalizeSlug(raw) {
      return String(raw || '')
        .trim()
        .toLowerCase()
        .replace(/[^a-z0-9-_]+/g, '-')
        .replace(/^-+|-+$/g, '')
        .slice(0, 64);
    }

    function escapeHtml(s) {
      const span = document.createElement('span');
      span.textContent = String(s || '');
      return span.innerHTML;
    }

    function getTagValue(tags, key) {
      const t = (Array.isArray(tags) ? tags : []).find(x => Array.isArray(x) && x[0] === key);
      return t && t[1] ? String(t[1]) : '';
    }

    function getTagValues(tags, key) {
      return (Array.isArray(tags) ? tags : [])
        .filter(x => Array.isArray(x) && x[0] === key && typeof x[1] === 'string')
        .map(x => String(x[1]));
    }

    function getLlmProviderEntries() {
      return getProviderEntries(aiConfig);
    }

    function parseLlmToken(value) {
      const raw = String(value || '').trim();
      const first = raw.split(',')[0].trim();
      if (!first) return { provider: '', model: '' };
      const slash = first.indexOf('/');
      if (slash > 0 && slash < first.length - 1) {
        return {
          provider: first.slice(0, slash).trim(),
          model: first.slice(slash + 1).trim()
        };
      }
      return {
        provider: String(aiConfig?.provider || '').trim(),
        model: first
      };
    }

    function getOrderedModels(filterText = '') {
      const models = Array.isArray(fetchedModels) ? fetchedModels : [];
      const favorites = Array.isArray(aiConfig?.favorites) ? aiConfig.favorites : [];
      const q = String(filterText || '').trim().toLowerCase();

      const filtered = models.filter((m) => {
        const id = String(m?.id || '').trim();
        if (!id) return false;
        return !q || id.toLowerCase().includes(q);
      });

      const fav = filtered.filter((m) => favorites.includes(String(m.id || '')));
      const other = filtered.filter((m) => !favorites.includes(String(m.id || '')));
      return { fav, other };
    }

    function renderLlmProviderOptions() {
      if (!selLlmProvider) return;
      const providers = getLlmProviderEntries();
      const current = String(aiConfig?.provider || '').trim();

      if (!providers.length) {
        selLlmProvider.innerHTML = '<option value="">(no providers)</option>';
        return;
      }

      selLlmProvider.innerHTML = providers
        .map((p) => `<option value="${escapeHtml(p.name)}" ${p.name === current ? 'selected' : ''}>${escapeHtml(p.name)}</option>`)
        .join('');

      if (current) selLlmProvider.value = current;
    }

    function renderLlmModelOptions() {
      if (!selLlmModel) return;
      const current = String(parseLlmToken(inpSkillLlm?.value).model || aiConfig?.model || '').trim();
      const { fav, other } = getOrderedModels(inpLlmModelFilter?.value || '');

      const rows = [];
      for (const m of fav) {
        const id = String(m?.id || '').trim();
        rows.push(`<option value="${escapeHtml(id)}" ${id === current ? 'selected' : ''}>★ ${escapeHtml(id)}</option>`);
      }
      for (const m of other) {
        const id = String(m?.id || '').trim();
        rows.push(`<option value="${escapeHtml(id)}" ${id === current ? 'selected' : ''}>${escapeHtml(id)}</option>`);
      }

      selLlmModel.innerHTML = rows.join('') || '<option value="">(no models)</option>';
      if (current) selLlmModel.value = current;

      const selected = String(selLlmModel.value || '').trim();
      const isFav = selected && Array.isArray(aiConfig?.favorites) && aiConfig.favorites.includes(selected);
      if (btnLlmFavorite) btnLlmFavorite.textContent = isFav ? '★ Favorited' : '☆ Favorite';
    }

    function selectLlmProvider(providerName) {
      const name = String(providerName || '').trim();
      if (!name) return;
      const providers = getLlmProviderEntries();
      const selected = providers.find((p) => p.name === name);
      if (!selected) return;

      aiConfig = {
        ...aiConfig,
        provider: selected.name,
        base_url: selected.url,
        api_key: selected.api_key,
        credit_id: selected.credit_id,
        model: selected.model || aiConfig.model,
        max_tokens: Number.isFinite(selected.max_tokens) && selected.max_tokens > 0 ? Math.floor(selected.max_tokens) : aiConfig.max_tokens,
        temperature: Number.isFinite(selected.temperature) ? selected.temperature : aiConfig.temperature
      };
      aiConfig = saveAiConfigLocal(aiConfig, AI_STORAGE_KEY);
      patchUserSettings({ global_llm: normalizeAiConfig(aiConfig) }).catch((error) => {
        console.warn('[skills-edit.html] failed to persist provider to user-settings:', error);
      });
      renderLlmProviderOptions();
      fetchLlmModels();
    }

    async function fetchLlmModels() {
      const baseUrl = normalizeBaseUrl(aiConfig?.base_url || '');
      const apiKey = String(aiConfig?.api_key || '').trim();

      if (!baseUrl) {
        fetchedModels = [];
        renderLlmModelOptions();
        setLlmHelperStatus('No provider base URL configured in AI settings.');
        return;
      }
      if (!apiKey) {
        fetchedModels = [];
        renderLlmModelOptions();
        setLlmHelperStatus('No API key configured for selected provider.');
        return;
      }

      try {
        setLlmHelperStatus('Loading models...');
        const response = await fetch(`${baseUrl}/v1/models`, {
          headers: { Authorization: `Bearer ${apiKey}` }
        });
        if (!response.ok) throw new Error(`HTTP ${response.status}`);
        const data = await response.json();
        const seen = new Set();
        fetchedModels = (Array.isArray(data?.data) ? data.data : [])
          .map((m) => ({ id: String(m?.id || '').trim() }))
          .filter((m) => m.id && !seen.has(m.id) && seen.add(m.id))
          .sort((a, b) => a.id.localeCompare(b.id));
        renderLlmModelOptions();
        setLlmHelperStatus(`Loaded ${fetchedModels.length} models from ${baseUrl}.`);
      } catch (error) {
        fetchedModels = [];
        renderLlmModelOptions();
        setLlmHelperStatus(`Model fetch failed: ${String(error?.message || error)}`);
      }
    }

    function syncLlmHelperFromSkillInput() {
      if (!selLlmProvider || !selLlmModel) return;
      const parsed = parseLlmToken(inpSkillLlm?.value || '');
      if (parsed.provider && Array.from(selLlmProvider.options).some((o) => o.value === parsed.provider)) {
        selLlmProvider.value = parsed.provider;
      }
      if (parsed.model && Array.from(selLlmModel.options).some((o) => o.value === parsed.model)) {
        selLlmModel.value = parsed.model;
      }
      renderLlmModelOptions();
    }

    function applyLlmSelectionToInput() {
      const provider = String(selLlmProvider?.value || aiConfig?.provider || '').trim();
      const model = String(selLlmModel?.value || '').trim();
      if (!provider || !model) {
        setStatus('Select provider and model first.');
        return;
      }
      inpSkillLlm.value = `${provider}/${model}`;
      setStatus(`Set llm tag to ${inpSkillLlm.value}`);
      syncLlmHelperFromSkillInput();
    }

    function toggleCurrentModelFavorite() {
      const model = String(selLlmModel?.value || '').trim();
      if (!model) return;
      const favorites = Array.isArray(aiConfig?.favorites) ? [...aiConfig.favorites] : [];
      const idx = favorites.indexOf(model);
      if (idx >= 0) favorites.splice(idx, 1);
      else favorites.push(model);
      aiConfig = { ...aiConfig, favorites };
      aiConfig = saveAiConfigLocal(aiConfig, AI_STORAGE_KEY);
      patchUserSettings({ global_llm: normalizeAiConfig(aiConfig) }).catch((error) => {
        console.warn('[skills-edit.html] failed to persist favorites to user-settings:', error);
      });
      renderLlmModelOptions();
    }

    async function initializeLlmHelperFromSettings() {
      aiConfig = loadAiConfigLocal(aiConfig || getDefaultAiConfig(), AI_STORAGE_KEY);

      const llmNode = pageSettings?.global_llm || pageSettings?.ai || null;
      if (llmNode && typeof llmNode === 'object') {
        aiConfig = mergeAiConfigFromSettings(aiConfig, llmNode);
      }

      aiConfig = saveAiConfigLocal(aiConfig, AI_STORAGE_KEY);
      renderLlmProviderOptions();
      renderLlmModelOptions();
      syncLlmHelperFromSkillInput();
      fetchLlmModels();
    }

    function parseAddress(address) {
      const parts = String(address || '').split(':');
      if (parts.length < 3) return null;
      const kind = Number(parts[0]);
      const pubkey = parts[1] || '';
      const dTag = parts.slice(2).join(':');
      if (!Number.isFinite(kind) || !pubkey || !dTag) return null;
      return { kind, pubkey, dTag };
    }

    function makeAddress(kind, pubkey, dTag) {
      return `${Number(kind)}:${String(pubkey || '').trim()}:${String(dTag || '').trim()}`;
    }

    function isAddressAdopted(address) {
      return adoptedAddresses.includes(String(address || ''));
    }

    function cronPartsFromFilter(filterRaw) {
      const parts = String(filterRaw || '').trim().split(/\s+/);
      if (parts.length === 5) return parts;
      return ['*', '*', '*', '*', '*'];
    }

    function dmFromFromFilter(filterRaw) {
      try {
        const parsed = JSON.parse(String(filterRaw || '{}'));
        const from = String(parsed?.from || 'admin');
        if (['admin', 'wot', 'any'].includes(from)) return from;
      } catch (_error) {}
      return 'admin';
    }

    function setFilterPlaceholderByTrigger(trigger) {
      const t = String(trigger || '').trim();
      if (!t) {
        inpSkillFilter.placeholder = '(select a trigger)';
        return;
      }
      if (t === 'dm') inpSkillFilter.placeholder = '{"from":"admin"}';
      else if (t === 'cron') inpSkillFilter.placeholder = '0 12 * * *';
      else if (t === 'nostr-subscription') inpSkillFilter.placeholder = '{"#p":["<admin_pubkey>"],"kinds":[1]}';
      else if (t === 'webhook') inpSkillFilter.placeholder = '{}';
      else if (t === 'chain') inpSkillFilter.placeholder = 'source-skill-d-tag';
      else inpSkillFilter.placeholder = 'type-specific filter';
    }

    function renderTriggerFilterHelper() {
      if (!divTriggerFilterHelper) return;
      const trigger = String(selSkillTrigger.value || '').trim();
      setFilterPlaceholderByTrigger(trigger);

      if (!trigger) {
        divTriggerFilterHelper.innerHTML = '<div class="triggerMini">Select a trigger to see filter options and presets.</div>';
        return;
      }

      if (trigger === 'dm') {
        const currentFrom = dmFromFromFilter(inpSkillFilter.value);
        divTriggerFilterHelper.innerHTML = `
          <div class="triggerHelpTitle">DM filter helper</div>
          <div class="triggerHelpRow">
            <select id="selDmFilterFrom" class="sel">
              <option value="admin" ${currentFrom === 'admin' ? 'selected' : ''}>admin</option>
              <option value="wot" ${currentFrom === 'wot' ? 'selected' : ''}>wot</option>
              <option value="any" ${currentFrom === 'any' ? 'selected' : ''}>any</option>
            </select>
            <button id="btnApplyDmFilter" class="btn" type="button">Apply DM filter</button>
          </div>
          <div class="triggerMini">Writes JSON like <code>{"from":"admin"}</code>.</div>
        `;

        const sel = document.getElementById('selDmFilterFrom');
        const btn = document.getElementById('btnApplyDmFilter');
        if (btn && sel) {
          btn.addEventListener('click', () => {
            inpSkillFilter.value = JSON.stringify({ from: String(sel.value || 'admin') });
            setStatus(`Set dm filter: ${inpSkillFilter.value}`);
          });
        }
        return;
      }

      if (trigger === 'cron') {
        const parts = cronPartsFromFilter(inpSkillFilter.value);
        divTriggerFilterHelper.innerHTML = `
          <div class="triggerHelpTitle">Cron filter helper</div>
          <div class="triggerHelpRow">
            <select id="selCronPreset" class="sel">
              ${CRON_PRESETS.map(([label, expr]) => `<option value="${escapeHtml(expr)}">${escapeHtml(label)} — ${escapeHtml(expr)}</option>`).join('')}
            </select>
            <button id="btnApplyCronPreset" class="btn" type="button">Use preset</button>
          </div>
          <div class="triggerHelpRow">
            <input id="inpCronMin" class="inp" type="text" value="${escapeHtml(parts[0])}" placeholder="min" />
            <input id="inpCronHour" class="inp" type="text" value="${escapeHtml(parts[1])}" placeholder="hour" />
            <input id="inpCronDom" class="inp" type="text" value="${escapeHtml(parts[2])}" placeholder="day" />
            <input id="inpCronMon" class="inp" type="text" value="${escapeHtml(parts[3])}" placeholder="month" />
            <input id="inpCronDow" class="inp" type="text" value="${escapeHtml(parts[4])}" placeholder="dow" />
            <button id="btnComposeCron" class="btn" type="button">Compose</button>
          </div>
          <div class="triggerMini">Format: minute hour day-of-month month day-of-week</div>
        `;

        const btnPreset = document.getElementById('btnApplyCronPreset');
        const selPreset = document.getElementById('selCronPreset');
        const btnCompose = document.getElementById('btnComposeCron');
        if (btnPreset && selPreset) {
          btnPreset.addEventListener('click', () => {
            inpSkillFilter.value = String(selPreset.value || '').trim();
            setStatus(`Set cron filter: ${inpSkillFilter.value}`);
            renderTriggerFilterHelper();
          });
        }
        if (btnCompose) {
          btnCompose.addEventListener('click', () => {
            const min = String(document.getElementById('inpCronMin')?.value || '*').trim() || '*';
            const hour = String(document.getElementById('inpCronHour')?.value || '*').trim() || '*';
            const dom = String(document.getElementById('inpCronDom')?.value || '*').trim() || '*';
            const mon = String(document.getElementById('inpCronMon')?.value || '*').trim() || '*';
            const dow = String(document.getElementById('inpCronDow')?.value || '*').trim() || '*';
            inpSkillFilter.value = `${min} ${hour} ${dom} ${mon} ${dow}`;
            setStatus(`Set cron filter: ${inpSkillFilter.value}`);
          });
        }
        return;
      }

      if (trigger === 'chain') {
        const options = Array.from(skillsByAddress.values())
          .filter((s) => s && s.slug)
          .sort((a, b) => String(a.slug).localeCompare(String(b.slug)));

        divTriggerFilterHelper.innerHTML = `
          <div class="triggerHelpTitle">Chain filter helper</div>
          <div class="triggerHelpRow">
            <select id="selChainSource" class="sel">
              <option value="">(pick source skill d-tag)</option>
              ${options.map((s) => `<option value="${escapeHtml(s.slug)}">${escapeHtml(s.slug)} (${escapeHtml(shortPubkey(s.author))})</option>`).join('')}
            </select>
            <button id="btnApplyChain" class="btn" type="button">Apply chain filter</button>
          </div>
          <div class="triggerMini">Filter should be the source skill <code>d</code> tag.</div>
        `;

        const selChain = document.getElementById('selChainSource');
        const btnChain = document.getElementById('btnApplyChain');
        if (selChain && inpSkillFilter.value) selChain.value = String(inpSkillFilter.value || '').trim();
        if (btnChain && selChain) {
          btnChain.addEventListener('click', () => {
            const v = String(selChain.value || '').trim();
            if (!v) return;
            inpSkillFilter.value = v;
            setStatus(`Set chain filter: ${inpSkillFilter.value}`);
          });
        }
        return;
      }

      if (trigger === 'nostr-subscription') {
        divTriggerFilterHelper.innerHTML = `
          <div class="triggerHelpTitle">Nostr-subscription filter helper</div>
          <div class="triggerHelpRow">
            <select id="selNostrFilterPreset" class="sel">
              ${NOSTR_FILTER_PRESETS.map(([label, value]) => `<option value="${escapeHtml(value)}">${escapeHtml(label)}</option>`).join('')}
            </select>
            <button id="btnApplyNostrFilter" class="btn" type="button">Use preset</button>
          </div>
          <div class="triggerMini">Filter is a JSON-encoded Nostr subscription filter.</div>
        `;

        const sel = document.getElementById('selNostrFilterPreset');
        const btn = document.getElementById('btnApplyNostrFilter');
        if (btn && sel) {
          btn.addEventListener('click', () => {
            inpSkillFilter.value = String(sel.value || '').trim();
            setStatus('Set nostr-subscription filter preset.');
          });
        }
        return;
      }

      if (trigger === 'webhook') {
        divTriggerFilterHelper.innerHTML = `
          <div class="triggerHelpTitle">Webhook filter helper</div>
          <div class="triggerHelpRow">
            <select id="selWebhookFilterPreset" class="sel">
              ${WEBHOOK_FILTER_PRESETS.map(([label, value]) => `<option value="${escapeHtml(value)}">${escapeHtml(label)}</option>`).join('')}
            </select>
            <button id="btnApplyWebhookFilter" class="btn" type="button">Use preset</button>
          </div>
          <div class="triggerMini">Use <code>{}</code> to match all requests.</div>
        `;

        const sel = document.getElementById('selWebhookFilterPreset');
        const btn = document.getElementById('btnApplyWebhookFilter');
        if (btn && sel) {
          btn.addEventListener('click', () => {
            inpSkillFilter.value = String(sel.value || '{}').trim();
            setStatus(`Set webhook filter: ${inpSkillFilter.value}`);
          });
        }
        return;
      }

      divTriggerFilterHelper.innerHTML = '<div class="triggerMini">No helper available for this trigger.</div>';
    }

    function toggleTabUI() {
      btnTabMy.classList.toggle('active', activeTab === 'my');
      btnTabPublic.classList.toggle('active', activeTab === 'public');
      btnTabAdopted.classList.toggle('active', activeTab === 'adopted');
    }

    function clearEditor() {
      selectedSkillAddress = null;
      selSkillKind.value = '31123';
      inpSkillSlug.value = '';
      inpSkillDescription.value = '';
      selSkillTrigger.value = '';
      inpSkillFilter.value = '';
      inpSkillLlm.value = '';
      inpSkillTemperature.value = '';
      inpSkillMaxTokens.value = '';
      inpSkillSeed.value = '';
      taSkillContent.value = '## Purpose\nYou are a helpful assistant.\n\nInput:\n{{message}}';
      requiresTools = [];
      optionalTools = [];
      requiresSkills = [];
      renderTagChips();
      renderHighlight();
      renderTools();
      renderTriggerFilterHelper();
      syncLlmHelperFromSkillInput();
      setStatus('New skill ready.');
      btnCopyFork.style.display = 'none';
    }
function renderTagChips() {
  // Tag chips panel intentionally removed from UI to save space.
  // Keep this as a no-op so existing workflows can continue calling it safely.
}


    function getVisibleSkills() {
      const q = String(inpSkillSearch.value || '').trim().toLowerCase();
      const skills = Array.from(skillsByAddress.values());
      let scoped = [];

      if (activeTab === 'my') {
        scoped = skills.filter(s => s.author === currentPubkey);
      } else if (activeTab === 'public') {
        scoped = skills.filter(s => s.kind === 31123 && s.author !== currentPubkey);
      } else {
        scoped = adoptedAddresses
          .map(addr => skillsByAddress.get(addr) || {
            address: addr,
            slug: parseAddress(addr)?.dTag || addr,
            description: '(not loaded yet)',
            author: parseAddress(addr)?.pubkey || '',
            kind: parseAddress(addr)?.kind || 31123,
            missing: true,
            created_at: 0
          });
      }

      if (q) {
        scoped = scoped.filter((s) => {
          const text = `${s.slug || ''} ${s.description || ''} ${s.author || ''}`.toLowerCase();
          return text.includes(q);
        });
      }

      if (activeTab !== 'adopted') {
        scoped.sort((a, b) => {
          const ta = Number(a.created_at || 0);
          const tb = Number(b.created_at || 0);
          if (tb !== ta) return tb - ta;
          return String(a.slug || '').localeCompare(String(b.slug || ''));
        });
      }

      return scoped;
    }

    function renderSkillsList() {
      toggleTabUI();
      const list = getVisibleSkills();
      if (!list.length) {
        divSkillsList.innerHTML = '<div class="hint">No skills in this tab.</div>';
        return;
      }

      divSkillsList.innerHTML = list.map((s) => {
        const adopted = isAddressAdopted(s.address);
        const active = selectedSkillAddress === s.address ? 'active' : '';
        const author = shortPubkey(s.author);
        return `
          <div class="skillItem ${active}" data-address="${escapeHtml(s.address)}">
            <div class="skillItemTitle">
              <span>${escapeHtml(s.slug || '(missing slug)')}</span>
              <span>${adopted ? '<span class="chipStar">★</span>' : ''}</span>
            </div>
            <div class="skillItemMeta">${escapeHtml(s.description || '')}</div>
            <div class="skillItemMeta">${escapeHtml(author)} · ${escapeHtml(String(s.kind || ''))}${s.missing ? ' · missing' : ''}</div>
          </div>
        `;
      }).join('');

      Array.from(divSkillsList.querySelectorAll('.skillItem[data-address]')).forEach((el) => {
        el.addEventListener('click', () => {
          const address = String(el.getAttribute('data-address') || '');
          loadSkillIntoEditor(address);
        });
      });
    }

    function renderAdoptedList() {
      if (!adoptedAddresses.length) {
        divAdoptedList.innerHTML = '<div class="hint">No adopted skills yet.</div>';
        return;
      }

      divAdoptedList.innerHTML = adoptedAddresses.map((addr, i) => {
        const s = skillsByAddress.get(addr);
        const label = s ? `${s.slug} (${shortPubkey(s.author)})` : (parseAddress(addr)?.dTag || addr);
        return `
          <div class="adoptItem" data-address="${escapeHtml(addr)}">
            <div>${escapeHtml(label)}</div>
            <div class="adoptActions">
              <button class="miniBtn" type="button" data-act="up" data-i="${i}">↑</button>
              <button class="miniBtn" type="button" data-act="down" data-i="${i}">↓</button>
              <button class="miniBtn" type="button" data-act="remove" data-i="${i}">×</button>
            </div>
          </div>
        `;
      }).join('');

      Array.from(divAdoptedList.querySelectorAll('button[data-act][data-i]')).forEach((btn) => {
        btn.addEventListener('click', () => {
          const i = Number(btn.getAttribute('data-i'));
          const act = String(btn.getAttribute('data-act') || '');
          if (!Number.isFinite(i) || i < 0 || i >= adoptedAddresses.length) return;
          if (act === 'remove') {
            adoptedAddresses.splice(i, 1);
          } else if (act === 'up' && i > 0) {
            const tmp = adoptedAddresses[i - 1];
            adoptedAddresses[i - 1] = adoptedAddresses[i];
            adoptedAddresses[i] = tmp;
          } else if (act === 'down' && i < adoptedAddresses.length - 1) {
            const tmp = adoptedAddresses[i + 1];
            adoptedAddresses[i + 1] = adoptedAddresses[i];
            adoptedAddresses[i] = tmp;
          }
          renderAdoptedList();
          renderSkillsList();
        });
      });
    }

    function renderTools() {
      const q = String(inpToolSearch.value || '').trim().toLowerCase();
      const filtered = DIDACTYL_TOOLS.filter((t) => {
        if (!q) return true;
        return t.name.toLowerCase().includes(q) || t.description.toLowerCase().includes(q);
      });

      divToolsList.innerHTML = filtered.map((t) => {
        const required = requiresTools.includes(t.name) ? 'required' : '';
        return `
          <div class="toolItem ${required}" data-tool="${escapeHtml(t.name)}">
            <div class="toolName">${escapeHtml(t.name)}</div>
            <div class="toolDesc">${escapeHtml(t.description)}</div>
          </div>
        `;
      }).join('');

      Array.from(divToolsList.querySelectorAll('.toolItem[data-tool]')).forEach((el) => {
        el.addEventListener('dblclick', () => {
          const tool = String(el.getAttribute('data-tool') || '');
          if (!tool) return;
          if (!requiresTools.includes(tool)) requiresTools.push(tool);
          insertAtCursor(taSkillContent, `{{${tool}}}`);
          renderTagChips();
          renderTools();
          renderHighlight();
          setStatus(`Added requires_tool and inserted {{${tool}}}`);
        });
      });
    }

    function insertAtCursor(textarea, text) {
      const start = textarea.selectionStart ?? textarea.value.length;
      const end = textarea.selectionEnd ?? textarea.value.length;
      const before = textarea.value.slice(0, start);
      const after = textarea.value.slice(end);
      textarea.value = `${before}${text}${after}`;
      const next = start + text.length;
      textarea.selectionStart = next;
      textarea.selectionEnd = next;
      textarea.focus();
    }

    function renderHighlight() {
      let html = escapeHtml(taSkillContent.value || '');

      html = html.replace(/\{\{\s*([a-zA-Z0-9_-]+)\s*\}\}/g, (m, name) => {
        if (TOOL_NAME_SET.has(name)) {
          return `<span class="hl-tool">{{${escapeHtml(name)}}}</span>`;
        }
        return `<span class="hl-var">{{${escapeHtml(name)}}}</span>`;
      });

      preSkillHighlight.innerHTML = html + '\n';
    }

    function syncEditorScroll() {
      preSkillHighlight.scrollTop = taSkillContent.scrollTop;
      preSkillHighlight.scrollLeft = taSkillContent.scrollLeft;
    }

    function upsertSkill(skill) {
      if (!skill || !skill.address) return;
      const prev = skillsByAddress.get(skill.address);
      if (!prev || Number(skill.created_at || 0) >= Number(prev.created_at || 0)) {
        skillsByAddress.set(skill.address, skill);
      }
    }

    async function parseSkillEvent(evt) {
      if (!evt || (evt.kind !== 31123 && evt.kind !== 31124)) return null;
      const slug = normalizeSlug(getTagValue(evt.tags, 'd'));
      if (!slug) return null;

      let content = String(evt.content || '');
      let tags = Array.isArray(evt.tags) ? evt.tags.map(t => Array.isArray(t) ? [...t] : t) : [];
      let decryptError = '';

      if (evt.kind === 31124) {
        try {
          if (!window.nostr?.nip44?.decrypt) throw new Error('nip44 decrypt unavailable in signer');
          const plaintext = await window.nostr.nip44.decrypt(String(evt.pubkey || ''), String(evt.content || ''));
          const parsed = JSON.parse(String(plaintext || '{}'));
          content = String(parsed?.content || '');
          const privateTags = Array.isArray(parsed?.private_tags) ? parsed.private_tags : [];
          tags = [['d', slug], ...privateTags.filter(t => Array.isArray(t) && t.length >= 2)];
        } catch (error) {
          decryptError = String(error?.message || error || 'decrypt failed');
        }
      }

      const address = makeAddress(evt.kind, evt.pubkey, slug);
      return {
        address,
        key: `${address}`,
        kind: Number(evt.kind),
        slug,
        description: getTagValue(tags, 'description') || slug,
        trigger: getTagValue(tags, 'trigger'),
        filter: getTagValue(tags, 'filter'),
        llm: getTagValue(tags, 'llm'),
        temperature: getTagValue(tags, 'temperature'),
        max_tokens: getTagValue(tags, 'max_tokens'),
        seed: getTagValue(tags, 'seed'),
        requires_tool: getTagValues(tags, 'requires_tool'),
        optional_tool: getTagValues(tags, 'optional_tool'),
        requires_skill: getTagValues(tags, 'requires_skill'),
        content,
        rawTags: tags,
        rawEvent: evt,
        author: String(evt.pubkey || ''),
        created_at: Number(evt.created_at || 0),
        decryptError
      };
    }

    function loadSkillIntoEditor(address) {
      const skill = skillsByAddress.get(address);
      selectedSkillAddress = address;
      renderSkillsList();
      if (!skill) {
        setStatus('Skill record is not loaded yet.');
        return;
      }

      selSkillKind.value = String(skill.kind || 31123);
      inpSkillSlug.value = skill.slug || '';
      inpSkillDescription.value = skill.description || '';
      selSkillTrigger.value = skill.trigger || '';
      inpSkillFilter.value = skill.filter || '';
      renderTriggerFilterHelper();
      inpSkillLlm.value = skill.llm || '';
      syncLlmHelperFromSkillInput();
      inpSkillTemperature.value = skill.temperature || '';
      inpSkillMaxTokens.value = skill.max_tokens || '';
      inpSkillSeed.value = skill.seed || '';
      taSkillContent.value = String(skill.content || '');
      requiresTools = [...(skill.requires_tool || [])];
      optionalTools = [...(skill.optional_tool || [])];
      requiresSkills = [...(skill.requires_skill || [])];

      renderTagChips();
      renderTools();
      renderHighlight();
      syncEditorScroll();

      const isForeignPublic = skill.kind === 31123 && skill.author && currentPubkey && skill.author !== currentPubkey;
      btnCopyFork.style.display = isForeignPublic ? '' : 'none';

      if (skill.decryptError) {
        setStatus(`Loaded ${skill.slug} with decrypt warning: ${skill.decryptError}`);
      } else {
        setStatus(`Loaded ${skill.slug}`);
      }
    }

    async function handleIncomingSkillEvent(evt) {
      const parsed = await parseSkillEvent(evt);
      if (!parsed) return;
      upsertSkill(parsed);
      renderSkillsList();
      renderAdoptedList();
      renderTriggerFilterHelper();
      if (selectedSkillAddress === parsed.address) {
        loadSkillIntoEditor(parsed.address);
      }
    }

    function parseAdoptionEvent(evt) {
      if (!evt || evt.kind !== 10123 || evt.pubkey !== currentPubkey) return;
      const tags = Array.isArray(evt.tags) ? evt.tags : [];
      adoptedAddresses = tags
        .filter(t => Array.isArray(t) && t[0] === 'a' && typeof t[1] === 'string')
        .map(t => String(t[1]));
      adoptionEventId = String(evt.id || '');
      renderAdoptedList();
      renderSkillsList();
    }

    function buildSkillTags() {
      const tags = [];
      const slug = normalizeSlug(inpSkillSlug.value);
      const description = String(inpSkillDescription.value || '').trim();
      const trigger = String(selSkillTrigger.value || '').trim();
      const filter = String(inpSkillFilter.value || '').trim();
      const llm = String(inpSkillLlm.value || '').trim();
      const temperature = String(inpSkillTemperature.value || '').trim();
      const maxTokens = String(inpSkillMaxTokens.value || '').trim();
      const seed = String(inpSkillSeed.value || '').trim();

      tags.push(['d', slug]);
      if (description) tags.push(['description', description]);
      if (trigger) tags.push(['trigger', trigger]);
      if (filter) tags.push(['filter', filter]);
      if (llm) tags.push(['llm', llm]);
      if (temperature) tags.push(['temperature', temperature]);
      if (maxTokens) tags.push(['max_tokens', maxTokens]);
      if (seed) tags.push(['seed', seed]);

      for (const t of requiresTools) tags.push(['requires_tool', t]);
      for (const t of optionalTools) tags.push(['optional_tool', t]);
      for (const s of requiresSkills) tags.push(['requires_skill', s]);

      return tags;
    }

    async function saveSkill(kind) {
      const slug = normalizeSlug(inpSkillSlug.value);
      if (!slug) return setStatus('Slug is required.');
      const content = String(taSkillContent.value || '').trim();
      if (!content) return setStatus('Skill content is required.');
      const now = Math.floor(Date.now() / 1000);

      try {
        if (Number(kind) === 31123) {
          const event = {
            kind: 31123,
            content,
            tags: buildSkillTags(),
            created_at: now
          };
          await publishEvent(event);
          setStatus(`Published public skill: ${slug}`);
        } else {
          if (!window.nostr?.nip44?.encrypt) {
            throw new Error('NIP-44 encryption unavailable in signer');
          }
          const allTags = buildSkillTags();
          const privateTags = allTags.filter((t) => Array.isArray(t) && t[0] !== 'd');
          const payload = JSON.stringify({
            content,
            private_tags: privateTags
          });
          const ciphertext = await window.nostr.nip44.encrypt(currentPubkey, payload);
          const event = {
            kind: 31124,
            content: String(ciphertext || ''),
            tags: [['d', slug]],
            created_at: now
          };
          await publishEvent(event);
          setStatus(`Published private skill: ${slug}`);
        }

        refreshSkills();
      } catch (error) {
        setStatus(`Save failed: ${String(error?.message || error)}`);
      }
    }

    async function deleteSelectedSkill() {
      const skill = selectedSkillAddress ? skillsByAddress.get(selectedSkillAddress) : null;
      if (!skill) return setStatus('Select a skill first.');
      if (skill.author !== currentPubkey) return setStatus('You can only delete your own skills.');
      if (!confirm(`Delete skill "${skill.slug}"?`)) return;

      const event = {
        kind: 5,
        content: '',
        tags: [['a', skill.address]],
        created_at: Math.floor(Date.now() / 1000)
      };
      if (skill.rawEvent?.id) event.tags.push(['e', skill.rawEvent.id]);

      try {
        await publishEvent(event);
        setStatus(`Deletion request published for ${skill.slug}`);
      } catch (error) {
        setStatus(`Delete failed: ${String(error?.message || error)}`);
      }
    }

    function currentEditorAddress() {
      const kind = Number(selSkillKind.value || 31123);
      const slug = normalizeSlug(inpSkillSlug.value);
      if (!slug || !currentPubkey) return null;
      return makeAddress(kind, currentPubkey, slug);
    }

    async function publishAdoptionList() {
      try {
        const event = {
          kind: 10123,
          content: '',
          tags: adoptedAddresses.map((addr) => ['a', addr]),
          created_at: Math.floor(Date.now() / 1000)
        };
        if (adoptionEventId) event.tags.push(['e', adoptionEventId]);
        await publishEvent(event);
        setStatus(`Published adoption list (${adoptedAddresses.length} skills).`);
      } catch (error) {
        setStatus(`Publish 10123 failed: ${String(error?.message || error)}`);
      }
    }

    async function adoptCurrentSkill() {
      let addr = selectedSkillAddress;
      if (!addr) addr = currentEditorAddress();
      if (!addr) return setStatus('Need selected skill or valid slug/kind to adopt.');
      if (!adoptedAddresses.includes(addr)) adoptedAddresses.push(addr);
      renderAdoptedList();
      renderSkillsList();
      await publishAdoptionList();
    }

    async function removeCurrentAdoptedSkill() {
      let addr = selectedSkillAddress;
      if (!addr) addr = currentEditorAddress();
      if (!addr) return setStatus('Need selected skill or valid slug/kind.');
      adoptedAddresses = adoptedAddresses.filter((x) => x !== addr);
      renderAdoptedList();
      renderSkillsList();
      await publishAdoptionList();
    }

    function copySelectedSkillToMine() {
      const skill = selectedSkillAddress ? skillsByAddress.get(selectedSkillAddress) : null;
      if (!skill) return setStatus('Select a public skill to copy.');
      if (skill.kind !== 31123 || skill.author === currentPubkey) return setStatus('Copy is for public skills by other authors.');

      const nextSlug = normalizeSlug(`${skill.slug}-copy`) || `skill-copy-${Date.now()}`;
      selSkillKind.value = '31123';
      inpSkillSlug.value = nextSlug;
      inpSkillDescription.value = `${skill.description || skill.slug} (copy)`;
      selSkillTrigger.value = skill.trigger || '';
      inpSkillFilter.value = skill.filter || '';
      inpSkillLlm.value = skill.llm || '';
      syncLlmHelperFromSkillInput();
      inpSkillTemperature.value = skill.temperature || '';
      inpSkillMaxTokens.value = skill.max_tokens || '';
      inpSkillSeed.value = skill.seed || '';
      taSkillContent.value = skill.content || '';
      requiresTools = [...(skill.requires_tool || [])];
      optionalTools = [...(skill.optional_tool || [])];
      requiresSkills = [...(skill.requires_skill || [])];
      selectedSkillAddress = null;
      renderTagChips();
      renderTools();
      renderHighlight();
      renderTriggerFilterHelper();
      renderSkillsList();
      btnCopyFork.style.display = 'none';
      setStatus(`Copied ${skill.slug} to editor as ${nextSlug}. Save to publish.`);
    }

    function clearSub(refName) {
      try {
        const sub = (refName === 'my') ? mySkillSub : (refName === 'public') ? publicSkillSub : adoptionSub;
        if (sub && typeof sub.close === 'function') sub.close();
      } catch (_error) {}
      if (refName === 'my') {
        mySkillSub = null;
        mySkillSubId = null;
      } else if (refName === 'public') {
        publicSkillSub = null;
        publicSkillSubId = null;
      } else {
        adoptionSub = null;
        adoptionSubId = null;
      }
    }

    function refreshMySkills() {
      clearSub('my');
      mySkillSub = subscribe({ kinds: [31123, 31124], authors: [currentPubkey], limit: 400 }, { closeOnEose: false, cacheUsage: 'CACHE_FIRST' });
      mySkillSubId = mySkillSub?.subId || null;
    }

    function refreshPublicSkills() {
      clearSub('public');
      publicSkillSub = subscribe({ kinds: [31123], limit: 500 }, { closeOnEose: false, cacheUsage: 'CACHE_FIRST' });
      publicSkillSubId = publicSkillSub?.subId || null;
      publicLoaded = true;
    }

    function refreshAdoption() {
      clearSub('adoption');
      adoptionSub = subscribe({ kinds: [10123], authors: [currentPubkey], limit: 1 }, { closeOnEose: false, cacheUsage: 'CACHE_FIRST' });
      adoptionSubId = adoptionSub?.subId || null;
    }

    function refreshSkills() {
      setStatus('Refreshing skills...');
      refreshMySkills();
      refreshAdoption();
      if (activeTab === 'public' || publicLoaded) refreshPublicSkills();
    }

    function bindSkillEventListeners() {
      window.addEventListener('ndkEvent', async (event) => {
        const evt = event?.detail;
        if (!evt) return;
        if (evt.kind === 10123 && evt.pubkey === currentPubkey) {
          parseAdoptionEvent(evt);
          return;
        }
        if (evt.kind === 31123 || evt.kind === 31124) {
          await handleIncomingSkillEvent(evt);
        }
      });

      window.addEventListener('ndkEose', (event) => {
        const sid = event?.detail?.subId || null;
        if (!sid) return;
        if (sid === mySkillSubId || sid === publicSkillSubId || sid === adoptionSubId) {
          if (!getVisibleSkills().length) {
            setStatus(activeTab === 'public' ? 'No public skills found yet.' : 'No skills found yet.');
          }
        }
      });
    }

    function bindUi() {
      btnTabMy.addEventListener('click', () => {
        activeTab = 'my';
        renderSkillsList();
      });
      btnTabPublic.addEventListener('click', () => {
        activeTab = 'public';
        if (!publicLoaded) refreshPublicSkills();
        renderSkillsList();
      });
      btnTabAdopted.addEventListener('click', () => {
        activeTab = 'adopted';
        renderSkillsList();
      });

      inpSkillSearch.addEventListener('input', renderSkillsList);
      inpToolSearch.addEventListener('input', renderTools);
      selSkillTrigger.addEventListener('change', renderTriggerFilterHelper);
      selLlmProvider?.addEventListener('change', () => selectLlmProvider(selLlmProvider.value));
      selLlmModel?.addEventListener('change', renderLlmModelOptions);
      inpLlmModelFilter?.addEventListener('input', renderLlmModelOptions);
      btnLlmFavorite?.addEventListener('click', toggleCurrentModelFavorite);
      btnLlmApply?.addEventListener('click', applyLlmSelectionToInput);

      btnRefreshSkills.addEventListener('click', refreshSkills);
      btnNewSkill.addEventListener('click', clearEditor);

      taSkillContent.addEventListener('input', renderHighlight);
      taSkillContent.addEventListener('scroll', syncEditorScroll);
      window.addEventListener('resize', syncEditorScroll);

      btnAddRequiresSkill.addEventListener('click', () => {
        const v = normalizeSlug(inpRequiresSkill.value);
        if (!v) return;
        if (!requiresSkills.includes(v)) requiresSkills.push(v);
        inpRequiresSkill.value = '';
        renderTagChips();
      });

      btnSavePublic.addEventListener('click', () => saveSkill(31123));
      btnSavePrivate.addEventListener('click', () => saveSkill(31124));
      btnDeleteSkill.addEventListener('click', deleteSelectedSkill);
      btnCopyFork.addEventListener('click', copySelectedSkillToMine);
      btnAdoptCurrent.addEventListener('click', adoptCurrentSkill);
      btnRemoveAdoptCurrent.addEventListener('click', removeCurrentAdoptedSkill);
      btnPublishAdoption.addEventListener('click', publishAdoptionList);

      btnCopyFork.style.display = 'none';
      renderTriggerFilterHelper();
      syncLlmHelperFromSkillInput();
    }

    function initHamburgerMenu() {
      hamburgerInstance = new HamburgerMorphing('#divSvgHam', {
        foreground: 'var(--primary-color)',
        background: 'var(--secondary-color)',
        hover: 'var(--accent-color)'
      });
      hamburgerInstance.animateTo('burger');
    }

    function openNav() {
      divSideNav.style.zIndex = 3;
      divSideNav.style.width = 'clamp(400px, 50vw, 600px)';
      isNavOpen = true;
      if (hamburgerInstance) hamburgerInstance.animateTo('arrow_left');

      if (!logoutHamburger) {
        logoutHamburger = new HamburgerMorphing('#logoutHamburgerContainer', {
          size: 24,
          foreground: 'var(--primary-color)',
          background: 'var(--secondary-color)',
          hover: 'var(--accent-color)'
        });
        logoutHamburger.animateTo('x');
      }

      if (!themeToggleHamburger) {
        themeToggleHamburger = new HamburgerMorphing('#themeToggleHamburgerContainer', {
          size: 24,
          foreground: 'var(--primary-color)',
          background: 'var(--secondary-color)',
          hover: 'var(--accent-color)'
        });
        const savedTheme = localStorage.getItem('theme');
        isDarkMode = savedTheme === 'dark' || document.body.classList.contains('dark-mode');
        themeToggleHamburger.animateTo(isDarkMode ? 'moon' : 'circle');
      }
    }

    function closeNav() {
      divSideNav.style.width = '0vw';
      divSideNav.style.zIndex = -1;
      isNavOpen = false;
      if (hamburgerInstance) hamburgerInstance.animateTo('burger');
    }

    function toggleNav() {
      if (isNavOpen) closeNav();
      else openNav();
    }

    function hasTargetPubkeyInUrl() {
      const params = new URLSearchParams(window.location.search || '');
      return Boolean(String(params.get('npub') || '').trim() || String(params.get('pubkey') || '').trim());
    }

    function resolveAuthModeFromUrl() {
      const params = new URLSearchParams(window.location.search || '');
      const explicitAuth = String(params.get('auth') || '').trim().toLowerCase();
      if (['required', 'optional', 'none'].includes(explicitAuth)) return explicitAuth;
      if (hasTargetPubkeyInUrl()) return 'optional';
      return 'required';
    }

    function isAuthRequiredError(error) {
      const message = String(error?.message || error || '').toLowerCase();
      return message.includes('authentication required');
    }

    async function initializeAuthentication(mode) {
      if (mode === 'required') {
        await initNDKPage();
        currentPubkey = await getPubkey();
        isAuthenticated = true;
        return;
      }
      if (mode === 'none') {
        isAuthenticated = false;
        currentPubkey = null;
        return;
      }

      try {
        await initNDKPage();
        currentPubkey = await getPubkey();
        isAuthenticated = true;
      } catch (error) {
        if (isAuthRequiredError(error)) {
          isAuthenticated = false;
          currentPubkey = null;
          return;
        }
        throw error;
      }
    }

    async function UpdateFooter() {
      try {
        await updateFooterRelayStatus();
        await updateSidenavRelaySection();
        await updateBlossomSection();
        divFooterCenter.innerHTML = '';
        divFooterRight.innerHTML = '';
      } catch (error) {
        console.error('[skills-edit.html] footer update error:', error);
      }
    }

    async function initializeAuthenticatedPageFeatures() {
      if (!isAuthenticated || authedPageInitialized) return;

      await injectHeaderAvatar(currentPubkey);

      try {
        pageSettings = await getUserSettings();
      } catch (_error) {
        pageSettings = {};
      }

      if (!unsubscribeUserSettings) {
        unsubscribeUserSettings = onUserSettings((settings) => {
          pageSettings = settings || {};
          initializeLlmHelperFromSettings();
        });
      }

      initFooterRelayStatus();
      initSidenavRelaySection();
      await initBlossomSection();
      initAiSectionWithLocalConfig();
      initializeLlmHelperFromSettings();
      await UpdateFooter();
      if (!updateIntervalId) updateIntervalId = setInterval(UpdateFooter, 1000);

      if (!relayActivityListenersBound) {
        window.addEventListener('ndkRelayActivity', (event) => {
          const { relayUrl, activity } = event.detail || {};
          if (relayUrl && activity) setRelayActivityState(relayUrl, activity);
        });

        window.addEventListener('message', (event) => {
          if (event.data && event.data.type === 'relayActivity') {
            const { relayUrl, activity } = event.data;
            if (relayUrl && activity) setRelayActivityState(relayUrl, activity);
          }
        });

        relayActivityListenersBound = true;
      }

      bindUi();
      bindSkillEventListeners();
      renderTools();
      clearEditor();
      refreshSkills();
      renderSkillsList();
      renderAdoptedList();

      authedPageInitialized = true;
    }

    async function promptLoginIfNeeded() {
      if (isAuthenticated) return true;
      await initNDKPage();
      currentPubkey = await getPubkey();
      isAuthenticated = true;
      await initializeAuthenticatedPageFeatures();
      return true;
    }

    const Logout = async () => {
      if (updateIntervalId) {
        clearInterval(updateIntervalId);
        updateIntervalId = null;
      }
      clearSub('my');
      clearSub('public');
      clearSub('adoption');
      disconnect();
      if (window.NOSTR_LOGIN_LITE?.logout) await window.NOSTR_LOGIN_LITE.logout();
      localStorage.clear();
      sessionStorage.clear();
      if (window.indexedDB) {
        const databases = await window.indexedDB.databases();
        for (const db of databases) {
          if (db.name) window.indexedDB.deleteDatabase(db.name);
        }
      }
      location.reload(true);
    };

    (async function main() {
      console.log('[skills-edit.html] Starting initialization...');
      try {
        initHamburgerMenu();

        const divSvgHam = document.getElementById('divSvgHam');
        if (divSvgHam) divSvgHam.addEventListener('click', toggleNav);

        const themeToggleButton = document.getElementById('themeToggleButton');
        const logoutButton = document.getElementById('logoutButton');

        if (themeToggleButton) {
          themeToggleButton.addEventListener('click', () => {
            isDarkMode = !isDarkMode;
            localStorage.setItem('theme', isDarkMode ? 'dark' : 'light');
            localStorage.setItem('sidenavWasOpen', isNavOpen ? 'true' : 'false');
            window.location.reload();
          });
        }

        if (logoutButton) {
          logoutButton.addEventListener('click', async () => {
            try {
              if (!isAuthenticated) {
                await promptLoginIfNeeded();
                return;
              }
              await Logout();
            } catch (error) {
              console.error('[skills-edit.html] logout/login failed', error);
            }
          });
        }

        authMode = resolveAuthModeFromUrl();
        await initializeAuthentication(authMode);
        await initializeAuthenticatedPageFeatures();

        const sidenavWasOpen = localStorage.getItem('sidenavWasOpen');
        if (sidenavWasOpen === 'true') {
          localStorage.removeItem('sidenavWasOpen');
          openNav();
        }

        if (!isAuthenticated && (authMode === 'optional' || authMode === 'none')) {
          divFooterCenter.textContent = 'Public mode';
          divFooterRight.textContent = 'Sign in from side menu for private features';
        }

        await updateVersionDisplay();
      } catch (error) {
        console.error('[skills-edit.html] Initialization failed:', error);
        divBody.innerHTML = `<div style="text-align:center;padding:50px;">
          <div style="font-size:24px;margin-bottom:20px;color:red;">❌ Initialization Error</div>
          <div style="font-size:16px;color:#666;">${escapeHtml(error?.message || error)}</div>
          <div style="margin-top:20px;"><button onclick="location.reload()" style="padding:10px 20px; font-size:16px;">Retry</button></div>
        </div>`;
      }
    })();
  