@kolbo/mcp 1.52.1 → 1.53.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/package.json +1 -1
- package/src/apps/index.js +17 -0
- package/src/apps/theme.js +3 -2
- package/src/apps/widgets/list.js +101 -0
- package/src/apps/widgets/mediaGrid.js +26 -15
- package/src/tools/agents.js +25 -5
- package/src/tools/docs.js +21 -2
- package/src/tools/media.js +19 -9
- package/src/tools/projects.js +60 -13
- package/src/tools/visual_dna.js +18 -1
package/package.json
CHANGED
package/src/apps/index.js
CHANGED
|
@@ -21,6 +21,7 @@ const { mediaGridWidgetHtml } = require('./widgets/mediaGrid');
|
|
|
21
21
|
const { catalogWidgetHtml } = require('./widgets/catalog');
|
|
22
22
|
const { transcriptWidgetHtml } = require('./widgets/transcript');
|
|
23
23
|
const { uploadWidgetHtml } = require('./widgets/upload');
|
|
24
|
+
const { listWidgetHtml } = require('./widgets/list');
|
|
24
25
|
|
|
25
26
|
const UI = {
|
|
26
27
|
generation: 'ui://kolbo/generation.html',
|
|
@@ -28,6 +29,7 @@ const UI = {
|
|
|
28
29
|
catalog: 'ui://kolbo/catalog.html',
|
|
29
30
|
transcript: 'ui://kolbo/transcript.html',
|
|
30
31
|
upload: 'ui://kolbo/upload.html',
|
|
32
|
+
list: 'ui://kolbo/list.html',
|
|
31
33
|
};
|
|
32
34
|
|
|
33
35
|
const WIDGET_BUILDERS = {
|
|
@@ -36,6 +38,7 @@ const WIDGET_BUILDERS = {
|
|
|
36
38
|
[UI.catalog]: catalogWidgetHtml,
|
|
37
39
|
[UI.transcript]: transcriptWidgetHtml,
|
|
38
40
|
[UI.upload]: uploadWidgetHtml,
|
|
41
|
+
[UI.list]: listWidgetHtml,
|
|
39
42
|
};
|
|
40
43
|
|
|
41
44
|
// Widgets are pure functions of source — build once per process.
|
|
@@ -93,6 +96,7 @@ function registerApps(server) {
|
|
|
93
96
|
[UI.catalog, 'Kolbo Model Catalog Widget'],
|
|
94
97
|
[UI.transcript, 'Kolbo Transcription Widget'],
|
|
95
98
|
[UI.upload, 'Kolbo Upload Widget'],
|
|
99
|
+
[UI.list, 'Kolbo List Widget'],
|
|
96
100
|
]) {
|
|
97
101
|
registerAppResource(
|
|
98
102
|
server, name, uri,
|
|
@@ -292,8 +296,21 @@ const TOOL_WIDGETS = {
|
|
|
292
296
|
list_visual_dnas: UI.mediaGrid,
|
|
293
297
|
list_moodboards: UI.mediaGrid,
|
|
294
298
|
shorts_analyze: UI.mediaGrid,
|
|
299
|
+
// NOTE: list_color_palettes' handler has always called uiResult(UI.mediaGrid, ...)
|
|
300
|
+
// (see color_palettes.js) but was missing here — hosts that prepare the widget
|
|
301
|
+
// iframe from the tool DECLARATION (claude.ai reads tools/list, not the result)
|
|
302
|
+
// never saw it as widget-carrying. Result-level _meta alone isn't enough.
|
|
303
|
+
list_color_palettes: UI.mediaGrid,
|
|
295
304
|
// upload widget
|
|
296
305
|
media_upload_widget: UI.upload,
|
|
306
|
+
// generic list widget — flat record lists with no natural thumbnail
|
|
307
|
+
list_projects: UI.list,
|
|
308
|
+
list_sessions: UI.list,
|
|
309
|
+
list_project_context: UI.list,
|
|
310
|
+
list_agents: UI.list,
|
|
311
|
+
list_docs: UI.list,
|
|
312
|
+
list_media_folders: UI.list,
|
|
313
|
+
list_visual_dna_folders: UI.list,
|
|
297
314
|
};
|
|
298
315
|
|
|
299
316
|
function attachToolWidgetMeta(server) {
|
package/src/apps/theme.js
CHANGED
|
@@ -242,8 +242,9 @@ html.k-fullscreen .k-actions { flex: none; padding-top: 8px; }
|
|
|
242
242
|
.k-cell { position: relative; border-radius: 10px; overflow: hidden; border: 1px solid var(--border);
|
|
243
243
|
background: var(--surface); cursor: pointer; transition: transform 250ms var(--spring); }
|
|
244
244
|
.k-cell:hover { transform: translateY(-2px) scale(1.01); }
|
|
245
|
-
.k-cell .k-cell-media { aspect-ratio: 1; background: #000; }
|
|
245
|
+
.k-cell .k-cell-media { position: relative; aspect-ratio: 1; background: #000; }
|
|
246
246
|
.k-cell .k-cell-media img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
|
247
|
+
.k-cell-play { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
|
|
247
248
|
.k-cell .k-cell-label { padding: 6px 8px; font-size: 11px; color: var(--text-muted);
|
|
248
249
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
249
250
|
.k-cell .k-cell-sub { padding: 0 8px 7px; font-size: 10px; color: var(--text-faint);
|
|
@@ -274,7 +275,7 @@ html.k-fullscreen .k-actions { flex: none; padding-top: 8px; }
|
|
|
274
275
|
@media (max-width: 520px) {
|
|
275
276
|
.k-generated-audio { grid-template-columns: 36px minmax(0, 1fr); }
|
|
276
277
|
.k-generated-audio .k-audio-art { width: 36px; height: 36px; }
|
|
277
|
-
.k-generated-audio .k-
|
|
278
|
+
.k-generated-audio .k-btn { grid-column: 2; justify-self: start; }
|
|
278
279
|
.k-generated-audio .k-audio-player { grid-column: 1 / -1; }
|
|
279
280
|
}
|
|
280
281
|
.k-play {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { widgetPage } = require('../html');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generic list widget — for tools that return a flat list of named records
|
|
7
|
+
* with no natural thumbnail (projects, sessions, agents, docs, folders,
|
|
8
|
+
* knowledge-base sources). Reuses the exact row shell mediaGrid's audio rows
|
|
9
|
+
* and catalog's model rows already use (`.k-audio-row` / `.k-audio-meta` /
|
|
10
|
+
* `.k-chip` / `.k-btn` — no new CSS needed).
|
|
11
|
+
*
|
|
12
|
+
* structuredContent contract:
|
|
13
|
+
* {
|
|
14
|
+
* widget: 'list',
|
|
15
|
+
* title: 'Your Projects',
|
|
16
|
+
* items: [{
|
|
17
|
+
* id, title, subtitle, // main two lines
|
|
18
|
+
* badge, // small pill, e.g. role/status ("owner", "default", "shared")
|
|
19
|
+
* meta, // small trailing text, e.g. a date or count
|
|
20
|
+
* open_url, // optional — renders an "Open" button (openLink)
|
|
21
|
+
* use_hint // optional — row becomes clickable, sends this on click
|
|
22
|
+
* }],
|
|
23
|
+
* total
|
|
24
|
+
* }
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const BODY = `
|
|
28
|
+
<div class="k-card">
|
|
29
|
+
<div class="k-head">
|
|
30
|
+
<span class="k-logo" id="logo"></span>
|
|
31
|
+
<span class="k-title" id="title"></span>
|
|
32
|
+
<span class="k-spacer"></span>
|
|
33
|
+
<span class="k-chip" id="count-chip" style="display:none"></span>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="k-body"><div id="stage" class="k-empty">Loading…</div></div>
|
|
36
|
+
<div class="k-footer"><span>Powered by <a href="#" id="kolbo-link">Kolbo.AI</a></span></div>
|
|
37
|
+
</div>
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
const SCRIPT = `
|
|
41
|
+
el('logo').innerHTML = KOLBO_LOGO + '<span>Kolbo</span>';
|
|
42
|
+
el('kolbo-link').onclick = function (e) { e.preventDefault(); window.kolbo.openLink('https://app.kolbo.ai'); };
|
|
43
|
+
var state = null;
|
|
44
|
+
|
|
45
|
+
function boot(sc) {
|
|
46
|
+
if (!sc || !sc.items) return;
|
|
47
|
+
state = sc;
|
|
48
|
+
el('title').textContent = sc.title || 'List';
|
|
49
|
+
var total = sc.total != null ? sc.total : sc.items.length;
|
|
50
|
+
el('count-chip').style.display = '';
|
|
51
|
+
el('count-chip').textContent = total + (total === 1 ? ' item' : ' items');
|
|
52
|
+
if (!sc.items.length) { el('stage').innerHTML = '<div class="k-empty">Nothing here yet</div>'; return; }
|
|
53
|
+
el('stage').innerHTML = sc.items.slice(0, 40).map(itemHTML).join('');
|
|
54
|
+
el('stage').classList.remove('k-empty');
|
|
55
|
+
wire();
|
|
56
|
+
window.kolbo.notifySize();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function itemHTML(item, i) {
|
|
60
|
+
var clickable = !!item.use_hint;
|
|
61
|
+
return '<div class="k-audio-row" data-i="' + i + '"' + (clickable ? ' style="cursor:pointer"' : '') + '>' +
|
|
62
|
+
monogram(item.title || '?') +
|
|
63
|
+
'<div class="k-audio-meta"><div class="k-audio-title">' + esc(item.title || '') + '</div>' +
|
|
64
|
+
(item.subtitle ? '<div class="k-audio-sub">' + esc(item.subtitle) + '</div>' : '') + '</div>' +
|
|
65
|
+
(item.badge ? '<span class="k-chip" style="flex:none">' + esc(item.badge) + '</span>' : '') +
|
|
66
|
+
(item.meta ? '<span style="flex:none;font-size:10.5px;color:var(--text-faint)">' + esc(item.meta) + '</span>' : '') +
|
|
67
|
+
(item.open_url ? '<button class="k-btn" data-open="' + i + '">Open</button>' : '') +
|
|
68
|
+
'</div>';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function wire() {
|
|
72
|
+
Array.prototype.forEach.call(document.querySelectorAll('[data-open]'), function (b) {
|
|
73
|
+
b.onclick = function (e) {
|
|
74
|
+
e.stopPropagation();
|
|
75
|
+
window.kolbo.openLink(state.items[+b.getAttribute('data-open')].open_url);
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
Array.prototype.forEach.call(document.querySelectorAll('.k-audio-row'), function (row) {
|
|
79
|
+
var item = state.items[+row.getAttribute('data-i')];
|
|
80
|
+
if (!item || !item.use_hint) return;
|
|
81
|
+
row.onclick = function () {
|
|
82
|
+
var msg = item.use_hint.replace('{TITLE}', item.title || '').replace('{ID}', item.id || '');
|
|
83
|
+
window.kolbo.sendMessage(msg);
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
window.kolbo.onToolResult(function (result) {
|
|
89
|
+
var sc = result.structuredContent || structured(result);
|
|
90
|
+
if (sc && sc.items) return boot(sc);
|
|
91
|
+
var card = document.querySelector('.k-card');
|
|
92
|
+
if (card) card.style.display = 'none';
|
|
93
|
+
window.kolbo.notifySize();
|
|
94
|
+
});
|
|
95
|
+
`;
|
|
96
|
+
|
|
97
|
+
function listWidgetHtml() {
|
|
98
|
+
return widgetPage({ title: 'Kolbo List', body: BODY, script: SCRIPT });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
module.exports = { listWidgetHtml };
|
|
@@ -38,7 +38,6 @@ const SCRIPT = `
|
|
|
38
38
|
el('logo').innerHTML = KOLBO_LOGO + '<span>Kolbo</span>';
|
|
39
39
|
el('kolbo-link').onclick = function (e) { e.preventDefault(); window.kolbo.openLink('https://app.kolbo.ai'); };
|
|
40
40
|
var state = null;
|
|
41
|
-
var playing = null;
|
|
42
41
|
|
|
43
42
|
function boot(sc) {
|
|
44
43
|
if (!sc || !sc.items) return;
|
|
@@ -63,12 +62,15 @@ function boot(sc) {
|
|
|
63
62
|
|
|
64
63
|
function cellHTML(item, i) {
|
|
65
64
|
var idx = state.items.indexOf(item);
|
|
65
|
+
var isVideo = item.media_type === 'video' && item.url;
|
|
66
66
|
var media = item.thumbnail
|
|
67
67
|
? '<img src="' + esc(item.thumbnail) + '" loading="lazy" alt="">'
|
|
68
68
|
: '<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-faint);font-size:22px">' +
|
|
69
69
|
kindIcon(item.media_type) + '</div>';
|
|
70
70
|
return '<div class="k-cell" data-i="' + idx + '">' +
|
|
71
|
-
'<div class="k-cell-media">' + media +
|
|
71
|
+
'<div class="k-cell-media">' + media +
|
|
72
|
+
(isVideo ? '<button class="k-play k-cell-play" data-video-play="' + esc(item.url) + '">' + ICONS.play + '</button>' : '') +
|
|
73
|
+
'</div>' +
|
|
72
74
|
'<div class="k-cell-label">' + esc(item.title || '') + '</div>' +
|
|
73
75
|
(item.subtitle ? '<div class="k-cell-sub">' + esc(item.subtitle) + '</div>' : '') +
|
|
74
76
|
'</div>';
|
|
@@ -76,13 +78,14 @@ function cellHTML(item, i) {
|
|
|
76
78
|
|
|
77
79
|
function audioRowHTML(item) {
|
|
78
80
|
var idx = state.items.indexOf(item);
|
|
79
|
-
|
|
81
|
+
var src = item.preview_audio || item.url;
|
|
82
|
+
return '<div class="k-audio-row k-generated-audio" data-i="' + idx + '">' +
|
|
80
83
|
(item.thumbnail ? '<img class="k-audio-art" src="' + esc(item.thumbnail) + '">' : '<div class="k-audio-art"></div>') +
|
|
81
84
|
'<div class="k-audio-meta"><div class="k-audio-title">' + esc(item.title || '') + '</div>' +
|
|
82
85
|
'<div class="k-audio-sub">' + esc(item.subtitle || '') + '</div></div>' +
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
'
|
|
86
|
+
'<button class="k-btn" data-use="' + idx + '">Use</button>' +
|
|
87
|
+
(src ? '<audio class="k-audio-player" src="' + esc(src) + '" controls preload="none" aria-label="Play ' + esc(item.title || '') + '"></audio>' : '') +
|
|
88
|
+
'</div>';
|
|
86
89
|
}
|
|
87
90
|
|
|
88
91
|
function wire() {
|
|
@@ -92,17 +95,25 @@ function wire() {
|
|
|
92
95
|
Array.prototype.forEach.call(document.querySelectorAll('[data-use]'), function (b) {
|
|
93
96
|
b.onclick = function (e) { e.stopPropagation(); useItem(+b.getAttribute('data-use')); };
|
|
94
97
|
});
|
|
95
|
-
|
|
98
|
+
// Native <audio controls> already gives play/pause + a seek bar + duration;
|
|
99
|
+
// just stop clicks on it from bubbling up to the row's "Use" handler, and
|
|
100
|
+
// pause any other preview when a new one starts.
|
|
101
|
+
var players = document.querySelectorAll('.k-audio-player');
|
|
102
|
+
Array.prototype.forEach.call(players, function (player) {
|
|
103
|
+
player.onclick = function (e) { e.stopPropagation(); };
|
|
104
|
+
player.addEventListener('play', function () {
|
|
105
|
+
Array.prototype.forEach.call(players, function (other) { if (other !== player) other.pause(); });
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
// Video cells stay a static thumbnail until the play overlay is clicked,
|
|
109
|
+
// then swap in a native <video controls> for an in-place scrubber.
|
|
110
|
+
Array.prototype.forEach.call(document.querySelectorAll('[data-video-play]'), function (b) {
|
|
96
111
|
b.onclick = function (e) {
|
|
97
112
|
e.stopPropagation();
|
|
98
|
-
var url = b.getAttribute('data-play');
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
playing = new Audio(url);
|
|
103
|
-
playing.play();
|
|
104
|
-
b.innerHTML = ICONS.pause;
|
|
105
|
-
playing.onended = function () { b.innerHTML = ICONS.play; };
|
|
113
|
+
var url = b.getAttribute('data-video-play');
|
|
114
|
+
var holder = b.parentNode;
|
|
115
|
+
holder.innerHTML = '<video src="' + esc(url) + '" controls autoplay playsinline style="width:100%;height:100%;object-fit:cover"></video>';
|
|
116
|
+
holder.querySelector('video').onclick = function (ev) { ev.stopPropagation(); };
|
|
106
117
|
};
|
|
107
118
|
});
|
|
108
119
|
}
|
package/src/tools/agents.js
CHANGED
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
* new OPTIONAL args only. Full rules: ../index.js top-of-file and CLAUDE.md. */
|
|
5
5
|
|
|
6
6
|
const { z } = require('zod');
|
|
7
|
+
const { UI, uiResult, appsEnabled } = require('../apps');
|
|
7
8
|
|
|
8
|
-
function registerAgentTools(server, client) {
|
|
9
|
+
function registerAgentTools(server, client, options = {}) {
|
|
10
|
+
const ui = () => appsEnabled(server, options);
|
|
9
11
|
// ─── list_agents ───────────────────────────────────────────
|
|
10
12
|
server.tool(
|
|
11
13
|
'list_agents',
|
|
@@ -14,10 +16,28 @@ function registerAgentTools(server, client) {
|
|
|
14
16
|
async ({ search }) => {
|
|
15
17
|
const qs = search ? `?search=${encodeURIComponent(search)}` : '';
|
|
16
18
|
const result = await client.get(`/v1/agents${qs}`);
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
const agents = result.agents || [];
|
|
20
|
+
const text = JSON.stringify({
|
|
21
|
+
agents,
|
|
22
|
+
count: result.count || agents.length
|
|
23
|
+
}, null, 2);
|
|
24
|
+
|
|
25
|
+
if (ui()) {
|
|
26
|
+
return uiResult(UI.list, text, {
|
|
27
|
+
widget: 'list',
|
|
28
|
+
title: 'Your Agents',
|
|
29
|
+
items: agents.map(a => ({
|
|
30
|
+
id: a.id,
|
|
31
|
+
title: (a.emoji ? a.emoji + ' ' : '') + a.name,
|
|
32
|
+
subtitle: a.description,
|
|
33
|
+
badge: a.is_global ? 'preset' : null,
|
|
34
|
+
use_hint: 'Use my "{TITLE}" agent (agent_id: {ID}) for this conversation.'
|
|
35
|
+
})),
|
|
36
|
+
total: agents.length
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return { content: [{ type: 'text', text }] };
|
|
21
41
|
}
|
|
22
42
|
);
|
|
23
43
|
|
package/src/tools/docs.js
CHANGED
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
|
|
6
6
|
const { z } = require('zod');
|
|
7
7
|
const { projectIdField } = require('./_shared');
|
|
8
|
+
const { UI, uiResult, appsEnabled } = require('../apps');
|
|
8
9
|
|
|
9
10
|
const CONTENT_GUIDE = 'HTML body content. Use clean semantic HTML the in-app editor understands: <h1>-<h3>, <p>, <ul>/<ol>/<li>, <table>, <blockquote>, <strong>/<em>, <a>. No <script>/<style>/<iframe> (stripped server-side). Write the FULL document yourself — this is where you author the doc.';
|
|
10
11
|
|
|
11
|
-
function registerDocTools(server, client) {
|
|
12
|
+
function registerDocTools(server, client, options = {}) {
|
|
13
|
+
const ui = () => appsEnabled(server, options);
|
|
12
14
|
// ─── create_doc ────────────────────────────────────────────
|
|
13
15
|
server.tool(
|
|
14
16
|
'create_doc',
|
|
@@ -50,7 +52,24 @@ function registerDocTools(server, client) {
|
|
|
50
52
|
if (limit) params.set('limit', String(limit));
|
|
51
53
|
const qs = params.toString();
|
|
52
54
|
const result = await client.get(`/v1/docs${qs ? '?' + qs : ''}`);
|
|
53
|
-
|
|
55
|
+
const docs = result.docs || [];
|
|
56
|
+
const text = JSON.stringify({ docs, pagination: result.pagination || null }, null, 2);
|
|
57
|
+
|
|
58
|
+
if (ui()) {
|
|
59
|
+
return uiResult(UI.list, text, {
|
|
60
|
+
widget: 'list',
|
|
61
|
+
title: 'Your AI Docs',
|
|
62
|
+
items: docs.map(d => ({
|
|
63
|
+
id: d.id,
|
|
64
|
+
title: d.title,
|
|
65
|
+
subtitle: (d.updated_at ? String(d.updated_at).slice(0, 10) : '') + (d.is_shared ? ' · shared' : ''),
|
|
66
|
+
open_url: d.share_url || null
|
|
67
|
+
})),
|
|
68
|
+
total: docs.length
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return { content: [{ type: 'text', text }] };
|
|
54
73
|
}
|
|
55
74
|
);
|
|
56
75
|
|
package/src/tools/media.js
CHANGED
|
@@ -227,15 +227,25 @@ function registerMediaTools(server, client, options = {}) {
|
|
|
227
227
|
{},
|
|
228
228
|
async () => {
|
|
229
229
|
const result = await client.get('/v1/media/folders');
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
230
|
+
const folders = result.folders || [];
|
|
231
|
+
const text = JSON.stringify({ folders, count: result.count || 0 }, null, 2);
|
|
232
|
+
|
|
233
|
+
if (ui()) {
|
|
234
|
+
return uiResult(UI.list, text, {
|
|
235
|
+
widget: 'list',
|
|
236
|
+
title: 'Media Folders',
|
|
237
|
+
items: folders.map(f => ({
|
|
238
|
+
id: f.id,
|
|
239
|
+
title: f.name,
|
|
240
|
+
subtitle: f.description,
|
|
241
|
+
meta: (f.item_count || 0) + (f.item_count === 1 ? ' item' : ' items'),
|
|
242
|
+
use_hint: 'List media in my "{TITLE}" folder (folder_id: {ID}).'
|
|
243
|
+
})),
|
|
244
|
+
total: folders.length
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return { content: [{ type: 'text', text }] };
|
|
239
249
|
}
|
|
240
250
|
);
|
|
241
251
|
|
package/src/tools/projects.js
CHANGED
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
|
|
6
6
|
const { z } = require('zod');
|
|
7
7
|
const { buildProjectUrl } = require('./_shared');
|
|
8
|
+
const { UI, uiResult, appsEnabled } = require('../apps');
|
|
8
9
|
|
|
9
|
-
function registerProjectTools(server, client) {
|
|
10
|
+
function registerProjectTools(server, client, options = {}) {
|
|
11
|
+
const ui = () => appsEnabled(server, options);
|
|
10
12
|
// ─── list_projects ─────────────────────────────────────────
|
|
11
13
|
server.tool(
|
|
12
14
|
'list_projects',
|
|
@@ -21,16 +23,28 @@ function registerProjectTools(server, client) {
|
|
|
21
23
|
is_default: !!p.is_default,
|
|
22
24
|
open_url: buildProjectUrl(p.id, { is_default: !!p.is_default })
|
|
23
25
|
}));
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
const text = JSON.stringify({
|
|
27
|
+
projects,
|
|
28
|
+
count: projects.length,
|
|
29
|
+
_hint: 'Pass the chosen `id` as `project_id` on any generate_* tool to drop the generation into that project. Omit project_id to use the project flagged is_default:true. `open_url` opens that project\'s media in the web app (share it with the user).'
|
|
30
|
+
}, null, 2);
|
|
31
|
+
|
|
32
|
+
if (ui()) {
|
|
33
|
+
return uiResult(UI.list, text, {
|
|
34
|
+
widget: 'list',
|
|
35
|
+
title: 'Your Projects',
|
|
36
|
+
items: projects.map(p => ({
|
|
37
|
+
id: p.id,
|
|
38
|
+
title: p.name,
|
|
39
|
+
subtitle: p.role + (p.is_default ? ' · default' : ''),
|
|
40
|
+
open_url: p.open_url,
|
|
41
|
+
use_hint: 'Use my "{TITLE}" project (project_id: {ID}) for what I do next.'
|
|
42
|
+
})),
|
|
43
|
+
total: projects.length
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return { content: [{ type: 'text', text }] };
|
|
34
48
|
}
|
|
35
49
|
);
|
|
36
50
|
|
|
@@ -131,7 +145,23 @@ function registerProjectTools(server, client) {
|
|
|
131
145
|
if (limit) params.set('limit', String(limit));
|
|
132
146
|
const qs = params.toString();
|
|
133
147
|
const result = await client.get(`/v1/sessions${qs ? '?' + qs : ''}`);
|
|
134
|
-
|
|
148
|
+
const sessions = result.sessions || [];
|
|
149
|
+
const text = JSON.stringify({ sessions, pagination: result.pagination || null }, null, 2);
|
|
150
|
+
|
|
151
|
+
if (ui()) {
|
|
152
|
+
return uiResult(UI.list, text, {
|
|
153
|
+
widget: 'list',
|
|
154
|
+
title: 'Sessions' + (type ? ' — ' + type : ''),
|
|
155
|
+
items: sessions.map(s => ({
|
|
156
|
+
id: s.session_id,
|
|
157
|
+
title: s.name || s.type,
|
|
158
|
+
subtitle: s.type + (s.updated_at ? ' · ' + String(s.updated_at).slice(0, 10) : '')
|
|
159
|
+
})),
|
|
160
|
+
total: sessions.length
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return { content: [{ type: 'text', text }] };
|
|
135
165
|
}
|
|
136
166
|
);
|
|
137
167
|
|
|
@@ -162,7 +192,24 @@ function registerProjectTools(server, client) {
|
|
|
162
192
|
{ project_id: z.string().describe('Project ObjectId.') },
|
|
163
193
|
async ({ project_id }) => {
|
|
164
194
|
const result = await client.get(`/v1/projects/${encodeURIComponent(project_id)}/context`);
|
|
165
|
-
|
|
195
|
+
const sources = result.sources || [];
|
|
196
|
+
const text = JSON.stringify({ sources, count: result.count || 0 }, null, 2);
|
|
197
|
+
|
|
198
|
+
if (ui()) {
|
|
199
|
+
return uiResult(UI.list, text, {
|
|
200
|
+
widget: 'list',
|
|
201
|
+
title: 'Project Knowledge Base',
|
|
202
|
+
items: sources.map(s => ({
|
|
203
|
+
id: s.file_key,
|
|
204
|
+
title: s.title || s.type,
|
|
205
|
+
subtitle: s.type + ' · ' + s.status,
|
|
206
|
+
open_url: s.url || null
|
|
207
|
+
})),
|
|
208
|
+
total: sources.length
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return { content: [{ type: 'text', text }] };
|
|
166
213
|
}
|
|
167
214
|
);
|
|
168
215
|
|
package/src/tools/visual_dna.js
CHANGED
|
@@ -210,7 +210,24 @@ function registerVisualDnaTools(server, client, options = {}) {
|
|
|
210
210
|
{},
|
|
211
211
|
async () => {
|
|
212
212
|
const result = await client.get('/v1/visual-dna/folders');
|
|
213
|
-
|
|
213
|
+
const folders = result.folders || [];
|
|
214
|
+
const text = JSON.stringify({ folders, count: result.count || 0 }, null, 2);
|
|
215
|
+
|
|
216
|
+
if (ui()) {
|
|
217
|
+
return uiResult(UI.list, text, {
|
|
218
|
+
widget: 'list',
|
|
219
|
+
title: 'Visual DNA Folders',
|
|
220
|
+
items: folders.map(f => ({
|
|
221
|
+
id: f.id,
|
|
222
|
+
title: f.name,
|
|
223
|
+
meta: f.item_count != null ? (f.item_count + (f.item_count === 1 ? ' DNA' : ' DNAs')) : null,
|
|
224
|
+
use_hint: 'List Visual DNAs in my "{TITLE}" folder (folder_id: {ID}).'
|
|
225
|
+
})),
|
|
226
|
+
total: folders.length
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return { content: [{ type: 'text', text }] };
|
|
214
231
|
}
|
|
215
232
|
);
|
|
216
233
|
|