@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.
Files changed (171) hide show
  1. package/dist/agent/agent-loop.d.ts +25 -0
  2. package/dist/agent/agent-loop.d.ts.map +1 -1
  3. package/dist/agent/agent-loop.js +67 -14
  4. package/dist/agent/agent-loop.js.map +1 -1
  5. package/dist/agent/code-act/host-bridge.d.ts.map +1 -1
  6. package/dist/agent/code-act/host-bridge.js +98 -0
  7. package/dist/agent/code-act/host-bridge.js.map +1 -1
  8. package/dist/agent/code-act/type-definition-generator.d.ts.map +1 -1
  9. package/dist/agent/code-act/type-definition-generator.js +0 -1
  10. package/dist/agent/code-act/type-definition-generator.js.map +1 -1
  11. package/dist/agent/gateway-tool-executor.d.ts +36 -1
  12. package/dist/agent/gateway-tool-executor.d.ts.map +1 -1
  13. package/dist/agent/gateway-tool-executor.js +938 -54
  14. package/dist/agent/gateway-tool-executor.js.map +1 -1
  15. package/dist/agent/gateway-tools.md +9 -0
  16. package/dist/agent/managed-agent-runtime-sync.d.ts +36 -0
  17. package/dist/agent/managed-agent-runtime-sync.d.ts.map +1 -0
  18. package/dist/agent/managed-agent-runtime-sync.js +207 -0
  19. package/dist/agent/managed-agent-runtime-sync.js.map +1 -0
  20. package/dist/agent/managed-agent-validation.d.ts +4 -0
  21. package/dist/agent/managed-agent-validation.d.ts.map +1 -0
  22. package/dist/agent/managed-agent-validation.js +84 -0
  23. package/dist/agent/managed-agent-validation.js.map +1 -0
  24. package/dist/agent/os-agent-capabilities.md +400 -0
  25. package/dist/agent/skill-loader.d.ts +2 -0
  26. package/dist/agent/skill-loader.d.ts.map +1 -1
  27. package/dist/agent/skill-loader.js +28 -0
  28. package/dist/agent/skill-loader.js.map +1 -1
  29. package/dist/agent/tool-registry.d.ts.map +1 -1
  30. package/dist/agent/tool-registry.js +66 -0
  31. package/dist/agent/tool-registry.js.map +1 -1
  32. package/dist/agent/types.d.ts +2 -1
  33. package/dist/agent/types.d.ts.map +1 -1
  34. package/dist/agent/types.js.map +1 -1
  35. package/dist/api/agent-handler.d.ts +34 -0
  36. package/dist/api/agent-handler.d.ts.map +1 -0
  37. package/dist/api/agent-handler.js +216 -0
  38. package/dist/api/agent-handler.js.map +1 -0
  39. package/dist/api/graph-api-types.d.ts +4 -0
  40. package/dist/api/graph-api-types.d.ts.map +1 -1
  41. package/dist/api/graph-api.d.ts +2 -2
  42. package/dist/api/graph-api.d.ts.map +1 -1
  43. package/dist/api/graph-api.js +480 -51
  44. package/dist/api/graph-api.js.map +1 -1
  45. package/dist/api/index.d.ts.map +1 -1
  46. package/dist/api/index.js +4 -0
  47. package/dist/api/index.js.map +1 -1
  48. package/dist/api/token-handler.d.ts +1 -0
  49. package/dist/api/token-handler.d.ts.map +1 -1
  50. package/dist/api/token-handler.js +4 -3
  51. package/dist/api/token-handler.js.map +1 -1
  52. package/dist/api/ui-command-handler.d.ts +48 -0
  53. package/dist/api/ui-command-handler.d.ts.map +1 -0
  54. package/dist/api/ui-command-handler.js +160 -0
  55. package/dist/api/ui-command-handler.js.map +1 -0
  56. package/dist/cli/commands/start.d.ts.map +1 -1
  57. package/dist/cli/commands/start.js +127 -1
  58. package/dist/cli/commands/start.js.map +1 -1
  59. package/dist/cli/config/config-manager.d.ts.map +1 -1
  60. package/dist/cli/config/config-manager.js +16 -31
  61. package/dist/cli/config/config-manager.js.map +1 -1
  62. package/dist/cli/runtime/agent-loop-init.d.ts.map +1 -1
  63. package/dist/cli/runtime/agent-loop-init.js +31 -7
  64. package/dist/cli/runtime/agent-loop-init.js.map +1 -1
  65. package/dist/cli/runtime/api-routes-init.d.ts +3 -0
  66. package/dist/cli/runtime/api-routes-init.d.ts.map +1 -1
  67. package/dist/cli/runtime/api-routes-init.js +283 -34
  68. package/dist/cli/runtime/api-routes-init.js.map +1 -1
  69. package/dist/cli/runtime/gateway-init.d.ts +2 -1
  70. package/dist/cli/runtime/gateway-init.d.ts.map +1 -1
  71. package/dist/cli/runtime/gateway-init.js +5 -1
  72. package/dist/cli/runtime/gateway-init.js.map +1 -1
  73. package/dist/connectors/framework/raw-store.d.ts +4 -0
  74. package/dist/connectors/framework/raw-store.d.ts.map +1 -1
  75. package/dist/connectors/framework/raw-store.js +33 -10
  76. package/dist/connectors/framework/raw-store.js.map +1 -1
  77. package/dist/db/agent-store.d.ts +115 -0
  78. package/dist/db/agent-store.d.ts.map +1 -0
  79. package/dist/db/agent-store.js +248 -0
  80. package/dist/db/agent-store.js.map +1 -0
  81. package/dist/db/migrations/agent-activity-validation-columns.d.ts +3 -0
  82. package/dist/db/migrations/agent-activity-validation-columns.d.ts.map +1 -0
  83. package/dist/db/migrations/agent-activity-validation-columns.js +22 -0
  84. package/dist/db/migrations/agent-activity-validation-columns.js.map +1 -0
  85. package/dist/db/migrations/agent-metrics-response-avg.d.ts +3 -0
  86. package/dist/db/migrations/agent-metrics-response-avg.d.ts.map +1 -0
  87. package/dist/db/migrations/agent-metrics-response-avg.js +19 -0
  88. package/dist/db/migrations/agent-metrics-response-avg.js.map +1 -0
  89. package/dist/db/migrations/agent-store-tables.d.ts +3 -0
  90. package/dist/db/migrations/agent-store-tables.d.ts.map +1 -0
  91. package/dist/db/migrations/agent-store-tables.js +59 -0
  92. package/dist/db/migrations/agent-store-tables.js.map +1 -0
  93. package/dist/db/migrations/token-usage-agent-version.d.ts +3 -0
  94. package/dist/db/migrations/token-usage-agent-version.d.ts.map +1 -0
  95. package/dist/db/migrations/token-usage-agent-version.js +16 -0
  96. package/dist/db/migrations/token-usage-agent-version.js.map +1 -0
  97. package/dist/db/migrations/validation-session-tables.d.ts +3 -0
  98. package/dist/db/migrations/validation-session-tables.d.ts.map +1 -0
  99. package/dist/db/migrations/validation-session-tables.js +59 -0
  100. package/dist/db/migrations/validation-session-tables.js.map +1 -0
  101. package/dist/gateways/message-router.d.ts +10 -0
  102. package/dist/gateways/message-router.d.ts.map +1 -1
  103. package/dist/gateways/message-router.js +188 -14
  104. package/dist/gateways/message-router.js.map +1 -1
  105. package/dist/gateways/types.d.ts +1 -1
  106. package/dist/gateways/types.d.ts.map +1 -1
  107. package/dist/multi-agent/agent-process-manager.js +1 -1
  108. package/dist/multi-agent/agent-process-manager.js.map +1 -1
  109. package/dist/multi-agent/conductor-persona.d.ts +13 -0
  110. package/dist/multi-agent/conductor-persona.d.ts.map +1 -0
  111. package/dist/multi-agent/conductor-persona.js +157 -0
  112. package/dist/multi-agent/conductor-persona.js.map +1 -0
  113. package/dist/multi-agent/dashboard-agent-persona.d.ts +1 -1
  114. package/dist/multi-agent/dashboard-agent-persona.d.ts.map +1 -1
  115. package/dist/multi-agent/dashboard-agent-persona.js +7 -3
  116. package/dist/multi-agent/dashboard-agent-persona.js.map +1 -1
  117. package/dist/multi-agent/delegation-manager.d.ts +5 -0
  118. package/dist/multi-agent/delegation-manager.d.ts.map +1 -1
  119. package/dist/multi-agent/delegation-manager.js +37 -0
  120. package/dist/multi-agent/delegation-manager.js.map +1 -1
  121. package/dist/multi-agent/ultrawork.d.ts +3 -0
  122. package/dist/multi-agent/ultrawork.d.ts.map +1 -1
  123. package/dist/multi-agent/ultrawork.js +9 -0
  124. package/dist/multi-agent/ultrawork.js.map +1 -1
  125. package/dist/validation/session-service.d.ts +72 -0
  126. package/dist/validation/session-service.d.ts.map +1 -0
  127. package/dist/validation/session-service.js +298 -0
  128. package/dist/validation/session-service.js.map +1 -0
  129. package/dist/validation/store.d.ts +25 -0
  130. package/dist/validation/store.d.ts.map +1 -0
  131. package/dist/validation/store.js +200 -0
  132. package/dist/validation/store.js.map +1 -0
  133. package/dist/validation/types.d.ts +119 -0
  134. package/dist/validation/types.d.ts.map +1 -0
  135. package/dist/validation/types.js +57 -0
  136. package/dist/validation/types.js.map +1 -0
  137. package/package.json +3 -3
  138. package/public/viewer/js/modules/agents.js +1148 -0
  139. package/public/viewer/js/modules/chat.js +20 -11
  140. package/public/viewer/js/modules/connector-feed.js +35 -0
  141. package/public/viewer/js/modules/dashboard.js +49 -0
  142. package/public/viewer/js/modules/memory.js +32 -0
  143. package/public/viewer/js/modules/settings.js +34 -79
  144. package/public/viewer/js/modules/wiki.js +59 -4
  145. package/public/viewer/js/utils/api.js +70 -0
  146. package/public/viewer/js/utils/dom.js +3 -0
  147. package/public/viewer/js/utils/ui-commands.js +93 -0
  148. package/public/viewer/log-viewer.html +2 -2
  149. package/public/viewer/src/modules/agents.ts +1299 -0
  150. package/public/viewer/src/modules/chat.ts +23 -14
  151. package/public/viewer/src/modules/connector-feed.ts +35 -0
  152. package/public/viewer/src/modules/dashboard.ts +50 -0
  153. package/public/viewer/src/modules/memory.ts +31 -0
  154. package/public/viewer/src/modules/settings.ts +36 -96
  155. package/public/viewer/src/modules/wiki.ts +73 -6
  156. package/public/viewer/src/types/global.d.ts +0 -9
  157. package/public/viewer/src/utils/api.ts +156 -2
  158. package/public/viewer/src/utils/dom.ts +6 -1
  159. package/public/viewer/src/utils/ui-commands.ts +118 -0
  160. package/public/viewer/viewer.css +105 -10
  161. package/public/viewer/viewer.html +1868 -777
  162. package/scripts/generate-gateway-tools.ts +5 -1
  163. package/public/viewer/js/modules/playground.js +0 -148
  164. package/public/viewer/js/modules/skills.js +0 -451
  165. package/public/viewer/src/modules/playground.ts +0 -173
  166. package/public/viewer/src/modules/skills.ts +0 -491
  167. package/templates/playgrounds/cron-workflow-lab.html +0 -1601
  168. package/templates/playgrounds/mama-log-viewer.html +0 -1341
  169. package/templates/playgrounds/skill-lab-playground.html +0 -1625
  170. package/templates/playgrounds/wave-visualizer.html +0 -694
  171. 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, '&amp;')
474
- .replace(/</g, '&lt;')
475
- .replace(/>/g, '&gt;')
476
- .replace(/"/g, '&quot;')
477
- .replace(/'/g, '&#039;');
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
- };