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