@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
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skills Marketplace Module
|
|
3
|
+
* @module modules/skills
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*
|
|
6
|
+
* Manages skill browsing, installation, and configuration
|
|
7
|
+
* across MAMA, Cowork, and OpenClaw sources.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/* eslint-env browser */
|
|
11
|
+
|
|
12
|
+
import { API, type SkillItem } from '../utils/api.js';
|
|
13
|
+
import { DebugLogger } from '../utils/debug-logger.js';
|
|
14
|
+
import { getElementByIdOrNull } from '../utils/dom.js';
|
|
15
|
+
import { renderSafeMarkdown } from '../utils/markdown.js';
|
|
16
|
+
|
|
17
|
+
const logger = new DebugLogger('Skills');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Skills marketplace module
|
|
21
|
+
*/
|
|
22
|
+
export const SkillsModule = {
|
|
23
|
+
/** All skills (installed + catalog) */
|
|
24
|
+
installed: [] as SkillItem[],
|
|
25
|
+
catalog: [] as SkillItem[],
|
|
26
|
+
/** Current filter */
|
|
27
|
+
currentFilter: 'all',
|
|
28
|
+
/** Search query */
|
|
29
|
+
searchQuery: '',
|
|
30
|
+
/** Whether initialized */
|
|
31
|
+
_initialized: false,
|
|
32
|
+
/** Debounce timer */
|
|
33
|
+
_searchTimer: null as ReturnType<typeof setTimeout> | null,
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Initialize the skills tab
|
|
37
|
+
*/
|
|
38
|
+
async init(): Promise<void> {
|
|
39
|
+
if (!this._initialized) {
|
|
40
|
+
this._bindEvents();
|
|
41
|
+
this._initialized = true;
|
|
42
|
+
}
|
|
43
|
+
await this.loadSkills();
|
|
44
|
+
this.render();
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Load installed + catalog skills
|
|
49
|
+
*/
|
|
50
|
+
async loadSkills(): Promise<void> {
|
|
51
|
+
try {
|
|
52
|
+
const [installedResponse, catalogResponse] = await Promise.all([
|
|
53
|
+
API.getSkills(),
|
|
54
|
+
API.getSkillCatalog('all'),
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
this.installed = installedResponse.skills || [];
|
|
58
|
+
const catalogItems = catalogResponse.skills || [];
|
|
59
|
+
const installedMap = new Set(
|
|
60
|
+
this.installed.map((item: SkillItem) => `${item.source}::${item.id}`)
|
|
61
|
+
);
|
|
62
|
+
this.catalog = catalogItems.filter(
|
|
63
|
+
(s: SkillItem) => !installedMap.has(`${s.source}::${s.id}`)
|
|
64
|
+
);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
67
|
+
logger.error('Unexpected error while loading skills:', message);
|
|
68
|
+
// Initialize with empty arrays on failure
|
|
69
|
+
this.installed = [];
|
|
70
|
+
this.catalog = [];
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Bind UI events
|
|
76
|
+
*/
|
|
77
|
+
_bindEvents(): void {
|
|
78
|
+
// Search input
|
|
79
|
+
const searchInput = getElementByIdOrNull<HTMLInputElement>('skills-search');
|
|
80
|
+
if (searchInput) {
|
|
81
|
+
searchInput.addEventListener('input', (e: Event) => {
|
|
82
|
+
clearTimeout(this._searchTimer);
|
|
83
|
+
this._searchTimer = setTimeout(() => {
|
|
84
|
+
const target = e.target;
|
|
85
|
+
if (!(target instanceof HTMLInputElement)) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
this.searchQuery = target.value.trim();
|
|
89
|
+
this.render();
|
|
90
|
+
}, 300);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// URL install
|
|
95
|
+
const urlBtn = getElementByIdOrNull<HTMLButtonElement>('skills-url-install-btn');
|
|
96
|
+
if (urlBtn) {
|
|
97
|
+
urlBtn.addEventListener('click', () => {
|
|
98
|
+
const input = getElementByIdOrNull<HTMLInputElement>('skills-url-input');
|
|
99
|
+
const url = input?.value?.trim();
|
|
100
|
+
if (url) {
|
|
101
|
+
this.installFromUrl(url);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
const urlInput = getElementByIdOrNull<HTMLInputElement>('skills-url-input');
|
|
106
|
+
if (urlInput) {
|
|
107
|
+
urlInput.addEventListener('keydown', (e: KeyboardEvent) => {
|
|
108
|
+
if (e.key === 'Enter') {
|
|
109
|
+
const target = e.target;
|
|
110
|
+
if (!(target instanceof HTMLInputElement)) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const url = target.value.trim();
|
|
114
|
+
if (url) {
|
|
115
|
+
this.installFromUrl(url);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Filter buttons
|
|
122
|
+
const filterBar = getElementByIdOrNull<HTMLElement>('skills-filter-bar');
|
|
123
|
+
if (filterBar) {
|
|
124
|
+
filterBar.addEventListener('click', (e: MouseEvent) => {
|
|
125
|
+
const target = e.target;
|
|
126
|
+
const btn =
|
|
127
|
+
target instanceof HTMLElement ? target.closest<HTMLElement>('[data-filter]') : null;
|
|
128
|
+
if (!btn) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const filter = btn.dataset.filter;
|
|
132
|
+
if (!filter) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
this.currentFilter = filter;
|
|
136
|
+
// Update active state
|
|
137
|
+
filterBar.querySelectorAll<HTMLElement>('[data-filter]').forEach((b) => {
|
|
138
|
+
b.classList.toggle('bg-yellow-400', b.dataset.filter === this.currentFilter);
|
|
139
|
+
b.classList.toggle('text-gray-900', b.dataset.filter === this.currentFilter);
|
|
140
|
+
b.classList.toggle('bg-gray-700', b.dataset.filter !== this.currentFilter);
|
|
141
|
+
b.classList.toggle('text-gray-300', b.dataset.filter !== this.currentFilter);
|
|
142
|
+
});
|
|
143
|
+
this.render();
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Render the full skills view
|
|
150
|
+
*/
|
|
151
|
+
render(): void {
|
|
152
|
+
const container = getElementByIdOrNull<HTMLElement>('skills-content');
|
|
153
|
+
if (!container) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const installed = this._filterSkills(this.installed);
|
|
158
|
+
const available = this._filterSkills(this.catalog);
|
|
159
|
+
|
|
160
|
+
container.innerHTML = `
|
|
161
|
+
${
|
|
162
|
+
installed.length > 0
|
|
163
|
+
? `
|
|
164
|
+
<div class="mb-6">
|
|
165
|
+
<h3 class="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-3">
|
|
166
|
+
Installed (${installed.length})
|
|
167
|
+
</h3>
|
|
168
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3">
|
|
169
|
+
${installed.map((s) => this._renderCard(s, true)).join('')}
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
`
|
|
173
|
+
: ''
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
<div class="mb-6">
|
|
177
|
+
<h3 class="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-3">
|
|
178
|
+
Available (${available.length})
|
|
179
|
+
</h3>
|
|
180
|
+
${
|
|
181
|
+
available.length > 0
|
|
182
|
+
? `
|
|
183
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3">
|
|
184
|
+
${available.map((s) => this._renderCard(s, false)).join('')}
|
|
185
|
+
</div>
|
|
186
|
+
`
|
|
187
|
+
: `
|
|
188
|
+
<p class="text-gray-500 text-sm">
|
|
189
|
+
${this.searchQuery ? 'No skills match your search.' : 'Loading catalog...'}
|
|
190
|
+
</p>
|
|
191
|
+
`
|
|
192
|
+
}
|
|
193
|
+
</div>
|
|
194
|
+
`;
|
|
195
|
+
|
|
196
|
+
// Bind card actions
|
|
197
|
+
container.querySelectorAll<HTMLButtonElement>('[data-action]').forEach((btn) => {
|
|
198
|
+
btn.addEventListener('click', (e: MouseEvent) => {
|
|
199
|
+
e.stopPropagation();
|
|
200
|
+
const action = btn.dataset.action;
|
|
201
|
+
const id = btn.dataset.id;
|
|
202
|
+
const source = btn.dataset.source;
|
|
203
|
+
if (!action || !id || !source) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (action === 'install') {
|
|
207
|
+
this.install(source, id);
|
|
208
|
+
} else if (action === 'uninstall') {
|
|
209
|
+
this.uninstall(source, id);
|
|
210
|
+
} else if (action === 'toggle') {
|
|
211
|
+
this.toggle(source, id, btn.dataset.enabled !== 'true');
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// Bind card click for detail
|
|
217
|
+
container.querySelectorAll<HTMLElement>('[data-skill-card]').forEach((card) => {
|
|
218
|
+
card.addEventListener('click', () => {
|
|
219
|
+
const source = card.dataset.source;
|
|
220
|
+
const id = card.dataset.id;
|
|
221
|
+
if (!source || !id) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
this.showDetail(source, id);
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Filter skills by current filter + search query
|
|
231
|
+
*/
|
|
232
|
+
_filterSkills(skills: SkillItem[]): SkillItem[] {
|
|
233
|
+
let filtered = skills;
|
|
234
|
+
|
|
235
|
+
// Source filter
|
|
236
|
+
if (this.currentFilter !== 'all' && this.currentFilter !== 'installed') {
|
|
237
|
+
filtered = filtered.filter((s) => s.source === this.currentFilter);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Search filter
|
|
241
|
+
if (this.searchQuery) {
|
|
242
|
+
const q = this.searchQuery.toLowerCase();
|
|
243
|
+
filtered = filtered.filter(
|
|
244
|
+
(s) =>
|
|
245
|
+
s.name.toLowerCase().includes(q) ||
|
|
246
|
+
(s.description || '').toLowerCase().includes(q) ||
|
|
247
|
+
s.id.toLowerCase().includes(q)
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return filtered;
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Render a single skill card
|
|
256
|
+
*/
|
|
257
|
+
_renderCard(skill: SkillItem, isInstalled: boolean): string {
|
|
258
|
+
const sourceColors = {
|
|
259
|
+
mama: 'bg-yellow-900/30 text-yellow-400',
|
|
260
|
+
cowork: 'bg-blue-900/30 text-blue-400',
|
|
261
|
+
external: 'bg-purple-900/30 text-purple-400',
|
|
262
|
+
};
|
|
263
|
+
const badgeClass = sourceColors[skill.source] || 'bg-gray-700 text-gray-400';
|
|
264
|
+
const enabledClass = skill.enabled !== false ? 'border-green-500/30' : 'border-gray-700';
|
|
265
|
+
|
|
266
|
+
return `
|
|
267
|
+
<div class="bg-gray-800 rounded-lg border ${enabledClass} p-3 cursor-pointer
|
|
268
|
+
hover:border-yellow-500/50 transition-colors"
|
|
269
|
+
data-skill-card data-id="${this._escapeHtml(skill.id)}" data-source="${this._escapeHtml(skill.source)}">
|
|
270
|
+
<div class="flex items-start justify-between mb-2">
|
|
271
|
+
<h4 class="font-medium text-sm text-white truncate flex-1">${this._escapeHtml(skill.name)}</h4>
|
|
272
|
+
<span class="text-xs px-1.5 py-0.5 rounded ${badgeClass} ml-2 whitespace-nowrap">
|
|
273
|
+
${this._escapeHtml(skill.source)}
|
|
274
|
+
</span>
|
|
275
|
+
</div>
|
|
276
|
+
<p class="text-xs text-gray-400 line-clamp-2 mb-3">${this._escapeHtml(skill.description || '')}</p>
|
|
277
|
+
<div class="flex items-center justify-between">
|
|
278
|
+
${
|
|
279
|
+
isInstalled
|
|
280
|
+
? `
|
|
281
|
+
<button data-action="toggle" data-id="${this._escapeHtml(skill.id)}" data-source="${this._escapeHtml(skill.source)}"
|
|
282
|
+
data-enabled="${skill.enabled !== false}"
|
|
283
|
+
class="text-xs px-2 py-1 rounded ${skill.enabled !== false ? 'bg-green-900/30 text-green-400' : 'bg-gray-700 text-gray-400'}">
|
|
284
|
+
${skill.enabled !== false ? 'Enabled' : 'Disabled'}
|
|
285
|
+
</button>
|
|
286
|
+
<button data-action="uninstall" data-id="${this._escapeHtml(skill.id)}" data-source="${this._escapeHtml(skill.source)}"
|
|
287
|
+
class="text-xs px-2 py-1 rounded bg-red-900/30 text-red-400 hover:bg-red-900/50">
|
|
288
|
+
Remove
|
|
289
|
+
</button>
|
|
290
|
+
`
|
|
291
|
+
: `
|
|
292
|
+
<span></span>
|
|
293
|
+
<button data-action="install" data-id="${this._escapeHtml(skill.id)}" data-source="${this._escapeHtml(skill.source)}"
|
|
294
|
+
class="text-xs px-2 py-1 rounded bg-yellow-900/30 text-yellow-400 hover:bg-yellow-900/50">
|
|
295
|
+
Install
|
|
296
|
+
</button>
|
|
297
|
+
`
|
|
298
|
+
}
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
`;
|
|
302
|
+
},
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Install a skill
|
|
306
|
+
*/
|
|
307
|
+
async install(source: string, name: string): Promise<void> {
|
|
308
|
+
try {
|
|
309
|
+
const btn = document.querySelector<HTMLButtonElement>(
|
|
310
|
+
`[data-action="install"][data-id="${CSS.escape(name)}"][data-source="${CSS.escape(source)}"]`
|
|
311
|
+
);
|
|
312
|
+
if (btn) {
|
|
313
|
+
btn.textContent = 'Installing...';
|
|
314
|
+
btn.disabled = true;
|
|
315
|
+
}
|
|
316
|
+
await API.installSkill(source, name);
|
|
317
|
+
await this.loadSkills();
|
|
318
|
+
this.render();
|
|
319
|
+
} catch (error) {
|
|
320
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
321
|
+
logger.error('Install failed:', message);
|
|
322
|
+
alert(`Failed to install ${name}: ${message}`);
|
|
323
|
+
this.render();
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Install from GitHub URL
|
|
329
|
+
*/
|
|
330
|
+
async installFromUrl(url: string): Promise<void> {
|
|
331
|
+
const btn = getElementByIdOrNull<HTMLButtonElement>('skills-url-install-btn');
|
|
332
|
+
const input = getElementByIdOrNull<HTMLInputElement>('skills-url-input');
|
|
333
|
+
try {
|
|
334
|
+
if (btn) {
|
|
335
|
+
btn.textContent = 'Installing...';
|
|
336
|
+
btn.disabled = true;
|
|
337
|
+
}
|
|
338
|
+
await API.installSkillFromUrl(url);
|
|
339
|
+
if (input) {
|
|
340
|
+
input.value = '';
|
|
341
|
+
}
|
|
342
|
+
await this.loadSkills();
|
|
343
|
+
this.render();
|
|
344
|
+
} catch (error) {
|
|
345
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
346
|
+
logger.error('URL install failed:', message);
|
|
347
|
+
alert(`Failed to install from URL: ${message}`);
|
|
348
|
+
} finally {
|
|
349
|
+
if (btn) {
|
|
350
|
+
btn.textContent = 'Install URL';
|
|
351
|
+
btn.disabled = false;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Uninstall a skill
|
|
358
|
+
*/
|
|
359
|
+
async uninstall(source: string, name: string): Promise<void> {
|
|
360
|
+
if (!confirm(`Remove skill "${name}"?`)) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
try {
|
|
365
|
+
await API.uninstallSkill(name, source);
|
|
366
|
+
await this.loadSkills();
|
|
367
|
+
this.render();
|
|
368
|
+
} catch (error) {
|
|
369
|
+
logger.error('Uninstall failed:', error instanceof Error ? error.message : String(error));
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Toggle skill enabled/disabled
|
|
375
|
+
*/
|
|
376
|
+
async toggle(source: string, name: string, enabled: boolean): Promise<void> {
|
|
377
|
+
try {
|
|
378
|
+
await API.toggleSkill(name, enabled, source);
|
|
379
|
+
// Update local state
|
|
380
|
+
const skill = this.installed.find((s) => s.id === name && s.source === source);
|
|
381
|
+
if (skill) {
|
|
382
|
+
skill.enabled = enabled;
|
|
383
|
+
}
|
|
384
|
+
this.render();
|
|
385
|
+
} catch (error) {
|
|
386
|
+
logger.error('Toggle failed:', error instanceof Error ? error.message : String(error));
|
|
387
|
+
}
|
|
388
|
+
},
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Show skill detail modal
|
|
392
|
+
*/
|
|
393
|
+
async showDetail(source: string, name: string): Promise<void> {
|
|
394
|
+
const modal = getElementByIdOrNull<HTMLElement>('skill-detail-modal');
|
|
395
|
+
const modalContent = getElementByIdOrNull<HTMLElement>('skill-detail-content');
|
|
396
|
+
if (!modal || !modalContent) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
modalContent.innerHTML = '<p class="text-gray-400">Loading...</p>';
|
|
401
|
+
modal.classList.remove('hidden');
|
|
402
|
+
|
|
403
|
+
try {
|
|
404
|
+
const { content } = await API.getSkillContent(name, source);
|
|
405
|
+
modalContent.innerHTML = `
|
|
406
|
+
<div class="flex items-center justify-between mb-4">
|
|
407
|
+
<h2 class="text-lg font-bold text-white">${this._escapeHtml(name)}</h2>
|
|
408
|
+
<span class="text-xs px-2 py-1 rounded bg-gray-700 text-gray-400">${this._escapeHtml(source)}</span>
|
|
409
|
+
</div>
|
|
410
|
+
<div class="prose prose-invert prose-sm max-w-none">
|
|
411
|
+
${this._renderMarkdown(content)}
|
|
412
|
+
</div>
|
|
413
|
+
`;
|
|
414
|
+
} catch (error) {
|
|
415
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
416
|
+
modalContent.innerHTML = `<p class="text-red-400">Failed to load: ${this._escapeHtml(message)}</p>`;
|
|
417
|
+
}
|
|
418
|
+
},
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Close detail modal
|
|
422
|
+
*/
|
|
423
|
+
closeDetail(): void {
|
|
424
|
+
const modal = getElementByIdOrNull<HTMLElement>('skill-detail-modal');
|
|
425
|
+
if (modal) {
|
|
426
|
+
modal.classList.add('hidden');
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Escape HTML special characters
|
|
432
|
+
*/
|
|
433
|
+
_escapeHtml(unsafe: unknown): string {
|
|
434
|
+
if (!unsafe) {
|
|
435
|
+
return '';
|
|
436
|
+
}
|
|
437
|
+
return unsafe
|
|
438
|
+
.toString()
|
|
439
|
+
.replace(/&/g, '&')
|
|
440
|
+
.replace(/</g, '<')
|
|
441
|
+
.replace(/>/g, '>')
|
|
442
|
+
.replace(/"/g, '"')
|
|
443
|
+
.replace(/'/g, ''');
|
|
444
|
+
},
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Render markdown to HTML using marked.js, with sanitization
|
|
448
|
+
*/
|
|
449
|
+
_renderMarkdown(md: string): string {
|
|
450
|
+
if (!md) {
|
|
451
|
+
return '';
|
|
452
|
+
}
|
|
453
|
+
// Remove frontmatter
|
|
454
|
+
md = md.replace(/^---\n[\s\S]*?\n---\n/, '');
|
|
455
|
+
return renderSafeMarkdown(md);
|
|
456
|
+
},
|
|
457
|
+
};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
export {};
|
|
3
|
+
|
|
4
|
+
declare global {
|
|
5
|
+
interface SpeechRecognitionConstructor {
|
|
6
|
+
new (): SpeechRecognition;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface SpeechRecognitionAlternative {
|
|
10
|
+
transcript: string;
|
|
11
|
+
confidence: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface SpeechRecognitionResult {
|
|
15
|
+
length: number;
|
|
16
|
+
isFinal: boolean;
|
|
17
|
+
item(index: number): SpeechRecognitionAlternative;
|
|
18
|
+
[index: number]: SpeechRecognitionAlternative;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface SpeechRecognitionResultList {
|
|
22
|
+
length: number;
|
|
23
|
+
item(index: number): SpeechRecognitionResult;
|
|
24
|
+
[index: number]: SpeechRecognitionResult;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface SpeechRecognitionErrorEvent extends Event {
|
|
28
|
+
error: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface SpeechRecognitionEvent extends Event {
|
|
32
|
+
resultIndex: number;
|
|
33
|
+
results: SpeechRecognitionResultList;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface SpeechRecognition extends EventTarget {
|
|
37
|
+
lang: string;
|
|
38
|
+
continuous: boolean;
|
|
39
|
+
interimResults: boolean;
|
|
40
|
+
maxAlternatives: number;
|
|
41
|
+
onresult: ((event: SpeechRecognitionEvent) => void) | null;
|
|
42
|
+
onend: (() => void) | null;
|
|
43
|
+
onerror: ((event: SpeechRecognitionErrorEvent) => void) | null;
|
|
44
|
+
start(): void;
|
|
45
|
+
stop(): void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface VisNodeRecord {
|
|
49
|
+
id?: string | number;
|
|
50
|
+
from?: string | number;
|
|
51
|
+
to?: string | number;
|
|
52
|
+
font?: {
|
|
53
|
+
color?: string;
|
|
54
|
+
[key: string]: unknown;
|
|
55
|
+
};
|
|
56
|
+
color?: unknown;
|
|
57
|
+
label?: string;
|
|
58
|
+
title?: string;
|
|
59
|
+
size?: number;
|
|
60
|
+
borderWidth?: number;
|
|
61
|
+
data?: unknown;
|
|
62
|
+
hidden?: boolean;
|
|
63
|
+
opacity?: number;
|
|
64
|
+
[key: string]: unknown;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface VisDataSet<T extends VisNodeRecord> {
|
|
68
|
+
get(): T[];
|
|
69
|
+
update(item: Partial<T> | Partial<T>[]): void;
|
|
70
|
+
add(items: T | T[]): void;
|
|
71
|
+
clear?(): void;
|
|
72
|
+
remove?(id: string | number | Array<string | number>): void;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface VisNetworkDataContext {
|
|
76
|
+
nodes: VisDataSet<VisNodeRecord>;
|
|
77
|
+
edges: VisDataSet<VisNodeRecord>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface VisNetwork {
|
|
81
|
+
on(
|
|
82
|
+
event: 'click' | 'stabilized',
|
|
83
|
+
handler: (params: { nodes: Array<string | number> }) => void
|
|
84
|
+
): void;
|
|
85
|
+
body: {
|
|
86
|
+
data: VisNetworkDataContext;
|
|
87
|
+
};
|
|
88
|
+
focus(nodeId: string | number, options?: unknown): void;
|
|
89
|
+
selectNodes(ids: Array<string | number>): void;
|
|
90
|
+
destroy?: () => void;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
interface VisConstructor {
|
|
94
|
+
DataSet: new <T extends VisNodeRecord>(items?: T[]) => VisDataSet<T>;
|
|
95
|
+
DataSet<T extends VisNodeRecord>(items?: T[]): VisDataSet<T>;
|
|
96
|
+
Network: new (
|
|
97
|
+
container: HTMLElement,
|
|
98
|
+
data: Record<string, unknown>,
|
|
99
|
+
options: unknown
|
|
100
|
+
) => VisNetwork;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const vis: VisConstructor;
|
|
104
|
+
const marked: {
|
|
105
|
+
parse(
|
|
106
|
+
markdown: string,
|
|
107
|
+
options?: {
|
|
108
|
+
mangle?: boolean;
|
|
109
|
+
headerIds?: boolean;
|
|
110
|
+
sanitize?: boolean;
|
|
111
|
+
}
|
|
112
|
+
): string;
|
|
113
|
+
};
|
|
114
|
+
interface DOMPurifyConfig {
|
|
115
|
+
[key: string]: unknown;
|
|
116
|
+
ALLOWED_TAGS?: string[];
|
|
117
|
+
ALLOWED_ATTR?: string[];
|
|
118
|
+
ADD_TAGS?: string[];
|
|
119
|
+
ADD_ATTR?: string[];
|
|
120
|
+
}
|
|
121
|
+
const DOMPurify: {
|
|
122
|
+
sanitize(dirty: string, options?: DOMPurifyConfig): string;
|
|
123
|
+
};
|
|
124
|
+
const lucide: {
|
|
125
|
+
createIcons(config?: unknown): void;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
interface Window {
|
|
129
|
+
switchTab?: (tab: string) => void;
|
|
130
|
+
chatModule?: {
|
|
131
|
+
toggleToolCard: (toolId: string) => void;
|
|
132
|
+
};
|
|
133
|
+
graphModule?: {
|
|
134
|
+
navigateToNode: (nodeId: string) => void;
|
|
135
|
+
};
|
|
136
|
+
memoryModule?: {
|
|
137
|
+
toggleCard: (index: number) => void;
|
|
138
|
+
searchWithQuery: (query: string) => Promise<void>;
|
|
139
|
+
showSaveFormWithText: (text: string) => void;
|
|
140
|
+
showSaveForm: () => void;
|
|
141
|
+
};
|
|
142
|
+
settingsModule?: {
|
|
143
|
+
init?: () => Promise<void>;
|
|
144
|
+
addCronJob?: () => Promise<void>;
|
|
145
|
+
resetForm?: () => void;
|
|
146
|
+
saveAndRestart?: () => Promise<void>;
|
|
147
|
+
toggleAgent: (agentId: string, enabled: boolean) => Promise<void>;
|
|
148
|
+
onAgentBackendChange: (agentId: string) => void;
|
|
149
|
+
saveAgentConfig: (agentId: string) => Promise<void>;
|
|
150
|
+
toggleAllGateway?: (checked: boolean) => void;
|
|
151
|
+
toggleAllMCP?: (checked: boolean) => void;
|
|
152
|
+
toggleCronJob?: (id: string, enabled: boolean) => Promise<void>;
|
|
153
|
+
deleteCronJob?: (id: string) => Promise<void>;
|
|
154
|
+
};
|
|
155
|
+
skillsModule?: {
|
|
156
|
+
closeDetail?: () => void;
|
|
157
|
+
install?: (source: string, name: string) => Promise<void>;
|
|
158
|
+
uninstall?: (source: string, name: string) => Promise<void>;
|
|
159
|
+
toggle?: (source: string, name: string, enabled: boolean) => Promise<void>;
|
|
160
|
+
init?: () => Promise<void>;
|
|
161
|
+
loadSkills?: () => Promise<void>;
|
|
162
|
+
render?: () => void;
|
|
163
|
+
};
|
|
164
|
+
SpeechRecognition?: SpeechRecognitionConstructor;
|
|
165
|
+
webkitSpeechRecognition?: SpeechRecognitionConstructor;
|
|
166
|
+
lucideConfig?: unknown;
|
|
167
|
+
}
|
|
168
|
+
}
|