glad-web 1.0.33 → 1.0.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/web/codex.js +155 -12
- package/lib/web/core.js +2 -0
- package/lib/web/session.js +2 -0
- package/lib/web/styles.css +12 -2
- package/package.json +1 -1
package/lib/web/codex.js
CHANGED
|
@@ -603,19 +603,157 @@
|
|
|
603
603
|
}
|
|
604
604
|
}
|
|
605
605
|
|
|
606
|
-
|
|
606
|
+
const CODEX_RECENT_SKILLS_KEY = 'glad.codex.recentSkills';
|
|
607
|
+
|
|
608
|
+
function codexSkillIdentity(skill) {
|
|
609
|
+
return `${String(skill?.name || '')}\n${String(skill?.path || '')}`;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function codexSkillDisplayName(skill) {
|
|
613
|
+
return String(skill?.interface?.displayName || skill?.name || 'Unknown skill');
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function codexSkillDescription(skill) {
|
|
617
|
+
return String(skill?.interface?.shortDescription || skill?.shortDescription || skill?.description || '');
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function codexSkillDirectory(skill) {
|
|
621
|
+
const path = String(skill?.path || '').replace(/\\/g, '/');
|
|
622
|
+
const separator = path.lastIndexOf('/');
|
|
623
|
+
const directory = separator > 0 ? path.slice(0, separator) : path;
|
|
624
|
+
const skillRoot = directory.toLowerCase().lastIndexOf('/skills/');
|
|
625
|
+
return skillRoot >= 0 ? directory.slice(skillRoot + '/skills/'.length) : directory.split('/').filter(Boolean).pop() || directory;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function codexSkillSource(skill) {
|
|
629
|
+
const scope = String(skill?.scope || '').toLowerCase();
|
|
630
|
+
const path = String(skill?.path || '').replace(/\\/g, '/').toLowerCase();
|
|
631
|
+
if (['repo', 'project', 'workspace'].includes(scope)) return { group: 'project', label: 'Project' };
|
|
632
|
+
if (['user', 'personal'].includes(scope)) return { group: 'personal', label: 'Personal' };
|
|
633
|
+
if (scope === 'plugin') return { group: 'other', label: 'Plugin' };
|
|
634
|
+
if (['system', 'admin', 'builtin', 'built-in'].includes(scope)) return { group: 'other', label: 'System' };
|
|
635
|
+
if (path.includes('/plugins/')) return { group: 'other', label: 'Plugin' };
|
|
636
|
+
if (path.includes('/.system/')) return { group: 'other', label: 'System' };
|
|
637
|
+
if (path.includes('/.codex/skills/')) return { group: 'personal', label: 'Personal' };
|
|
638
|
+
if (path.includes('/.agents/skills/')) return { group: 'project', label: 'Project' };
|
|
639
|
+
return { group: 'other', label: scope ? scope[0].toUpperCase() + scope.slice(1) : 'Other' };
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function compareCodexSkills(left, right) {
|
|
643
|
+
return codexSkillDisplayName(left).localeCompare(codexSkillDisplayName(right), undefined, {
|
|
644
|
+
sensitivity: 'base',
|
|
645
|
+
numeric: true
|
|
646
|
+
}) || String(left.name || '').localeCompare(String(right.name || ''), undefined, { sensitivity: 'base', numeric: true })
|
|
647
|
+
|| String(left.path || '').localeCompare(String(right.path || ''), undefined, { sensitivity: 'base', numeric: true });
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function readRecentCodexSkills() {
|
|
651
|
+
try {
|
|
652
|
+
const value = JSON.parse(localStorage.getItem(CODEX_RECENT_SKILLS_KEY) || '[]');
|
|
653
|
+
return Array.isArray(value) ? value.filter(item => item?.name && item?.path).slice(0, 3) : [];
|
|
654
|
+
} catch (_) {
|
|
655
|
+
return [];
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function rememberCodexSkill(skill) {
|
|
660
|
+
const key = codexSkillIdentity(skill);
|
|
661
|
+
const recent = readRecentCodexSkills().filter(item => codexSkillIdentity(item) !== key);
|
|
662
|
+
recent.unshift({ name: skill.name, path: skill.path });
|
|
663
|
+
try { localStorage.setItem(CODEX_RECENT_SKILLS_KEY, JSON.stringify(recent.slice(0, 3))); } catch (_) {}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function scoreCodexSkillSearch(skill, query) {
|
|
667
|
+
const tokens = String(query || '').toLocaleLowerCase().trim().split(/\s+/).filter(Boolean);
|
|
668
|
+
if (!tokens.length) return 0;
|
|
669
|
+
const source = codexSkillSource(skill);
|
|
670
|
+
const fields = [
|
|
671
|
+
String(skill.name || '').toLocaleLowerCase(),
|
|
672
|
+
codexSkillDisplayName(skill).toLocaleLowerCase(),
|
|
673
|
+
codexSkillDescription(skill).toLocaleLowerCase(),
|
|
674
|
+
source.label.toLocaleLowerCase(),
|
|
675
|
+
String(skill.scope || '').toLocaleLowerCase(),
|
|
676
|
+
String(skill.path || '').toLocaleLowerCase()
|
|
677
|
+
];
|
|
678
|
+
let score = 0;
|
|
679
|
+
for (const token of tokens) {
|
|
680
|
+
let tokenScore = Number.POSITIVE_INFINITY;
|
|
681
|
+
fields.forEach((field, fieldIndex) => {
|
|
682
|
+
const position = field.indexOf(token);
|
|
683
|
+
if (position < 0) return;
|
|
684
|
+
const exactBonus = field === token ? -4 : position === 0 ? -2 : 0;
|
|
685
|
+
tokenScore = Math.min(tokenScore, fieldIndex * 10 + position + exactBonus);
|
|
686
|
+
});
|
|
687
|
+
if (!Number.isFinite(tokenScore)) return null;
|
|
688
|
+
score += tokenScore;
|
|
689
|
+
}
|
|
690
|
+
return score;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function codexSkillItemHtml(item, index) {
|
|
694
|
+
const selected = selectedCodexSkill?.name === item.name && selectedCodexSkill?.path === item.path;
|
|
695
|
+
const displayName = codexSkillDisplayName(item);
|
|
696
|
+
const description = codexSkillDescription(item);
|
|
697
|
+
const source = codexSkillSource(item);
|
|
698
|
+
const fullName = String(item.name || displayName);
|
|
699
|
+
const directory = codexSkillDirectory(item);
|
|
700
|
+
return `<button type="button" class="codex-skill-item${selected ? ' selected' : ''}" data-codex-skill-path="${escapeHtml(item.path || '')}" title="${escapeHtml(item.path || displayName)}" onclick="selectCodexSkill(${index})"><span class="codex-skill-item-name"><span>${escapeHtml(fullName)}</span><span class="codex-skill-source">${escapeHtml(source.label)}</span></span>${description ? `<span class="codex-skill-description">${escapeHtml(description)}</span>` : ''}<span class="codex-skill-meta"><span class="codex-skill-directory" title="${escapeHtml(directory)}">Dir · ${escapeHtml(directory)}</span></span></button>`;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function codexSkillSectionHtml(title, entries) {
|
|
704
|
+
if (!entries.length) return '';
|
|
705
|
+
return `<section class="codex-skill-section"><div class="codex-skill-section-title"><span>${escapeHtml(title)}</span><span>${entries.length}</span></div>${entries.map(entry => codexSkillItemHtml(entry.item, entry.index)).join('')}</section>`;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function codexSkillResultsHtml() {
|
|
709
|
+
const entries = codexSkillItems.map((item, index) => ({ item, index }));
|
|
710
|
+
const query = codexSkillQuery.trim();
|
|
711
|
+
let sections = '';
|
|
712
|
+
if (query) {
|
|
713
|
+
const matches = entries.map(entry => ({ ...entry, score: scoreCodexSkillSearch(entry.item, query) }))
|
|
714
|
+
.filter(entry => entry.score != null)
|
|
715
|
+
.sort((left, right) => left.score - right.score || compareCodexSkills(left.item, right.item));
|
|
716
|
+
sections = codexSkillSectionHtml('Search results', matches);
|
|
717
|
+
} else {
|
|
718
|
+
const byIdentity = new Map(entries.map(entry => [codexSkillIdentity(entry.item), entry]));
|
|
719
|
+
const recent = readRecentCodexSkills().map(item => byIdentity.get(codexSkillIdentity(item))).filter(Boolean);
|
|
720
|
+
const recentKeys = new Set(recent.map(entry => codexSkillIdentity(entry.item)));
|
|
721
|
+
const remaining = entries.filter(entry => !recentKeys.has(codexSkillIdentity(entry.item)));
|
|
722
|
+
const group = name => remaining.filter(entry => codexSkillSource(entry.item).group === name)
|
|
723
|
+
.sort((left, right) => compareCodexSkills(left.item, right.item));
|
|
724
|
+
sections = codexSkillSectionHtml('Recently used', recent)
|
|
725
|
+
+ codexSkillSectionHtml('Current project', group('project'))
|
|
726
|
+
+ codexSkillSectionHtml('Personal', group('personal'))
|
|
727
|
+
+ codexSkillSectionHtml('Other', group('other'));
|
|
728
|
+
}
|
|
729
|
+
const emptyText = query ? 'No skills match your search.' : 'No enabled skills found for this folder.';
|
|
730
|
+
const empty = !sections && !codexSkillLoading && !codexSkillError
|
|
731
|
+
? `<div class="claude-resume-meta codex-skill-empty">${emptyText}</div>` : '';
|
|
732
|
+
return `${codexSkillError ? `<div class="claude-resume-meta codex-skill-error">${escapeHtml(codexSkillError)}</div>` : ''}${sections}${empty}`;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function renderCodexSkillPanel(focusSearch = false) {
|
|
607
736
|
const panel = document.getElementById('codex-skill-panel');
|
|
608
737
|
if (!panel) return;
|
|
609
738
|
panel.classList.toggle('active', codexSkillPanelOpen);
|
|
610
739
|
if (!codexSkillPanelOpen) { panel.innerHTML = ''; return; }
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
740
|
+
panel.innerHTML = `<div class="codex-skill-header"><div class="codex-skill-header-row"><span>Available skills</span><span>${codexSkillLoading ? 'Loading…' : codexSkillItems.length}</span></div><input id="codex-skill-search" class="codex-skill-search" type="search" aria-label="Search skills" placeholder="Search skills…" value="${escapeHtml(codexSkillQuery)}" oninput="updateCodexSkillQuery(this.value)" autocomplete="off" spellcheck="false"></div><div id="codex-skill-results">${codexSkillResultsHtml()}</div>`;
|
|
741
|
+
if (focusSearch) {
|
|
742
|
+
requestAnimationFrame(() => {
|
|
743
|
+
const search = document.getElementById('codex-skill-search');
|
|
744
|
+
if (!search) return;
|
|
745
|
+
search.focus({ preventScroll: true });
|
|
746
|
+
search.setSelectionRange(search.value.length, search.value.length);
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
updateTerminalControlsHeight();
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
function updateCodexSkillQuery(value) {
|
|
753
|
+
codexSkillQuery = String(value || '');
|
|
754
|
+
const results = document.getElementById('codex-skill-results');
|
|
755
|
+
if (results) results.innerHTML = codexSkillResultsHtml();
|
|
756
|
+
else renderCodexSkillPanel(true);
|
|
619
757
|
updateTerminalControlsHeight();
|
|
620
758
|
}
|
|
621
759
|
|
|
@@ -636,18 +774,22 @@
|
|
|
636
774
|
return;
|
|
637
775
|
}
|
|
638
776
|
codexSkillLoading = true;
|
|
777
|
+
codexSkillQuery = '';
|
|
778
|
+
codexSkillError = '';
|
|
639
779
|
codexSkillItems = [];
|
|
640
|
-
renderCodexSkillPanel();
|
|
780
|
+
renderCodexSkillPanel(true);
|
|
641
781
|
try {
|
|
642
782
|
const res = await fetchWithTimeout(`/api/sessions/${activeSessionId}/codex-skills?forceReload=true`, {}, 30000);
|
|
643
783
|
const data = await res.json();
|
|
644
784
|
if (!res.ok || !data.success) throw new Error(data.error || 'Failed to load skills');
|
|
645
785
|
codexSkillItems = data.skills || [];
|
|
646
786
|
codexSkillLoading = false;
|
|
647
|
-
|
|
787
|
+
codexSkillError = (data.errors || []).map(item => item.message || String(item)).filter(Boolean).join(' · ');
|
|
788
|
+
renderCodexSkillPanel(true);
|
|
648
789
|
} catch (error) {
|
|
649
790
|
codexSkillLoading = false;
|
|
650
|
-
|
|
791
|
+
codexSkillError = error.message;
|
|
792
|
+
renderCodexSkillPanel(true);
|
|
651
793
|
}
|
|
652
794
|
}
|
|
653
795
|
|
|
@@ -656,6 +798,7 @@
|
|
|
656
798
|
if (!skill) return;
|
|
657
799
|
const alreadySelected = selectedCodexSkill?.name === skill.name && selectedCodexSkill?.path === skill.path;
|
|
658
800
|
selectedCodexSkill = alreadySelected ? null : { name: skill.name, path: skill.path };
|
|
801
|
+
if (selectedCodexSkill) rememberCodexSkill(skill);
|
|
659
802
|
codexSkillPanelOpen = false;
|
|
660
803
|
renderCodexSkillPanel();
|
|
661
804
|
applyCodexState({});
|
package/lib/web/core.js
CHANGED
package/lib/web/session.js
CHANGED
|
@@ -140,6 +140,8 @@
|
|
|
140
140
|
codexSkillPanelOpen = false;
|
|
141
141
|
codexSkillItems = [];
|
|
142
142
|
codexSkillLoading = false;
|
|
143
|
+
codexSkillQuery = '';
|
|
144
|
+
codexSkillError = '';
|
|
143
145
|
selectedCodexSkill = null;
|
|
144
146
|
document.getElementById('codex-model-panel').classList.remove('active');
|
|
145
147
|
document.getElementById('codex-resume-panel').classList.remove('active');
|
package/lib/web/styles.css
CHANGED
|
@@ -193,12 +193,22 @@
|
|
|
193
193
|
#codex-resume-panel, #codex-fork-panel { max-height: min(280px, 38dvh); overflow-y: auto; }
|
|
194
194
|
#codex-prompt-panel { max-height: min(440px, 52dvh); overflow-y: auto; }
|
|
195
195
|
#codex-skill-panel { max-height: min(360px, 46dvh); overflow-y: auto; }
|
|
196
|
-
.codex-skill-header { position: sticky; top: 0; z-index:
|
|
196
|
+
.codex-skill-header { position: sticky; top: 0; z-index: 3; padding: 9px 11px 10px; border-bottom: 1px solid rgba(255,255,255,.08); background: rgba(28,28,30,.97); color: var(--text-dim); font-size: 11px; font-weight: 800; }
|
|
197
|
+
.codex-skill-header-row, .codex-skill-section-title { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
|
198
|
+
.codex-skill-search { width: 100%; height: 34px; margin-top: 8px; padding: 0 11px; border: 1px solid rgba(255,255,255,.12); border-radius: 9px; outline: none; background: rgba(255,255,255,.07); color: #f5f5f7; font-size: 12px; box-sizing: border-box; }
|
|
199
|
+
.codex-skill-search::placeholder { color: #77777e; }
|
|
200
|
+
.codex-skill-search:focus { border-color: rgba(0,122,255,.65); background: rgba(255,255,255,.1); box-shadow: 0 0 0 2px rgba(0,122,255,.14); }
|
|
201
|
+
.codex-skill-section + .codex-skill-section { border-top: 1px solid rgba(255,255,255,.09); }
|
|
202
|
+
.codex-skill-section-title { position: sticky; top: 62px; z-index: 2; min-height: 26px; padding: 0 11px; background: rgba(36,36,39,.98); color: #8e8e93; font-size: 9px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }
|
|
197
203
|
.codex-skill-item { display: block; width: 100%; padding: 10px 11px; border: 0; border-bottom: 1px solid rgba(255,255,255,.06); background: transparent; color: #f5f5f7; text-align: left; cursor: pointer; }
|
|
198
204
|
.codex-skill-item.selected { background: rgba(0,122,255,.18); }
|
|
199
205
|
.codex-skill-item-name { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 12px; font-weight: 800; }
|
|
200
|
-
.codex-skill-
|
|
206
|
+
.codex-skill-source { flex: 0 0 auto; padding: 2px 6px; border: 1px solid rgba(0,122,255,.28); border-radius: 999px; background: rgba(0,122,255,.1); color: #79b8ff; font-size: 9px; text-transform: uppercase; }
|
|
201
207
|
.codex-skill-description { display: block; margin-top: 4px; color: #a9a9b0; font-size: 11px; line-height: 1.35; white-space: normal; overflow-wrap: anywhere; }
|
|
208
|
+
.codex-skill-meta { display: flex; min-width: 0; margin-top: 7px; gap: 5px; flex-wrap: wrap; }
|
|
209
|
+
.codex-skill-directory { display: block; min-width: 0; max-width: 100%; flex: 1 1 180px; padding: 2px 6px; overflow: hidden; border: 1px solid rgba(255,255,255,.1); border-radius: 999px; background: rgba(255,255,255,.055); color: #8e8e93; font: 9px/1.25 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; text-overflow: ellipsis; white-space: nowrap; box-sizing: border-box; }
|
|
210
|
+
.codex-skill-empty, .codex-skill-error { padding: 12px; }
|
|
211
|
+
.codex-skill-error { color: #ff6b61; }
|
|
202
212
|
.codex-prompt-header { position: sticky; top: 0; z-index: 2; display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 11px; border-bottom: 1px solid rgba(255,255,255,.08); background: rgba(28,28,30,.97); color: var(--text-dim); font-size: 11px; font-weight: 800; }
|
|
203
213
|
.codex-prompt-item { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; align-items: end; padding: 10px 11px; border-bottom: 1px solid rgba(255,255,255,.06); }
|
|
204
214
|
.codex-prompt-item:last-of-type { border-bottom: 0; }
|