@jungjaehoon/mama-os 0.18.2 → 0.19.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/dist/agent/agent-loop.d.ts +25 -0
- package/dist/agent/agent-loop.d.ts.map +1 -1
- package/dist/agent/agent-loop.js +67 -14
- package/dist/agent/agent-loop.js.map +1 -1
- package/dist/agent/code-act/host-bridge.d.ts.map +1 -1
- package/dist/agent/code-act/host-bridge.js +98 -0
- package/dist/agent/code-act/host-bridge.js.map +1 -1
- package/dist/agent/code-act/type-definition-generator.d.ts.map +1 -1
- package/dist/agent/code-act/type-definition-generator.js +0 -1
- package/dist/agent/code-act/type-definition-generator.js.map +1 -1
- package/dist/agent/gateway-tool-executor.d.ts +36 -1
- package/dist/agent/gateway-tool-executor.d.ts.map +1 -1
- package/dist/agent/gateway-tool-executor.js +938 -54
- package/dist/agent/gateway-tool-executor.js.map +1 -1
- package/dist/agent/gateway-tools.md +9 -0
- package/dist/agent/managed-agent-runtime-sync.d.ts +36 -0
- package/dist/agent/managed-agent-runtime-sync.d.ts.map +1 -0
- package/dist/agent/managed-agent-runtime-sync.js +207 -0
- package/dist/agent/managed-agent-runtime-sync.js.map +1 -0
- package/dist/agent/managed-agent-validation.d.ts +4 -0
- package/dist/agent/managed-agent-validation.d.ts.map +1 -0
- package/dist/agent/managed-agent-validation.js +84 -0
- package/dist/agent/managed-agent-validation.js.map +1 -0
- package/dist/agent/os-agent-capabilities.md +400 -0
- package/dist/agent/skill-loader.d.ts +2 -0
- package/dist/agent/skill-loader.d.ts.map +1 -1
- package/dist/agent/skill-loader.js +28 -0
- package/dist/agent/skill-loader.js.map +1 -1
- package/dist/agent/tool-registry.d.ts.map +1 -1
- package/dist/agent/tool-registry.js +66 -0
- package/dist/agent/tool-registry.js.map +1 -1
- package/dist/agent/types.d.ts +2 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/api/agent-handler.d.ts +34 -0
- package/dist/api/agent-handler.d.ts.map +1 -0
- package/dist/api/agent-handler.js +216 -0
- package/dist/api/agent-handler.js.map +1 -0
- package/dist/api/graph-api-types.d.ts +4 -0
- package/dist/api/graph-api-types.d.ts.map +1 -1
- package/dist/api/graph-api.d.ts +2 -2
- package/dist/api/graph-api.d.ts.map +1 -1
- package/dist/api/graph-api.js +480 -51
- package/dist/api/graph-api.js.map +1 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +4 -0
- package/dist/api/index.js.map +1 -1
- package/dist/api/token-handler.d.ts +1 -0
- package/dist/api/token-handler.d.ts.map +1 -1
- package/dist/api/token-handler.js +4 -3
- package/dist/api/token-handler.js.map +1 -1
- package/dist/api/ui-command-handler.d.ts +48 -0
- package/dist/api/ui-command-handler.d.ts.map +1 -0
- package/dist/api/ui-command-handler.js +160 -0
- package/dist/api/ui-command-handler.js.map +1 -0
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +127 -1
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/config/config-manager.d.ts.map +1 -1
- package/dist/cli/config/config-manager.js +16 -31
- package/dist/cli/config/config-manager.js.map +1 -1
- package/dist/cli/runtime/agent-loop-init.d.ts.map +1 -1
- package/dist/cli/runtime/agent-loop-init.js +31 -7
- package/dist/cli/runtime/agent-loop-init.js.map +1 -1
- package/dist/cli/runtime/api-routes-init.d.ts +3 -0
- package/dist/cli/runtime/api-routes-init.d.ts.map +1 -1
- package/dist/cli/runtime/api-routes-init.js +283 -34
- package/dist/cli/runtime/api-routes-init.js.map +1 -1
- package/dist/cli/runtime/gateway-init.d.ts +2 -1
- package/dist/cli/runtime/gateway-init.d.ts.map +1 -1
- package/dist/cli/runtime/gateway-init.js +5 -1
- package/dist/cli/runtime/gateway-init.js.map +1 -1
- package/dist/connectors/framework/raw-store.d.ts +4 -0
- package/dist/connectors/framework/raw-store.d.ts.map +1 -1
- package/dist/connectors/framework/raw-store.js +33 -10
- package/dist/connectors/framework/raw-store.js.map +1 -1
- package/dist/db/agent-store.d.ts +115 -0
- package/dist/db/agent-store.d.ts.map +1 -0
- package/dist/db/agent-store.js +248 -0
- package/dist/db/agent-store.js.map +1 -0
- package/dist/db/migrations/agent-activity-validation-columns.d.ts +3 -0
- package/dist/db/migrations/agent-activity-validation-columns.d.ts.map +1 -0
- package/dist/db/migrations/agent-activity-validation-columns.js +22 -0
- package/dist/db/migrations/agent-activity-validation-columns.js.map +1 -0
- package/dist/db/migrations/agent-metrics-response-avg.d.ts +3 -0
- package/dist/db/migrations/agent-metrics-response-avg.d.ts.map +1 -0
- package/dist/db/migrations/agent-metrics-response-avg.js +19 -0
- package/dist/db/migrations/agent-metrics-response-avg.js.map +1 -0
- package/dist/db/migrations/agent-store-tables.d.ts +3 -0
- package/dist/db/migrations/agent-store-tables.d.ts.map +1 -0
- package/dist/db/migrations/agent-store-tables.js +59 -0
- package/dist/db/migrations/agent-store-tables.js.map +1 -0
- package/dist/db/migrations/token-usage-agent-version.d.ts +3 -0
- package/dist/db/migrations/token-usage-agent-version.d.ts.map +1 -0
- package/dist/db/migrations/token-usage-agent-version.js +16 -0
- package/dist/db/migrations/token-usage-agent-version.js.map +1 -0
- package/dist/db/migrations/validation-session-tables.d.ts +3 -0
- package/dist/db/migrations/validation-session-tables.d.ts.map +1 -0
- package/dist/db/migrations/validation-session-tables.js +59 -0
- package/dist/db/migrations/validation-session-tables.js.map +1 -0
- package/dist/gateways/message-router.d.ts +10 -0
- package/dist/gateways/message-router.d.ts.map +1 -1
- package/dist/gateways/message-router.js +188 -14
- package/dist/gateways/message-router.js.map +1 -1
- package/dist/gateways/types.d.ts +1 -1
- package/dist/gateways/types.d.ts.map +1 -1
- package/dist/multi-agent/agent-process-manager.js +1 -1
- package/dist/multi-agent/agent-process-manager.js.map +1 -1
- package/dist/multi-agent/conductor-persona.d.ts +13 -0
- package/dist/multi-agent/conductor-persona.d.ts.map +1 -0
- package/dist/multi-agent/conductor-persona.js +157 -0
- package/dist/multi-agent/conductor-persona.js.map +1 -0
- package/dist/multi-agent/dashboard-agent-persona.d.ts +1 -1
- package/dist/multi-agent/dashboard-agent-persona.d.ts.map +1 -1
- package/dist/multi-agent/dashboard-agent-persona.js +7 -3
- package/dist/multi-agent/dashboard-agent-persona.js.map +1 -1
- package/dist/multi-agent/delegation-manager.d.ts +5 -0
- package/dist/multi-agent/delegation-manager.d.ts.map +1 -1
- package/dist/multi-agent/delegation-manager.js +37 -0
- package/dist/multi-agent/delegation-manager.js.map +1 -1
- package/dist/multi-agent/ultrawork.d.ts +3 -0
- package/dist/multi-agent/ultrawork.d.ts.map +1 -1
- package/dist/multi-agent/ultrawork.js +9 -0
- package/dist/multi-agent/ultrawork.js.map +1 -1
- package/dist/validation/session-service.d.ts +72 -0
- package/dist/validation/session-service.d.ts.map +1 -0
- package/dist/validation/session-service.js +298 -0
- package/dist/validation/session-service.js.map +1 -0
- package/dist/validation/store.d.ts +25 -0
- package/dist/validation/store.d.ts.map +1 -0
- package/dist/validation/store.js +200 -0
- package/dist/validation/store.js.map +1 -0
- package/dist/validation/types.d.ts +119 -0
- package/dist/validation/types.d.ts.map +1 -0
- package/dist/validation/types.js +57 -0
- package/dist/validation/types.js.map +1 -0
- package/package.json +3 -3
- package/public/viewer/js/modules/agents.js +1148 -0
- package/public/viewer/js/modules/chat.js +20 -11
- package/public/viewer/js/modules/connector-feed.js +35 -0
- package/public/viewer/js/modules/dashboard.js +49 -0
- package/public/viewer/js/modules/memory.js +32 -0
- package/public/viewer/js/modules/settings.js +34 -79
- package/public/viewer/js/modules/wiki.js +59 -4
- package/public/viewer/js/utils/api.js +70 -0
- package/public/viewer/js/utils/dom.js +3 -0
- package/public/viewer/js/utils/ui-commands.js +93 -0
- package/public/viewer/log-viewer.html +2 -2
- package/public/viewer/src/modules/agents.ts +1299 -0
- package/public/viewer/src/modules/chat.ts +23 -14
- package/public/viewer/src/modules/connector-feed.ts +35 -0
- package/public/viewer/src/modules/dashboard.ts +50 -0
- package/public/viewer/src/modules/memory.ts +31 -0
- package/public/viewer/src/modules/settings.ts +36 -96
- package/public/viewer/src/modules/wiki.ts +73 -6
- package/public/viewer/src/types/global.d.ts +0 -9
- package/public/viewer/src/utils/api.ts +156 -2
- package/public/viewer/src/utils/dom.ts +6 -1
- package/public/viewer/src/utils/ui-commands.ts +118 -0
- package/public/viewer/viewer.css +105 -10
- package/public/viewer/viewer.html +1868 -777
- package/scripts/generate-gateway-tools.ts +5 -1
- package/public/viewer/js/modules/playground.js +0 -148
- package/public/viewer/js/modules/skills.js +0 -451
- package/public/viewer/src/modules/playground.ts +0 -173
- package/public/viewer/src/modules/skills.ts +0 -491
- package/templates/playgrounds/cron-workflow-lab.html +0 -1601
- package/templates/playgrounds/mama-log-viewer.html +0 -1341
- package/templates/playgrounds/skill-lab-playground.html +0 -1625
- package/templates/playgrounds/wave-visualizer.html +0 -694
- package/templates/skills/playground.md +0 -197
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Playground Module
|
|
3
|
-
* @module modules/playground
|
|
4
|
-
*
|
|
5
|
-
* Lists, views, and manages interactive HTML playgrounds
|
|
6
|
-
* created by agents via playground_create gateway tool.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/* eslint-env browser */
|
|
10
|
-
|
|
11
|
-
interface PlaygroundEntry {
|
|
12
|
-
name: string;
|
|
13
|
-
slug: string;
|
|
14
|
-
description?: string;
|
|
15
|
-
created_at: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export const PlaygroundModule = {
|
|
19
|
-
initialized: false,
|
|
20
|
-
|
|
21
|
-
async init(): Promise<void> {
|
|
22
|
-
await this.loadList();
|
|
23
|
-
this.bindEvents();
|
|
24
|
-
this.initialized = true;
|
|
25
|
-
},
|
|
26
|
-
|
|
27
|
-
bindEvents(): void {
|
|
28
|
-
const refreshBtn = document.getElementById('playground-refresh-btn');
|
|
29
|
-
if (refreshBtn && !refreshBtn.dataset.bound) {
|
|
30
|
-
refreshBtn.addEventListener('click', () => this.loadList());
|
|
31
|
-
refreshBtn.dataset.bound = '1';
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const backBtn = document.getElementById('playground-back-btn');
|
|
35
|
-
if (backBtn && !backBtn.dataset.bound) {
|
|
36
|
-
backBtn.addEventListener('click', () => this.showList());
|
|
37
|
-
backBtn.dataset.bound = '1';
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
async loadList(): Promise<void> {
|
|
42
|
-
const listEl = document.getElementById('playground-list');
|
|
43
|
-
if (!listEl) return;
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
const res = await fetch('/api/playgrounds');
|
|
47
|
-
const items: PlaygroundEntry[] = await res.json();
|
|
48
|
-
|
|
49
|
-
if (!items.length) {
|
|
50
|
-
listEl.innerHTML =
|
|
51
|
-
'<div class="text-gray-500 text-sm col-span-full text-center py-8">No playgrounds yet. Ask an agent to create one!</div>';
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
listEl.innerHTML = items
|
|
56
|
-
.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
|
|
57
|
-
.map(
|
|
58
|
-
(item) => `
|
|
59
|
-
<div class="playground-card bg-white border border-mama-lavender-dark rounded-xl p-4 hover:shadow-md transition-shadow cursor-pointer flex flex-col gap-2" data-slug="${item.slug}">
|
|
60
|
-
<div class="flex items-center justify-between">
|
|
61
|
-
<h3 class="font-semibold text-mama-black text-sm truncate">${this.escapeHtml(item.name)}</h3>
|
|
62
|
-
<button class="playground-delete p-1 rounded hover:bg-red-100 text-gray-400 hover:text-red-500 transition-colors" data-slug="${item.slug}" title="Delete">
|
|
63
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
64
|
-
<polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
|
|
65
|
-
</svg>
|
|
66
|
-
</button>
|
|
67
|
-
</div>
|
|
68
|
-
${item.description ? `<p class="text-xs text-gray-500 line-clamp-2">${this.escapeHtml(item.description)}</p>` : ''}
|
|
69
|
-
<span class="text-[10px] text-gray-400 mt-auto">${new Date(item.created_at).toLocaleString()}</span>
|
|
70
|
-
</div>
|
|
71
|
-
`
|
|
72
|
-
)
|
|
73
|
-
.join('');
|
|
74
|
-
|
|
75
|
-
// Bind card clicks
|
|
76
|
-
listEl.querySelectorAll('.playground-card').forEach((card) => {
|
|
77
|
-
card.addEventListener('click', (e) => {
|
|
78
|
-
const target = e.target as HTMLElement;
|
|
79
|
-
if (target.closest('.playground-delete')) return;
|
|
80
|
-
const slug = (card as HTMLElement).dataset.slug;
|
|
81
|
-
if (slug) this.openPlayground(slug, items.find((i) => i.slug === slug)?.name || slug);
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// Bind delete buttons
|
|
86
|
-
listEl.querySelectorAll('.playground-delete').forEach((btn) => {
|
|
87
|
-
btn.addEventListener('click', async (e) => {
|
|
88
|
-
e.stopPropagation();
|
|
89
|
-
const slug = (btn as HTMLElement).dataset.slug;
|
|
90
|
-
if (slug && confirm(`Delete playground "${slug}"?`)) {
|
|
91
|
-
await this.deletePlayground(slug);
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
} catch (err) {
|
|
96
|
-
listEl.innerHTML =
|
|
97
|
-
'<div class="text-red-500 text-sm col-span-full text-center py-8">Failed to load playgrounds</div>';
|
|
98
|
-
console.error('[Playground] Failed to load:', err);
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
|
|
102
|
-
openPlayground(slug: string, name: string): void {
|
|
103
|
-
const listEl = document.getElementById('playground-list');
|
|
104
|
-
const viewerEl = document.getElementById('playground-viewer');
|
|
105
|
-
const iframe = document.getElementById('playground-iframe') as HTMLIFrameElement | null;
|
|
106
|
-
const titleEl = document.getElementById('playground-viewer-title');
|
|
107
|
-
const openNewEl = document.getElementById('playground-open-new') as HTMLAnchorElement | null;
|
|
108
|
-
|
|
109
|
-
if (!listEl || !viewerEl || !iframe) return;
|
|
110
|
-
|
|
111
|
-
const url = `/playgrounds/${slug}.html`;
|
|
112
|
-
listEl.classList.add('hidden');
|
|
113
|
-
viewerEl.classList.remove('hidden');
|
|
114
|
-
iframe.src = url;
|
|
115
|
-
if (titleEl) titleEl.textContent = name;
|
|
116
|
-
if (openNewEl) openNewEl.href = url;
|
|
117
|
-
},
|
|
118
|
-
|
|
119
|
-
showList(): void {
|
|
120
|
-
const listEl = document.getElementById('playground-list');
|
|
121
|
-
const viewerEl = document.getElementById('playground-viewer');
|
|
122
|
-
const iframe = document.getElementById('playground-iframe') as HTMLIFrameElement | null;
|
|
123
|
-
|
|
124
|
-
if (listEl) listEl.classList.remove('hidden');
|
|
125
|
-
if (viewerEl) viewerEl.classList.add('hidden');
|
|
126
|
-
if (iframe) iframe.src = 'about:blank';
|
|
127
|
-
},
|
|
128
|
-
|
|
129
|
-
async deletePlayground(slug: string): Promise<void> {
|
|
130
|
-
try {
|
|
131
|
-
await fetch(`/api/playgrounds/${slug}`, { method: 'DELETE' });
|
|
132
|
-
this.showList();
|
|
133
|
-
await this.loadList();
|
|
134
|
-
} catch (err) {
|
|
135
|
-
console.error('[Playground] Failed to delete:', err);
|
|
136
|
-
}
|
|
137
|
-
},
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Open Skill Lab playground and optionally send skill data for editing
|
|
141
|
-
*/
|
|
142
|
-
openSkillLab(skillData?: { id: string; name: string; content: string }): void {
|
|
143
|
-
const slug = 'skill-lab-playground';
|
|
144
|
-
this.openPlayground(slug, 'Skill Lab');
|
|
145
|
-
|
|
146
|
-
if (skillData) {
|
|
147
|
-
const iframe = document.getElementById('playground-iframe') as HTMLIFrameElement | null;
|
|
148
|
-
if (!iframe) return;
|
|
149
|
-
// Wait for iframe to load before sending skill:load
|
|
150
|
-
iframe.addEventListener(
|
|
151
|
-
'load',
|
|
152
|
-
() => {
|
|
153
|
-
iframe.contentWindow?.postMessage(
|
|
154
|
-
{
|
|
155
|
-
type: 'skill:load',
|
|
156
|
-
id: skillData.id,
|
|
157
|
-
name: skillData.name,
|
|
158
|
-
content: skillData.content,
|
|
159
|
-
},
|
|
160
|
-
'*'
|
|
161
|
-
);
|
|
162
|
-
},
|
|
163
|
-
{ once: true }
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
},
|
|
167
|
-
|
|
168
|
-
escapeHtml(str: string): string {
|
|
169
|
-
const div = document.createElement('div');
|
|
170
|
-
div.textContent = str;
|
|
171
|
-
return div.innerHTML;
|
|
172
|
-
},
|
|
173
|
-
};
|
|
@@ -1,491 +0,0 @@
|
|
|
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
|
-
const logger = new DebugLogger('Skills');
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Skills marketplace module
|
|
20
|
-
*/
|
|
21
|
-
export const SkillsModule = {
|
|
22
|
-
/** All skills (installed + catalog) */
|
|
23
|
-
installed: [] as SkillItem[],
|
|
24
|
-
catalog: [] as SkillItem[],
|
|
25
|
-
/** Current filter */
|
|
26
|
-
currentFilter: 'all',
|
|
27
|
-
/** Search query */
|
|
28
|
-
searchQuery: '',
|
|
29
|
-
/** Whether initialized */
|
|
30
|
-
_initialized: false,
|
|
31
|
-
/** Debounce timer */
|
|
32
|
-
_searchTimer: null as ReturnType<typeof setTimeout> | null,
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Initialize the skills tab
|
|
36
|
-
*/
|
|
37
|
-
async init(): Promise<void> {
|
|
38
|
-
if (!this._initialized) {
|
|
39
|
-
this._bindEvents();
|
|
40
|
-
this._initialized = true;
|
|
41
|
-
}
|
|
42
|
-
await this.loadSkills();
|
|
43
|
-
this.render();
|
|
44
|
-
},
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Load installed + catalog skills
|
|
48
|
-
*/
|
|
49
|
-
async loadSkills(): Promise<void> {
|
|
50
|
-
try {
|
|
51
|
-
const [installedResponse, catalogResponse] = await Promise.all([
|
|
52
|
-
API.getSkills(),
|
|
53
|
-
API.getSkillCatalog('all'),
|
|
54
|
-
]);
|
|
55
|
-
|
|
56
|
-
this.installed = installedResponse.skills || [];
|
|
57
|
-
const catalogItems = catalogResponse.skills || [];
|
|
58
|
-
const installedMap = new Set(
|
|
59
|
-
this.installed.map((item: SkillItem) => `${item.source}::${item.id}`)
|
|
60
|
-
);
|
|
61
|
-
this.catalog = catalogItems.filter(
|
|
62
|
-
(s: SkillItem) => !installedMap.has(`${s.source}::${s.id}`)
|
|
63
|
-
);
|
|
64
|
-
} catch (error) {
|
|
65
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
66
|
-
logger.error('Unexpected error while loading skills:', message);
|
|
67
|
-
// Initialize with empty arrays on failure
|
|
68
|
-
this.installed = [];
|
|
69
|
-
this.catalog = [];
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Bind UI events
|
|
75
|
-
*/
|
|
76
|
-
_bindEvents(): void {
|
|
77
|
-
// Search input
|
|
78
|
-
const searchInput = getElementByIdOrNull<HTMLInputElement>('skills-search');
|
|
79
|
-
if (searchInput) {
|
|
80
|
-
searchInput.addEventListener('input', (e: Event) => {
|
|
81
|
-
clearTimeout(this._searchTimer);
|
|
82
|
-
this._searchTimer = setTimeout(() => {
|
|
83
|
-
const target = e.target;
|
|
84
|
-
if (!(target instanceof HTMLInputElement)) {
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
this.searchQuery = target.value.trim();
|
|
88
|
-
this.render();
|
|
89
|
-
}, 300);
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// URL install
|
|
94
|
-
const urlBtn = getElementByIdOrNull<HTMLButtonElement>('skills-url-install-btn');
|
|
95
|
-
if (urlBtn) {
|
|
96
|
-
urlBtn.addEventListener('click', () => {
|
|
97
|
-
const input = getElementByIdOrNull<HTMLInputElement>('skills-url-input');
|
|
98
|
-
const url = input?.value?.trim();
|
|
99
|
-
if (url) {
|
|
100
|
-
this.installFromUrl(url);
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
const urlInput = getElementByIdOrNull<HTMLInputElement>('skills-url-input');
|
|
105
|
-
if (urlInput) {
|
|
106
|
-
urlInput.addEventListener('keydown', (e: KeyboardEvent) => {
|
|
107
|
-
if (e.key === 'Enter') {
|
|
108
|
-
const target = e.target;
|
|
109
|
-
if (!(target instanceof HTMLInputElement)) {
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
const url = target.value.trim();
|
|
113
|
-
if (url) {
|
|
114
|
-
this.installFromUrl(url);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Filter buttons
|
|
121
|
-
const filterBar = getElementByIdOrNull<HTMLElement>('skills-filter-bar');
|
|
122
|
-
if (filterBar) {
|
|
123
|
-
filterBar.addEventListener('click', (e: MouseEvent) => {
|
|
124
|
-
const target = e.target;
|
|
125
|
-
const btn =
|
|
126
|
-
target instanceof HTMLElement ? target.closest<HTMLElement>('[data-filter]') : null;
|
|
127
|
-
if (!btn) {
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
const filter = btn.dataset.filter;
|
|
131
|
-
if (!filter) {
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
this.currentFilter = filter;
|
|
135
|
-
// Update active state
|
|
136
|
-
filterBar.querySelectorAll<HTMLElement>('[data-filter]').forEach((b) => {
|
|
137
|
-
b.classList.toggle('bg-mama-yellow', b.dataset.filter === this.currentFilter);
|
|
138
|
-
b.classList.toggle('text-mama-black', b.dataset.filter === this.currentFilter);
|
|
139
|
-
b.classList.toggle('bg-white', b.dataset.filter !== this.currentFilter);
|
|
140
|
-
b.classList.toggle('text-gray-600', b.dataset.filter !== this.currentFilter);
|
|
141
|
-
});
|
|
142
|
-
this.render();
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
},
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Render the full skills view
|
|
149
|
-
*/
|
|
150
|
-
render(): void {
|
|
151
|
-
const container = getElementByIdOrNull<HTMLElement>('skills-content');
|
|
152
|
-
if (!container) {
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const installed = this._filterSkills(this.installed);
|
|
157
|
-
const available = this._filterSkills(this.catalog);
|
|
158
|
-
|
|
159
|
-
container.innerHTML = `
|
|
160
|
-
<div class="flex items-center justify-between mb-4">
|
|
161
|
-
<span></span>
|
|
162
|
-
<button id="skills-new-btn"
|
|
163
|
-
class="text-xs px-3 py-1.5 rounded-lg bg-yellow-500 text-gray-900 font-semibold hover:bg-yellow-400">
|
|
164
|
-
+ New Skill
|
|
165
|
-
</button>
|
|
166
|
-
</div>
|
|
167
|
-
${
|
|
168
|
-
installed.length > 0
|
|
169
|
-
? `
|
|
170
|
-
<div class="mb-6">
|
|
171
|
-
<h3 class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-3">
|
|
172
|
-
Installed (${installed.length})
|
|
173
|
-
</h3>
|
|
174
|
-
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3">
|
|
175
|
-
${installed.map((s) => this._renderCard(s, true)).join('')}
|
|
176
|
-
</div>
|
|
177
|
-
</div>
|
|
178
|
-
`
|
|
179
|
-
: ''
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
<div class="mb-6">
|
|
183
|
-
<h3 class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-3">
|
|
184
|
-
Available (${available.length})
|
|
185
|
-
</h3>
|
|
186
|
-
${
|
|
187
|
-
available.length > 0
|
|
188
|
-
? `
|
|
189
|
-
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3">
|
|
190
|
-
${available.map((s) => this._renderCard(s, false)).join('')}
|
|
191
|
-
</div>
|
|
192
|
-
`
|
|
193
|
-
: `
|
|
194
|
-
<p class="text-gray-500 text-sm">
|
|
195
|
-
${this.searchQuery ? 'No skills match your search.' : 'Loading catalog...'}
|
|
196
|
-
</p>
|
|
197
|
-
`
|
|
198
|
-
}
|
|
199
|
-
</div>
|
|
200
|
-
`;
|
|
201
|
-
|
|
202
|
-
// Bind card actions
|
|
203
|
-
container.querySelectorAll<HTMLButtonElement>('[data-action]').forEach((btn) => {
|
|
204
|
-
btn.addEventListener('click', (e: MouseEvent) => {
|
|
205
|
-
e.stopPropagation();
|
|
206
|
-
const action = btn.dataset.action;
|
|
207
|
-
const id = btn.dataset.id;
|
|
208
|
-
const source = btn.dataset.source;
|
|
209
|
-
if (!action || !id || !source) {
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
if (action === 'install') {
|
|
213
|
-
this.install(source, id);
|
|
214
|
-
} else if (action === 'uninstall') {
|
|
215
|
-
this.uninstall(source, id);
|
|
216
|
-
} else if (action === 'toggle') {
|
|
217
|
-
this.toggle(source, id, btn.dataset.enabled !== 'true');
|
|
218
|
-
} else if (action === 'edit') {
|
|
219
|
-
this.editInSkillLab(source, id);
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
// Bind + New button
|
|
225
|
-
const newBtn = container.querySelector<HTMLButtonElement>('#skills-new-btn');
|
|
226
|
-
if (newBtn) {
|
|
227
|
-
newBtn.addEventListener('click', () => this.openNewSkillLab());
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Bind card click for detail
|
|
231
|
-
container.querySelectorAll<HTMLElement>('[data-skill-card]').forEach((card) => {
|
|
232
|
-
card.addEventListener('click', () => {
|
|
233
|
-
const source = card.dataset.source;
|
|
234
|
-
const id = card.dataset.id;
|
|
235
|
-
if (!source || !id) {
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
this.showDetail(source, id);
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
},
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Filter skills by current filter + search query
|
|
245
|
-
*/
|
|
246
|
-
_filterSkills(skills: SkillItem[]): SkillItem[] {
|
|
247
|
-
let filtered = skills;
|
|
248
|
-
|
|
249
|
-
// Source filter
|
|
250
|
-
if (this.currentFilter !== 'all' && this.currentFilter !== 'installed') {
|
|
251
|
-
filtered = filtered.filter((s) => s.source === this.currentFilter);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Search filter
|
|
255
|
-
if (this.searchQuery) {
|
|
256
|
-
const q = this.searchQuery.toLowerCase();
|
|
257
|
-
filtered = filtered.filter(
|
|
258
|
-
(s) =>
|
|
259
|
-
s.name.toLowerCase().includes(q) ||
|
|
260
|
-
(s.description || '').toLowerCase().includes(q) ||
|
|
261
|
-
s.id.toLowerCase().includes(q)
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return filtered;
|
|
266
|
-
},
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Render a single skill card
|
|
270
|
-
*/
|
|
271
|
-
_renderCard(skill: SkillItem, isInstalled: boolean): string {
|
|
272
|
-
const sourceColors = {
|
|
273
|
-
mama: 'bg-mama-yellow/20 text-yellow-700',
|
|
274
|
-
cowork: 'bg-blue-100 text-blue-700',
|
|
275
|
-
external: 'bg-purple-100 text-purple-700',
|
|
276
|
-
};
|
|
277
|
-
const badgeClass = sourceColors[skill.source] || 'bg-gray-100 text-gray-600';
|
|
278
|
-
const enabledClass = skill.enabled !== false ? 'border-green-300' : 'border-gray-200';
|
|
279
|
-
|
|
280
|
-
return `
|
|
281
|
-
<div class="bg-white rounded-lg border ${enabledClass} p-3 cursor-pointer
|
|
282
|
-
hover:border-mama-yellow hover:shadow-md transition-all"
|
|
283
|
-
data-skill-card data-id="${this._escapeHtml(skill.id)}" data-source="${this._escapeHtml(skill.source)}">
|
|
284
|
-
<div class="flex items-start justify-between mb-2">
|
|
285
|
-
<h4 class="font-medium text-sm text-gray-900 truncate flex-1">${this._escapeHtml(skill.name)}</h4>
|
|
286
|
-
<span class="text-[10px] px-1.5 py-0.5 rounded ${badgeClass} ml-2 whitespace-nowrap font-medium">
|
|
287
|
-
${this._escapeHtml(skill.source)}
|
|
288
|
-
</span>
|
|
289
|
-
</div>
|
|
290
|
-
<p class="text-xs text-gray-500 line-clamp-2 mb-3">${this._escapeHtml(skill.description || '')}</p>
|
|
291
|
-
<div class="flex items-center justify-between">
|
|
292
|
-
${
|
|
293
|
-
isInstalled
|
|
294
|
-
? `
|
|
295
|
-
<button data-action="toggle" data-id="${this._escapeHtml(skill.id)}" data-source="${this._escapeHtml(skill.source)}"
|
|
296
|
-
data-enabled="${skill.enabled !== false}"
|
|
297
|
-
class="text-xs px-2 py-1 rounded ${skill.enabled !== false ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-500'}">
|
|
298
|
-
${skill.enabled !== false ? 'Enabled' : 'Disabled'}
|
|
299
|
-
</button>
|
|
300
|
-
<div class="flex gap-1">
|
|
301
|
-
<button data-action="edit" data-id="${this._escapeHtml(skill.id)}" data-source="${this._escapeHtml(skill.source)}"
|
|
302
|
-
class="text-xs px-2 py-1 rounded bg-blue-100 text-blue-600 hover:bg-blue-200">
|
|
303
|
-
Edit
|
|
304
|
-
</button>
|
|
305
|
-
<button data-action="uninstall" data-id="${this._escapeHtml(skill.id)}" data-source="${this._escapeHtml(skill.source)}"
|
|
306
|
-
class="text-xs px-2 py-1 rounded bg-red-100 text-red-600 hover:bg-red-200">
|
|
307
|
-
Remove
|
|
308
|
-
</button>
|
|
309
|
-
</div>
|
|
310
|
-
`
|
|
311
|
-
: `
|
|
312
|
-
<span></span>
|
|
313
|
-
<button data-action="install" data-id="${this._escapeHtml(skill.id)}" data-source="${this._escapeHtml(skill.source)}"
|
|
314
|
-
class="text-xs px-2 py-1 rounded bg-mama-yellow text-mama-black hover:bg-mama-yellow-hover font-medium">
|
|
315
|
-
Install
|
|
316
|
-
</button>
|
|
317
|
-
`
|
|
318
|
-
}
|
|
319
|
-
</div>
|
|
320
|
-
</div>
|
|
321
|
-
`;
|
|
322
|
-
},
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Install a skill
|
|
326
|
-
*/
|
|
327
|
-
async install(source: string, name: string): Promise<void> {
|
|
328
|
-
try {
|
|
329
|
-
const btn = document.querySelector<HTMLButtonElement>(
|
|
330
|
-
`[data-action="install"][data-id="${CSS.escape(name)}"][data-source="${CSS.escape(source)}"]`
|
|
331
|
-
);
|
|
332
|
-
if (btn) {
|
|
333
|
-
btn.textContent = 'Installing...';
|
|
334
|
-
btn.disabled = true;
|
|
335
|
-
}
|
|
336
|
-
await API.installSkill(source, name);
|
|
337
|
-
await this.loadSkills();
|
|
338
|
-
this.render();
|
|
339
|
-
} catch (error) {
|
|
340
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
341
|
-
logger.error('Install failed:', message);
|
|
342
|
-
alert(`Failed to install ${name}: ${message}`);
|
|
343
|
-
this.render();
|
|
344
|
-
}
|
|
345
|
-
},
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* Install from GitHub URL
|
|
349
|
-
*/
|
|
350
|
-
async installFromUrl(url: string): Promise<void> {
|
|
351
|
-
const btn = getElementByIdOrNull<HTMLButtonElement>('skills-url-install-btn');
|
|
352
|
-
const input = getElementByIdOrNull<HTMLInputElement>('skills-url-input');
|
|
353
|
-
try {
|
|
354
|
-
if (btn) {
|
|
355
|
-
btn.textContent = 'Installing...';
|
|
356
|
-
btn.disabled = true;
|
|
357
|
-
}
|
|
358
|
-
await API.installSkillFromUrl(url);
|
|
359
|
-
if (input) {
|
|
360
|
-
input.value = '';
|
|
361
|
-
}
|
|
362
|
-
await this.loadSkills();
|
|
363
|
-
this.render();
|
|
364
|
-
} catch (error) {
|
|
365
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
366
|
-
logger.error('URL install failed:', message);
|
|
367
|
-
alert(`Failed to install from URL: ${message}`);
|
|
368
|
-
} finally {
|
|
369
|
-
if (btn) {
|
|
370
|
-
btn.textContent = 'Install URL';
|
|
371
|
-
btn.disabled = false;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
},
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* Uninstall a skill
|
|
378
|
-
*/
|
|
379
|
-
async uninstall(source: string, name: string): Promise<void> {
|
|
380
|
-
if (!confirm(`Remove skill "${name}"?`)) {
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
try {
|
|
385
|
-
await API.uninstallSkill(name, source);
|
|
386
|
-
await this.loadSkills();
|
|
387
|
-
this.render();
|
|
388
|
-
} catch (error) {
|
|
389
|
-
logger.error('Uninstall failed:', error instanceof Error ? error.message : String(error));
|
|
390
|
-
}
|
|
391
|
-
},
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* Toggle skill enabled/disabled
|
|
395
|
-
*/
|
|
396
|
-
async toggle(source: string, name: string, enabled: boolean): Promise<void> {
|
|
397
|
-
try {
|
|
398
|
-
await API.toggleSkill(name, enabled, source);
|
|
399
|
-
// Update local state
|
|
400
|
-
const skill = this.installed.find((s) => s.id === name && s.source === source);
|
|
401
|
-
if (skill) {
|
|
402
|
-
skill.enabled = enabled;
|
|
403
|
-
}
|
|
404
|
-
this.render();
|
|
405
|
-
} catch (error) {
|
|
406
|
-
logger.error('Toggle failed:', error instanceof Error ? error.message : String(error));
|
|
407
|
-
}
|
|
408
|
-
},
|
|
409
|
-
|
|
410
|
-
/**
|
|
411
|
-
* Show skill detail modal
|
|
412
|
-
*/
|
|
413
|
-
async showDetail(source: string, name: string): Promise<void> {
|
|
414
|
-
const modal = getElementByIdOrNull<HTMLElement>('skill-detail-modal');
|
|
415
|
-
const modalContent = getElementByIdOrNull<HTMLElement>('skill-detail-content');
|
|
416
|
-
if (!modal || !modalContent) {
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
modalContent.innerHTML = '<p class="text-gray-500">Loading...</p>';
|
|
421
|
-
modal.classList.remove('hidden');
|
|
422
|
-
|
|
423
|
-
try {
|
|
424
|
-
const { content } = await API.getSkillContent(name, source);
|
|
425
|
-
modalContent.innerHTML = `
|
|
426
|
-
<div class="flex items-center justify-between mb-4">
|
|
427
|
-
<h2 class="text-lg font-bold text-gray-900">${this._escapeHtml(name)}</h2>
|
|
428
|
-
<span class="text-[10px] px-2 py-1 rounded bg-gray-100 text-gray-600">${this._escapeHtml(source)}</span>
|
|
429
|
-
</div>
|
|
430
|
-
<div class="prose prose-sm max-w-none">
|
|
431
|
-
${this._renderMarkdown(content)}
|
|
432
|
-
</div>
|
|
433
|
-
`;
|
|
434
|
-
} catch (error) {
|
|
435
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
436
|
-
modalContent.innerHTML = `<p class="text-red-600">Failed to load: ${this._escapeHtml(message)}</p>`;
|
|
437
|
-
}
|
|
438
|
-
},
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* Close detail modal
|
|
442
|
-
*/
|
|
443
|
-
closeDetail(): void {
|
|
444
|
-
const modal = getElementByIdOrNull<HTMLElement>('skill-detail-modal');
|
|
445
|
-
if (modal) {
|
|
446
|
-
modal.classList.add('hidden');
|
|
447
|
-
}
|
|
448
|
-
},
|
|
449
|
-
|
|
450
|
-
/**
|
|
451
|
-
* Open Skill Lab with existing skill content for editing
|
|
452
|
-
*/
|
|
453
|
-
async editInSkillLab(_source: string, id: string): Promise<void> {
|
|
454
|
-
alert(`Skill editing is available via CLI: mama skill edit ${id}`);
|
|
455
|
-
},
|
|
456
|
-
|
|
457
|
-
/**
|
|
458
|
-
* Open Skill Lab with empty state for new skill creation
|
|
459
|
-
*/
|
|
460
|
-
openNewSkillLab(): void {
|
|
461
|
-
alert('Create new skills via CLI: mama skill create');
|
|
462
|
-
},
|
|
463
|
-
|
|
464
|
-
/**
|
|
465
|
-
* Escape HTML special characters
|
|
466
|
-
*/
|
|
467
|
-
_escapeHtml(unsafe: unknown): string {
|
|
468
|
-
if (!unsafe) {
|
|
469
|
-
return '';
|
|
470
|
-
}
|
|
471
|
-
return unsafe
|
|
472
|
-
.toString()
|
|
473
|
-
.replace(/&/g, '&')
|
|
474
|
-
.replace(/</g, '<')
|
|
475
|
-
.replace(/>/g, '>')
|
|
476
|
-
.replace(/"/g, '"')
|
|
477
|
-
.replace(/'/g, ''');
|
|
478
|
-
},
|
|
479
|
-
|
|
480
|
-
/**
|
|
481
|
-
* Render markdown to HTML using marked.js, with sanitization
|
|
482
|
-
*/
|
|
483
|
-
_renderMarkdown(md: string): string {
|
|
484
|
-
if (!md) {
|
|
485
|
-
return '';
|
|
486
|
-
}
|
|
487
|
-
// Remove frontmatter
|
|
488
|
-
md = md.replace(/^---\n[\s\S]*?\n---\n/, '');
|
|
489
|
-
return renderSafeMarkdown(md);
|
|
490
|
-
},
|
|
491
|
-
};
|