@jungjaehoon/mama-os 0.8.3 → 0.9.0
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/CHANGELOG.md +10 -0
- package/dist/agent/agent-loop.d.ts +1 -8
- package/dist/agent/agent-loop.d.ts.map +1 -1
- package/dist/agent/agent-loop.js +44 -159
- package/dist/agent/agent-loop.js.map +1 -1
- package/dist/agent/claude-cli-wrapper.d.ts +6 -0
- package/dist/agent/claude-cli-wrapper.d.ts.map +1 -1
- package/dist/agent/claude-cli-wrapper.js +6 -0
- package/dist/agent/claude-cli-wrapper.js.map +1 -1
- package/dist/agent/codex-mcp-process.d.ts +85 -0
- package/dist/agent/codex-mcp-process.d.ts.map +1 -0
- package/dist/agent/codex-mcp-process.js +357 -0
- package/dist/agent/codex-mcp-process.js.map +1 -0
- package/dist/agent/session-pool.d.ts +17 -2
- package/dist/agent/session-pool.d.ts.map +1 -1
- package/dist/agent/session-pool.js +51 -26
- package/dist/agent/session-pool.js.map +1 -1
- package/dist/agent/types.d.ts +9 -24
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/api/graph-api.d.ts.map +1 -1
- package/dist/api/graph-api.js +133 -45
- package/dist/api/graph-api.js.map +1 -1
- package/dist/cli/commands/init.d.ts +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +14 -25
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/run.d.ts.map +1 -1
- package/dist/cli/commands/run.js +3 -10
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +143 -54
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +2 -7
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/config/config-manager.d.ts.map +1 -1
- package/dist/cli/config/config-manager.js +9 -17
- package/dist/cli/config/config-manager.js.map +1 -1
- package/dist/cli/config/types.d.ts +19 -25
- package/dist/cli/config/types.d.ts.map +1 -1
- package/dist/cli/config/types.js.map +1 -1
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/gateways/context-injector.d.ts.map +1 -1
- package/dist/gateways/context-injector.js +6 -3
- package/dist/gateways/context-injector.js.map +1 -1
- package/dist/gateways/discord.d.ts +4 -0
- package/dist/gateways/discord.d.ts.map +1 -1
- package/dist/gateways/discord.js +39 -16
- package/dist/gateways/discord.js.map +1 -1
- package/dist/gateways/message-router.d.ts +6 -1
- package/dist/gateways/message-router.d.ts.map +1 -1
- package/dist/gateways/message-router.js +92 -7
- package/dist/gateways/message-router.js.map +1 -1
- package/dist/multi-agent/agent-process-manager.d.ts.map +1 -1
- package/dist/multi-agent/agent-process-manager.js +36 -9
- package/dist/multi-agent/agent-process-manager.js.map +1 -1
- package/dist/multi-agent/runtime-process.d.ts +4 -4
- package/dist/multi-agent/runtime-process.d.ts.map +1 -1
- package/dist/multi-agent/runtime-process.js +9 -20
- package/dist/multi-agent/runtime-process.js.map +1 -1
- package/dist/multi-agent/types.d.ts +13 -8
- package/dist/multi-agent/types.d.ts.map +1 -1
- package/dist/multi-agent/types.js.map +1 -1
- package/dist/setup/setup-prompt.d.ts +1 -1
- package/dist/setup/setup-prompt.d.ts.map +1 -1
- package/dist/setup/setup-prompt.js +19 -0
- package/dist/setup/setup-prompt.js.map +1 -1
- package/dist/setup/setup-server.d.ts.map +1 -1
- package/dist/setup/setup-server.js +39 -16
- package/dist/setup/setup-server.js.map +1 -1
- package/dist/skills/skill-registry.d.ts.map +1 -1
- package/dist/skills/skill-registry.js +5 -2
- package/dist/skills/skill-registry.js.map +1 -1
- package/package.json +5 -3
- package/public/setup.html +12 -1
- package/public/viewer/js/modules/chat.js +1760 -1976
- package/public/viewer/js/modules/dashboard.js +613 -695
- package/public/viewer/js/modules/graph.js +857 -970
- package/public/viewer/js/modules/memory.js +357 -312
- package/public/viewer/js/modules/settings.js +1009 -1026
- package/public/viewer/js/modules/skills.js +336 -355
- package/public/viewer/js/utils/api.js +255 -255
- package/public/viewer/js/utils/debug-logger.js +20 -26
- package/public/viewer/js/utils/dom.js +73 -60
- package/public/viewer/js/utils/format.js +182 -228
- package/public/viewer/js/utils/markdown.js +40 -0
- package/public/viewer/src/modules/chat.ts +2258 -0
- package/public/viewer/src/modules/dashboard.ts +1052 -0
- package/public/viewer/src/modules/graph.ts +1080 -0
- package/public/viewer/src/modules/memory.ts +453 -0
- package/public/viewer/src/modules/settings.ts +1398 -0
- package/public/viewer/src/modules/skills.ts +457 -0
- package/public/viewer/src/types/global.d.ts +168 -0
- package/public/viewer/src/utils/api.ts +650 -0
- package/public/viewer/src/utils/debug-logger.ts +36 -0
- package/public/viewer/src/utils/dom.ts +138 -0
- package/public/viewer/src/utils/format.ts +331 -0
- package/public/viewer/src/utils/markdown.ts +46 -0
- package/public/viewer/tsconfig.viewer.json +18 -0
- package/public/viewer/viewer.html +214 -311
- package/dist/agent/codex-cli-wrapper.d.ts +0 -85
- package/dist/agent/codex-cli-wrapper.d.ts.map +0 -1
- package/dist/agent/codex-cli-wrapper.js +0 -295
- package/dist/agent/codex-cli-wrapper.js.map +0 -1
|
@@ -6,138 +6,143 @@
|
|
|
6
6
|
* Manages skill browsing, installation, and configuration
|
|
7
7
|
* across MAMA, Cowork, and OpenClaw sources.
|
|
8
8
|
*/
|
|
9
|
-
|
|
10
9
|
/* eslint-env browser */
|
|
11
|
-
/* global marked */
|
|
12
|
-
|
|
13
10
|
import { API } from '../utils/api.js';
|
|
14
11
|
import { DebugLogger } from '../utils/debug-logger.js';
|
|
15
|
-
|
|
12
|
+
import { getElementByIdOrNull } from '../utils/dom.js';
|
|
13
|
+
import { renderSafeMarkdown } from '../utils/markdown.js';
|
|
16
14
|
const logger = new DebugLogger('Skills');
|
|
17
|
-
|
|
18
15
|
/**
|
|
19
16
|
* Skills marketplace module
|
|
20
17
|
*/
|
|
21
18
|
export const SkillsModule = {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
this._initialized = true;
|
|
41
|
-
}
|
|
42
|
-
await this.loadSkills();
|
|
43
|
-
this.render();
|
|
44
|
-
},
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Load installed + catalog skills
|
|
48
|
-
*/
|
|
49
|
-
async loadSkills() {
|
|
50
|
-
try {
|
|
51
|
-
const [installedRes, catalogRes] = await Promise.all([
|
|
52
|
-
API.getSkills().catch(() => ({ skills: [] })),
|
|
53
|
-
API.getSkillCatalog('all').catch(() => ({ skills: [] })),
|
|
54
|
-
]);
|
|
55
|
-
|
|
56
|
-
this.installed = installedRes.skills || [];
|
|
57
|
-
this.catalog = (catalogRes.skills || []).filter(
|
|
58
|
-
(s) => !this.installed.some((i) => i.id === s.id && i.source === s.source)
|
|
59
|
-
);
|
|
60
|
-
} catch (error) {
|
|
61
|
-
logger.error('Failed to load:', error);
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Bind UI events
|
|
67
|
-
*/
|
|
68
|
-
_bindEvents() {
|
|
69
|
-
// Search input
|
|
70
|
-
const searchInput = document.getElementById('skills-search');
|
|
71
|
-
if (searchInput) {
|
|
72
|
-
searchInput.addEventListener('input', (e) => {
|
|
73
|
-
clearTimeout(this._searchTimer);
|
|
74
|
-
this._searchTimer = setTimeout(() => {
|
|
75
|
-
this.searchQuery = e.target.value.trim();
|
|
76
|
-
this.render();
|
|
77
|
-
}, 300);
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// URL install
|
|
82
|
-
const urlBtn = document.getElementById('skills-url-install-btn');
|
|
83
|
-
if (urlBtn) {
|
|
84
|
-
urlBtn.addEventListener('click', () => {
|
|
85
|
-
const input = document.getElementById('skills-url-input');
|
|
86
|
-
const url = input?.value?.trim();
|
|
87
|
-
if (url) {
|
|
88
|
-
this.installFromUrl(url);
|
|
19
|
+
/** All skills (installed + catalog) */
|
|
20
|
+
installed: [],
|
|
21
|
+
catalog: [],
|
|
22
|
+
/** Current filter */
|
|
23
|
+
currentFilter: 'all',
|
|
24
|
+
/** Search query */
|
|
25
|
+
searchQuery: '',
|
|
26
|
+
/** Whether initialized */
|
|
27
|
+
_initialized: false,
|
|
28
|
+
/** Debounce timer */
|
|
29
|
+
_searchTimer: null,
|
|
30
|
+
/**
|
|
31
|
+
* Initialize the skills tab
|
|
32
|
+
*/
|
|
33
|
+
async init() {
|
|
34
|
+
if (!this._initialized) {
|
|
35
|
+
this._bindEvents();
|
|
36
|
+
this._initialized = true;
|
|
89
37
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
38
|
+
await this.loadSkills();
|
|
39
|
+
this.render();
|
|
40
|
+
},
|
|
41
|
+
/**
|
|
42
|
+
* Load installed + catalog skills
|
|
43
|
+
*/
|
|
44
|
+
async loadSkills() {
|
|
45
|
+
try {
|
|
46
|
+
const [installedResponse, catalogResponse] = await Promise.all([
|
|
47
|
+
API.getSkills(),
|
|
48
|
+
API.getSkillCatalog('all'),
|
|
49
|
+
]);
|
|
50
|
+
this.installed = installedResponse.skills || [];
|
|
51
|
+
const catalogItems = catalogResponse.skills || [];
|
|
52
|
+
const installedMap = new Set(this.installed.map((item) => `${item.source}::${item.id}`));
|
|
53
|
+
this.catalog = catalogItems.filter((s) => !installedMap.has(`${s.source}::${s.id}`));
|
|
100
54
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
filterBar.addEventListener('click', (e) => {
|
|
108
|
-
const btn = e.target.closest('[data-filter]');
|
|
109
|
-
if (!btn) {
|
|
110
|
-
return;
|
|
55
|
+
catch (error) {
|
|
56
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
57
|
+
logger.error('Unexpected error while loading skills:', message);
|
|
58
|
+
// Initialize with empty arrays on failure
|
|
59
|
+
this.installed = [];
|
|
60
|
+
this.catalog = [];
|
|
111
61
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
62
|
+
},
|
|
63
|
+
/**
|
|
64
|
+
* Bind UI events
|
|
65
|
+
*/
|
|
66
|
+
_bindEvents() {
|
|
67
|
+
// Search input
|
|
68
|
+
const searchInput = getElementByIdOrNull('skills-search');
|
|
69
|
+
if (searchInput) {
|
|
70
|
+
searchInput.addEventListener('input', (e) => {
|
|
71
|
+
clearTimeout(this._searchTimer);
|
|
72
|
+
this._searchTimer = setTimeout(() => {
|
|
73
|
+
const target = e.target;
|
|
74
|
+
if (!(target instanceof HTMLInputElement)) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
this.searchQuery = target.value.trim();
|
|
78
|
+
this.render();
|
|
79
|
+
}, 300);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// URL install
|
|
83
|
+
const urlBtn = getElementByIdOrNull('skills-url-install-btn');
|
|
84
|
+
if (urlBtn) {
|
|
85
|
+
urlBtn.addEventListener('click', () => {
|
|
86
|
+
const input = getElementByIdOrNull('skills-url-input');
|
|
87
|
+
const url = input?.value?.trim();
|
|
88
|
+
if (url) {
|
|
89
|
+
this.installFromUrl(url);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
const urlInput = getElementByIdOrNull('skills-url-input');
|
|
94
|
+
if (urlInput) {
|
|
95
|
+
urlInput.addEventListener('keydown', (e) => {
|
|
96
|
+
if (e.key === 'Enter') {
|
|
97
|
+
const target = e.target;
|
|
98
|
+
if (!(target instanceof HTMLInputElement)) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const url = target.value.trim();
|
|
102
|
+
if (url) {
|
|
103
|
+
this.installFromUrl(url);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
// Filter buttons
|
|
109
|
+
const filterBar = getElementByIdOrNull('skills-filter-bar');
|
|
110
|
+
if (filterBar) {
|
|
111
|
+
filterBar.addEventListener('click', (e) => {
|
|
112
|
+
const target = e.target;
|
|
113
|
+
const btn = target instanceof HTMLElement ? target.closest('[data-filter]') : null;
|
|
114
|
+
if (!btn) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const filter = btn.dataset.filter;
|
|
118
|
+
if (!filter) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
this.currentFilter = filter;
|
|
122
|
+
// Update active state
|
|
123
|
+
filterBar.querySelectorAll('[data-filter]').forEach((b) => {
|
|
124
|
+
b.classList.toggle('bg-yellow-400', b.dataset.filter === this.currentFilter);
|
|
125
|
+
b.classList.toggle('text-gray-900', b.dataset.filter === this.currentFilter);
|
|
126
|
+
b.classList.toggle('bg-gray-700', b.dataset.filter !== this.currentFilter);
|
|
127
|
+
b.classList.toggle('text-gray-300', b.dataset.filter !== this.currentFilter);
|
|
128
|
+
});
|
|
129
|
+
this.render();
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
/**
|
|
134
|
+
* Render the full skills view
|
|
135
|
+
*/
|
|
136
|
+
render() {
|
|
137
|
+
const container = getElementByIdOrNull('skills-content');
|
|
138
|
+
if (!container) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const installed = this._filterSkills(this.installed);
|
|
142
|
+
const available = this._filterSkills(this.catalog);
|
|
143
|
+
container.innerHTML = `
|
|
144
|
+
${installed.length > 0
|
|
145
|
+
? `
|
|
141
146
|
<div class="mb-6">
|
|
142
147
|
<h3 class="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-3">
|
|
143
148
|
Installed (${installed.length})
|
|
@@ -147,15 +152,13 @@ export const SkillsModule = {
|
|
|
147
152
|
</div>
|
|
148
153
|
</div>
|
|
149
154
|
`
|
|
150
|
-
|
|
151
|
-
}
|
|
155
|
+
: ''}
|
|
152
156
|
|
|
153
157
|
<div class="mb-6">
|
|
154
158
|
<h3 class="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-3">
|
|
155
159
|
Available (${available.length})
|
|
156
160
|
</h3>
|
|
157
|
-
${
|
|
158
|
-
available.length > 0
|
|
161
|
+
${available.length > 0
|
|
159
162
|
? `
|
|
160
163
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3">
|
|
161
164
|
${available.map((s) => this._renderCard(s, false)).join('')}
|
|
@@ -165,74 +168,72 @@ export const SkillsModule = {
|
|
|
165
168
|
<p class="text-gray-500 text-sm">
|
|
166
169
|
${this.searchQuery ? 'No skills match your search.' : 'Loading catalog...'}
|
|
167
170
|
</p>
|
|
168
|
-
`
|
|
169
|
-
}
|
|
171
|
+
`}
|
|
170
172
|
</div>
|
|
171
173
|
`;
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
174
|
+
// Bind card actions
|
|
175
|
+
container.querySelectorAll('[data-action]').forEach((btn) => {
|
|
176
|
+
btn.addEventListener('click', (e) => {
|
|
177
|
+
e.stopPropagation();
|
|
178
|
+
const action = btn.dataset.action;
|
|
179
|
+
const id = btn.dataset.id;
|
|
180
|
+
const source = btn.dataset.source;
|
|
181
|
+
if (!action || !id || !source) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (action === 'install') {
|
|
185
|
+
this.install(source, id);
|
|
186
|
+
}
|
|
187
|
+
else if (action === 'uninstall') {
|
|
188
|
+
this.uninstall(source, id);
|
|
189
|
+
}
|
|
190
|
+
else if (action === 'toggle') {
|
|
191
|
+
this.toggle(source, id, btn.dataset.enabled !== 'true');
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
// Bind card click for detail
|
|
196
|
+
container.querySelectorAll('[data-skill-card]').forEach((card) => {
|
|
197
|
+
card.addEventListener('click', () => {
|
|
198
|
+
const source = card.dataset.source;
|
|
199
|
+
const id = card.dataset.id;
|
|
200
|
+
if (!source || !id) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
this.showDetail(source, id);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
},
|
|
207
|
+
/**
|
|
208
|
+
* Filter skills by current filter + search query
|
|
209
|
+
*/
|
|
210
|
+
_filterSkills(skills) {
|
|
211
|
+
let filtered = skills;
|
|
212
|
+
// Source filter
|
|
213
|
+
if (this.currentFilter !== 'all' && this.currentFilter !== 'installed') {
|
|
214
|
+
filtered = filtered.filter((s) => s.source === this.currentFilter);
|
|
186
215
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
// Search filter
|
|
210
|
-
if (this.searchQuery) {
|
|
211
|
-
const q = this.searchQuery.toLowerCase();
|
|
212
|
-
filtered = filtered.filter(
|
|
213
|
-
(s) =>
|
|
214
|
-
s.name.toLowerCase().includes(q) ||
|
|
215
|
-
s.description.toLowerCase().includes(q) ||
|
|
216
|
-
s.id.toLowerCase().includes(q)
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return filtered;
|
|
221
|
-
},
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Render a single skill card
|
|
225
|
-
*/
|
|
226
|
-
_renderCard(skill, isInstalled) {
|
|
227
|
-
const sourceColors = {
|
|
228
|
-
mama: 'bg-yellow-900/30 text-yellow-400',
|
|
229
|
-
cowork: 'bg-blue-900/30 text-blue-400',
|
|
230
|
-
external: 'bg-purple-900/30 text-purple-400',
|
|
231
|
-
};
|
|
232
|
-
const badgeClass = sourceColors[skill.source] || 'bg-gray-700 text-gray-400';
|
|
233
|
-
const enabledClass = skill.enabled !== false ? 'border-green-500/30' : 'border-gray-700';
|
|
234
|
-
|
|
235
|
-
return `
|
|
216
|
+
// Search filter
|
|
217
|
+
if (this.searchQuery) {
|
|
218
|
+
const q = this.searchQuery.toLowerCase();
|
|
219
|
+
filtered = filtered.filter((s) => s.name.toLowerCase().includes(q) ||
|
|
220
|
+
(s.description || '').toLowerCase().includes(q) ||
|
|
221
|
+
s.id.toLowerCase().includes(q));
|
|
222
|
+
}
|
|
223
|
+
return filtered;
|
|
224
|
+
},
|
|
225
|
+
/**
|
|
226
|
+
* Render a single skill card
|
|
227
|
+
*/
|
|
228
|
+
_renderCard(skill, isInstalled) {
|
|
229
|
+
const sourceColors = {
|
|
230
|
+
mama: 'bg-yellow-900/30 text-yellow-400',
|
|
231
|
+
cowork: 'bg-blue-900/30 text-blue-400',
|
|
232
|
+
external: 'bg-purple-900/30 text-purple-400',
|
|
233
|
+
};
|
|
234
|
+
const badgeClass = sourceColors[skill.source] || 'bg-gray-700 text-gray-400';
|
|
235
|
+
const enabledClass = skill.enabled !== false ? 'border-green-500/30' : 'border-gray-700';
|
|
236
|
+
return `
|
|
236
237
|
<div class="bg-gray-800 rounded-lg border ${enabledClass} p-3 cursor-pointer
|
|
237
238
|
hover:border-yellow-500/50 transition-colors"
|
|
238
239
|
data-skill-card data-id="${this._escapeHtml(skill.id)}" data-source="${this._escapeHtml(skill.source)}">
|
|
@@ -242,11 +243,10 @@ export const SkillsModule = {
|
|
|
242
243
|
${this._escapeHtml(skill.source)}
|
|
243
244
|
</span>
|
|
244
245
|
</div>
|
|
245
|
-
<p class="text-xs text-gray-400 line-clamp-2 mb-3">${this._escapeHtml(skill.description)}</p>
|
|
246
|
+
<p class="text-xs text-gray-400 line-clamp-2 mb-3">${this._escapeHtml(skill.description || '')}</p>
|
|
246
247
|
<div class="flex items-center justify-between">
|
|
247
|
-
${
|
|
248
|
-
|
|
249
|
-
? `
|
|
248
|
+
${isInstalled
|
|
249
|
+
? `
|
|
250
250
|
<button data-action="toggle" data-id="${this._escapeHtml(skill.id)}" data-source="${this._escapeHtml(skill.source)}"
|
|
251
251
|
data-enabled="${skill.enabled !== false}"
|
|
252
252
|
class="text-xs px-2 py-1 rounded ${skill.enabled !== false ? 'bg-green-900/30 text-green-400' : 'bg-gray-700 text-gray-400'}">
|
|
@@ -257,119 +257,115 @@ export const SkillsModule = {
|
|
|
257
257
|
Remove
|
|
258
258
|
</button>
|
|
259
259
|
`
|
|
260
|
-
|
|
260
|
+
: `
|
|
261
261
|
<span></span>
|
|
262
262
|
<button data-action="install" data-id="${this._escapeHtml(skill.id)}" data-source="${this._escapeHtml(skill.source)}"
|
|
263
263
|
class="text-xs px-2 py-1 rounded bg-yellow-900/30 text-yellow-400 hover:bg-yellow-900/50">
|
|
264
264
|
Install
|
|
265
265
|
</button>
|
|
266
|
-
`
|
|
267
|
-
}
|
|
266
|
+
`}
|
|
268
267
|
</div>
|
|
269
268
|
</div>
|
|
270
269
|
`;
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
try {
|
|
371
|
-
const { content } = await API.getSkillContent(name, source);
|
|
372
|
-
modalContent.innerHTML = `
|
|
270
|
+
},
|
|
271
|
+
/**
|
|
272
|
+
* Install a skill
|
|
273
|
+
*/
|
|
274
|
+
async install(source, name) {
|
|
275
|
+
try {
|
|
276
|
+
const btn = document.querySelector(`[data-action="install"][data-id="${CSS.escape(name)}"][data-source="${CSS.escape(source)}"]`);
|
|
277
|
+
if (btn) {
|
|
278
|
+
btn.textContent = 'Installing...';
|
|
279
|
+
btn.disabled = true;
|
|
280
|
+
}
|
|
281
|
+
await API.installSkill(source, name);
|
|
282
|
+
await this.loadSkills();
|
|
283
|
+
this.render();
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
287
|
+
logger.error('Install failed:', message);
|
|
288
|
+
alert(`Failed to install ${name}: ${message}`);
|
|
289
|
+
this.render();
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
/**
|
|
293
|
+
* Install from GitHub URL
|
|
294
|
+
*/
|
|
295
|
+
async installFromUrl(url) {
|
|
296
|
+
const btn = getElementByIdOrNull('skills-url-install-btn');
|
|
297
|
+
const input = getElementByIdOrNull('skills-url-input');
|
|
298
|
+
try {
|
|
299
|
+
if (btn) {
|
|
300
|
+
btn.textContent = 'Installing...';
|
|
301
|
+
btn.disabled = true;
|
|
302
|
+
}
|
|
303
|
+
await API.installSkillFromUrl(url);
|
|
304
|
+
if (input) {
|
|
305
|
+
input.value = '';
|
|
306
|
+
}
|
|
307
|
+
await this.loadSkills();
|
|
308
|
+
this.render();
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
312
|
+
logger.error('URL install failed:', message);
|
|
313
|
+
alert(`Failed to install from URL: ${message}`);
|
|
314
|
+
}
|
|
315
|
+
finally {
|
|
316
|
+
if (btn) {
|
|
317
|
+
btn.textContent = 'Install URL';
|
|
318
|
+
btn.disabled = false;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
/**
|
|
323
|
+
* Uninstall a skill
|
|
324
|
+
*/
|
|
325
|
+
async uninstall(source, name) {
|
|
326
|
+
if (!confirm(`Remove skill "${name}"?`)) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
try {
|
|
330
|
+
await API.uninstallSkill(name, source);
|
|
331
|
+
await this.loadSkills();
|
|
332
|
+
this.render();
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
logger.error('Uninstall failed:', error instanceof Error ? error.message : String(error));
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
/**
|
|
339
|
+
* Toggle skill enabled/disabled
|
|
340
|
+
*/
|
|
341
|
+
async toggle(source, name, enabled) {
|
|
342
|
+
try {
|
|
343
|
+
await API.toggleSkill(name, enabled, source);
|
|
344
|
+
// Update local state
|
|
345
|
+
const skill = this.installed.find((s) => s.id === name && s.source === source);
|
|
346
|
+
if (skill) {
|
|
347
|
+
skill.enabled = enabled;
|
|
348
|
+
}
|
|
349
|
+
this.render();
|
|
350
|
+
}
|
|
351
|
+
catch (error) {
|
|
352
|
+
logger.error('Toggle failed:', error instanceof Error ? error.message : String(error));
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
/**
|
|
356
|
+
* Show skill detail modal
|
|
357
|
+
*/
|
|
358
|
+
async showDetail(source, name) {
|
|
359
|
+
const modal = getElementByIdOrNull('skill-detail-modal');
|
|
360
|
+
const modalContent = getElementByIdOrNull('skill-detail-content');
|
|
361
|
+
if (!modal || !modalContent) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
modalContent.innerHTML = '<p class="text-gray-400">Loading...</p>';
|
|
365
|
+
modal.classList.remove('hidden');
|
|
366
|
+
try {
|
|
367
|
+
const { content } = await API.getSkillContent(name, source);
|
|
368
|
+
modalContent.innerHTML = `
|
|
373
369
|
<div class="flex items-center justify-between mb-4">
|
|
374
370
|
<h2 class="text-lg font-bold text-white">${this._escapeHtml(name)}</h2>
|
|
375
371
|
<span class="text-xs px-2 py-1 rounded bg-gray-700 text-gray-400">${this._escapeHtml(source)}</span>
|
|
@@ -378,60 +374,45 @@ export const SkillsModule = {
|
|
|
378
374
|
${this._renderMarkdown(content)}
|
|
379
375
|
</div>
|
|
380
376
|
`;
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
if (typeof marked !== 'undefined' && marked.parse) {
|
|
424
|
-
return marked.parse(md, {
|
|
425
|
-
mangle: false,
|
|
426
|
-
headerIds: false,
|
|
427
|
-
// Sanitize any HTML in the markdown content
|
|
428
|
-
sanitize: true,
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// Fallback if marked.js is not available (should not happen in viewer)
|
|
433
|
-
const div = document.createElement('div');
|
|
434
|
-
div.textContent = md;
|
|
435
|
-
return div.innerHTML;
|
|
436
|
-
},
|
|
377
|
+
}
|
|
378
|
+
catch (error) {
|
|
379
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
380
|
+
modalContent.innerHTML = `<p class="text-red-400">Failed to load: ${this._escapeHtml(message)}</p>`;
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
/**
|
|
384
|
+
* Close detail modal
|
|
385
|
+
*/
|
|
386
|
+
closeDetail() {
|
|
387
|
+
const modal = getElementByIdOrNull('skill-detail-modal');
|
|
388
|
+
if (modal) {
|
|
389
|
+
modal.classList.add('hidden');
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
/**
|
|
393
|
+
* Escape HTML special characters
|
|
394
|
+
*/
|
|
395
|
+
_escapeHtml(unsafe) {
|
|
396
|
+
if (!unsafe) {
|
|
397
|
+
return '';
|
|
398
|
+
}
|
|
399
|
+
return unsafe
|
|
400
|
+
.toString()
|
|
401
|
+
.replace(/&/g, '&')
|
|
402
|
+
.replace(/</g, '<')
|
|
403
|
+
.replace(/>/g, '>')
|
|
404
|
+
.replace(/"/g, '"')
|
|
405
|
+
.replace(/'/g, ''');
|
|
406
|
+
},
|
|
407
|
+
/**
|
|
408
|
+
* Render markdown to HTML using marked.js, with sanitization
|
|
409
|
+
*/
|
|
410
|
+
_renderMarkdown(md) {
|
|
411
|
+
if (!md) {
|
|
412
|
+
return '';
|
|
413
|
+
}
|
|
414
|
+
// Remove frontmatter
|
|
415
|
+
md = md.replace(/^---\n[\s\S]*?\n---\n/, '');
|
|
416
|
+
return renderSafeMarkdown(md);
|
|
417
|
+
},
|
|
437
418
|
};
|