clementine-agent 1.11.3 → 1.12.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/cli/dashboard.js
CHANGED
|
@@ -4402,10 +4402,14 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
|
|
|
4402
4402
|
res.json({ enabled: false, toolkits: [] });
|
|
4403
4403
|
return;
|
|
4404
4404
|
}
|
|
4405
|
-
|
|
4405
|
+
// Fetch live: full catalog + user's connections + auth-config slugs.
|
|
4406
|
+
// This replaces the hardcoded CURATED_TOOLKITS — slug typos are now
|
|
4407
|
+
// impossible because we render directly from Composio's data, and
|
|
4408
|
+
// newly-added services appear automatically.
|
|
4409
|
+
const [catalog, connected, configured] = await Promise.all([
|
|
4410
|
+
c.listAllToolkits(),
|
|
4406
4411
|
c.listConnectedToolkits(),
|
|
4407
4412
|
c.listToolkitSlugsWithAuthConfig(),
|
|
4408
|
-
c.listToolkitMeta(),
|
|
4409
4413
|
]);
|
|
4410
4414
|
const connectionsBySlug = new Map();
|
|
4411
4415
|
for (const conn of connected) {
|
|
@@ -4426,39 +4430,52 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
|
|
|
4426
4430
|
accountAvatarUrl: conn.accountAvatarUrl ?? null,
|
|
4427
4431
|
createdAt: conn.createdAt ?? null,
|
|
4428
4432
|
});
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
+
// Merge: every catalog entry, plus any connection whose slug isn't in
|
|
4434
|
+
// the catalog (defensive — catalog dedupes are rare but possible).
|
|
4435
|
+
const catalogBySlug = new Map(catalog.map(t => [t.slug, t]));
|
|
4436
|
+
const orphanSlugs = [...connectionsBySlug.keys()].filter(s => !catalogBySlug.has(s));
|
|
4437
|
+
const toolkits = [
|
|
4438
|
+
...catalog.map(t => ({
|
|
4433
4439
|
slug: t.slug,
|
|
4434
|
-
displayName: t.
|
|
4440
|
+
displayName: t.name,
|
|
4435
4441
|
authMode: t.authMode,
|
|
4436
4442
|
hasAuthConfig: configured.has(t.slug),
|
|
4437
|
-
logoUrl:
|
|
4438
|
-
description:
|
|
4439
|
-
toolCount:
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
.filter(([slug]) => !c.CURATED_TOOLKITS.some(t => t.slug === slug))
|
|
4445
|
-
.map(([slug, conns]) => {
|
|
4446
|
-
const m = meta.get(slug);
|
|
4447
|
-
// Non-curated: infer auth mode. If a custom auth config exists,
|
|
4448
|
-
// user set it up themselves (BYO). Otherwise it must be managed.
|
|
4449
|
-
const authMode = configured.has(slug) ? 'byo' : 'managed';
|
|
4450
|
-
return {
|
|
4443
|
+
logoUrl: t.logoUrl ?? null,
|
|
4444
|
+
description: t.description ?? null,
|
|
4445
|
+
toolCount: t.toolsCount ?? null,
|
|
4446
|
+
categories: t.categories,
|
|
4447
|
+
connections: (connectionsBySlug.get(t.slug) ?? []).map(toView),
|
|
4448
|
+
})),
|
|
4449
|
+
...orphanSlugs.map(slug => ({
|
|
4451
4450
|
slug,
|
|
4452
|
-
displayName:
|
|
4453
|
-
authMode,
|
|
4451
|
+
displayName: c.displayNameFor(slug),
|
|
4452
|
+
authMode: 'managed',
|
|
4454
4453
|
hasAuthConfig: configured.has(slug),
|
|
4455
|
-
logoUrl:
|
|
4456
|
-
description:
|
|
4457
|
-
toolCount:
|
|
4458
|
-
|
|
4459
|
-
|
|
4454
|
+
logoUrl: null,
|
|
4455
|
+
description: null,
|
|
4456
|
+
toolCount: null,
|
|
4457
|
+
categories: [],
|
|
4458
|
+
connections: (connectionsBySlug.get(slug) ?? []).map(toView),
|
|
4459
|
+
})),
|
|
4460
|
+
];
|
|
4461
|
+
// Featured set = toolkits with active connections (highest signal),
|
|
4462
|
+
// then top toolkits by tool count. Cap at 30 to keep the default
|
|
4463
|
+
// grid focused; the search box covers the rest.
|
|
4464
|
+
const FEATURED_LIMIT = 30;
|
|
4465
|
+
const connectedSlugs = new Set(connectionsBySlug.keys());
|
|
4466
|
+
const featured = [
|
|
4467
|
+
...toolkits.filter(t => connectedSlugs.has(t.slug)),
|
|
4468
|
+
...toolkits
|
|
4469
|
+
.filter(t => !connectedSlugs.has(t.slug))
|
|
4470
|
+
.sort((a, b) => (b.toolCount ?? 0) - (a.toolCount ?? 0))
|
|
4471
|
+
.slice(0, Math.max(0, FEATURED_LIMIT - connectedSlugs.size)),
|
|
4472
|
+
];
|
|
4473
|
+
res.json({
|
|
4474
|
+
enabled: true,
|
|
4475
|
+
toolkits,
|
|
4476
|
+
featured: featured.map(t => t.slug),
|
|
4477
|
+
totalCount: toolkits.length,
|
|
4460
4478
|
});
|
|
4461
|
-
res.json({ enabled: true, toolkits: [...curated, ...extras] });
|
|
4462
4479
|
}
|
|
4463
4480
|
catch (err) {
|
|
4464
4481
|
res.status(500).json({ error: String(err) });
|
|
@@ -25780,53 +25797,104 @@ async function refreshComposioConnections() {
|
|
|
25780
25797
|
container.innerHTML = '<div class="empty-state" style="padding:16px">No toolkits available. Check that your Composio API key is valid.</div>';
|
|
25781
25798
|
return;
|
|
25782
25799
|
}
|
|
25800
|
+
// Stash full catalog in a closure so the search input can re-render
|
|
25801
|
+
// without refetching. Source of truth lives on the window object so
|
|
25802
|
+
// the input's oninput handler can find it.
|
|
25803
|
+
window._composioCatalog = {
|
|
25804
|
+
toolkits: toolkits,
|
|
25805
|
+
featured: new Set(d.featured || []),
|
|
25806
|
+
total: d.totalCount || toolkits.length,
|
|
25807
|
+
};
|
|
25808
|
+
renderComposioCatalog('');
|
|
25809
|
+
} catch (e) {
|
|
25810
|
+
container.innerHTML = '<div class="empty-state" style="color:var(--red);padding:16px">Failed to load Composio toolkits: ' + esc(String(e)) + '</div>';
|
|
25811
|
+
}
|
|
25812
|
+
}
|
|
25783
25813
|
|
|
25784
|
-
|
|
25785
|
-
|
|
25786
|
-
|
|
25787
|
-
|
|
25788
|
-
|
|
25789
|
-
|
|
25790
|
-
|
|
25814
|
+
function renderComposioCatalog(query) {
|
|
25815
|
+
var container = document.getElementById('composio-connections');
|
|
25816
|
+
if (!container) return;
|
|
25817
|
+
var cat = window._composioCatalog;
|
|
25818
|
+
if (!cat) return;
|
|
25819
|
+
var q = (query || '').trim().toLowerCase();
|
|
25820
|
+
var visible;
|
|
25821
|
+
if (q.length === 0) {
|
|
25822
|
+
// Default view: featured set (connected toolkits + most-popular ones).
|
|
25823
|
+
visible = cat.toolkits.filter(function(t) { return cat.featured.has(t.slug); });
|
|
25824
|
+
} else {
|
|
25825
|
+
// Search across slug, name, and description. Cap to 80 to keep DOM
|
|
25826
|
+
// size reasonable when the query matches a generic word.
|
|
25827
|
+
visible = cat.toolkits.filter(function(t) {
|
|
25828
|
+
return t.slug.toLowerCase().indexOf(q) !== -1
|
|
25829
|
+
|| (t.displayName||'').toLowerCase().indexOf(q) !== -1
|
|
25830
|
+
|| (t.description||'').toLowerCase().indexOf(q) !== -1;
|
|
25831
|
+
}).slice(0, 80);
|
|
25832
|
+
}
|
|
25833
|
+
// Always show connected toolkits first within the visible set.
|
|
25834
|
+
visible.sort(function(a, b) {
|
|
25835
|
+
var aConn = (a.connections||[]).some(function(c){ return c.status==='ACTIVE'; }) ? 0 : 1;
|
|
25836
|
+
var bConn = (b.connections||[]).some(function(c){ return c.status==='ACTIVE'; }) ? 0 : 1;
|
|
25837
|
+
if (aConn !== bConn) return aConn - bConn;
|
|
25838
|
+
return (b.toolCount||0) - (a.toolCount||0);
|
|
25839
|
+
});
|
|
25791
25840
|
|
|
25792
|
-
|
|
25793
|
-
|
|
25794
|
-
|
|
25795
|
-
|
|
25796
|
-
|
|
25797
|
-
html += '<div style="width:24px;height:24px;border-radius:4px;background:var(--bg-tertiary);flex-shrink:0"></div>';
|
|
25798
|
-
}
|
|
25799
|
-
html += '<div style="flex:1;min-width:0">';
|
|
25800
|
-
html += '<div style="font-size:13px;font-weight:600;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">' + esc(t.displayName) + '</div>';
|
|
25801
|
-
html += '<div style="font-size:10px;color:var(--text-muted)">' + (isConnected ? connected.length + ' account' + (connected.length !== 1 ? 's' : '') : 'Not connected') + '</div>';
|
|
25802
|
-
html += '</div>';
|
|
25803
|
-
html += '<span style="width:8px;height:8px;border-radius:50%;background:' + statusColor + ';flex-shrink:0"></span>';
|
|
25804
|
-
html += '</div>';
|
|
25841
|
+
var header = '<div style="display:flex;align-items:center;gap:10px;margin-bottom:14px">'
|
|
25842
|
+
+ '<input type="search" id="composio-search" value="' + esc(q) + '" placeholder="Search ' + cat.total + ' toolkits — Gmail, Slack, Notion, …" oninput="renderComposioCatalog(this.value)"'
|
|
25843
|
+
+ ' style="flex:1;padding:8px 12px;border:1px solid var(--border);border-radius:6px;background:var(--bg-input);color:var(--text-primary);font-size:13px">'
|
|
25844
|
+
+ '<span style="font-size:11px;color:var(--text-muted);white-space:nowrap">' + visible.length + (q.length === 0 ? ' featured' : ' match' + (visible.length !== 1 ? 'es' : '')) + ' / ' + cat.total + ' total</span>'
|
|
25845
|
+
+ '</div>';
|
|
25805
25846
|
|
|
25806
|
-
|
|
25807
|
-
|
|
25808
|
-
|
|
25809
|
-
|
|
25810
|
-
|
|
25811
|
-
html += '<span style="flex:1;color:var(--text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">' + esc(label) + '</span>';
|
|
25812
|
-
html += '<button class="btn-sm" onclick="disconnectComposio(\\'' + esc(t.slug) + '\\',\\'' + esc(c.id) + '\\')" style="font-size:10px;padding:2px 6px;background:transparent;border:1px solid var(--border);color:var(--text-muted);border-radius:4px;cursor:pointer">Disconnect</button>';
|
|
25813
|
-
html += '</div>';
|
|
25814
|
-
});
|
|
25815
|
-
html += '</div>';
|
|
25816
|
-
}
|
|
25847
|
+
if (visible.length === 0) {
|
|
25848
|
+
container.innerHTML = header + '<div class="empty-state" style="padding:16px">No toolkits match "' + esc(q) + '". Try a different search.</div>';
|
|
25849
|
+
setTimeout(function() { var s = document.getElementById('composio-search'); if (s) { s.focus(); s.setSelectionRange(s.value.length, s.value.length); } }, 0);
|
|
25850
|
+
return;
|
|
25851
|
+
}
|
|
25817
25852
|
|
|
25818
|
-
|
|
25819
|
-
|
|
25820
|
-
|
|
25853
|
+
var html = header + '<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px">';
|
|
25854
|
+
visible.forEach(function(t) {
|
|
25855
|
+
var connected = (t.connections || []).filter(function(c) { return c.status === 'ACTIVE'; });
|
|
25856
|
+
var pending = (t.connections || []).filter(function(c) { return c.status !== 'ACTIVE'; });
|
|
25857
|
+
var isConnected = connected.length > 0;
|
|
25858
|
+
var statusColor = isConnected ? 'var(--green)' : (pending.length > 0 ? 'var(--yellow,#f59e0b)' : 'var(--text-muted)');
|
|
25859
|
+
var byo = t.authMode === 'byo' && !t.hasAuthConfig;
|
|
25821
25860
|
|
|
25822
|
-
|
|
25861
|
+
html += '<div style="border:1px solid var(--border);border-radius:8px;padding:12px;background:var(--bg-secondary);display:flex;flex-direction:column;gap:8px">';
|
|
25862
|
+
html += '<div style="display:flex;align-items:center;gap:10px">';
|
|
25863
|
+
if (t.logoUrl) {
|
|
25864
|
+
html += '<img src="' + esc(t.logoUrl) + '" alt="" style="width:24px;height:24px;border-radius:4px;background:#fff;padding:2px;flex-shrink:0;object-fit:contain">';
|
|
25865
|
+
} else {
|
|
25866
|
+
html += '<div style="width:24px;height:24px;border-radius:4px;background:var(--bg-tertiary);flex-shrink:0"></div>';
|
|
25867
|
+
}
|
|
25868
|
+
html += '<div style="flex:1;min-width:0">';
|
|
25869
|
+
html += '<div style="font-size:13px;font-weight:600;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">' + esc(t.displayName) + '</div>';
|
|
25870
|
+
html += '<div style="font-size:10px;color:var(--text-muted)">' + (isConnected ? connected.length + ' account' + (connected.length !== 1 ? 's' : '') : ((t.toolCount||0) + ' tools')) + '</div>';
|
|
25871
|
+
html += '</div>';
|
|
25872
|
+
html += '<span style="width:8px;height:8px;border-radius:50%;background:' + statusColor + ';flex-shrink:0"></span>';
|
|
25873
|
+
html += '</div>';
|
|
25874
|
+
|
|
25875
|
+
if (connected.length > 0) {
|
|
25876
|
+
html += '<div style="display:flex;flex-direction:column;gap:4px">';
|
|
25877
|
+
connected.forEach(function(c) {
|
|
25878
|
+
var label = c.alias || c.accountLabel || c.accountEmail || c.accountName || 'connected';
|
|
25879
|
+
html += '<div style="display:flex;align-items:center;gap:6px;font-size:11px">';
|
|
25880
|
+
html += '<span style="flex:1;color:var(--text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">' + esc(label) + '</span>';
|
|
25881
|
+
html += '<button class="btn-sm" onclick="disconnectComposio(\\'' + esc(t.slug) + '\\',\\'' + esc(c.id) + '\\')" style="font-size:10px;padding:2px 6px;background:transparent;border:1px solid var(--border);color:var(--text-muted);border-radius:4px;cursor:pointer">Disconnect</button>';
|
|
25882
|
+
html += '</div>';
|
|
25883
|
+
});
|
|
25823
25884
|
html += '</div>';
|
|
25824
|
-
}
|
|
25885
|
+
}
|
|
25886
|
+
|
|
25887
|
+
if (byo) {
|
|
25888
|
+
html += '<div style="font-size:10px;color:var(--yellow,#f59e0b);background:rgba(245,158,11,0.08);padding:6px 8px;border-radius:4px;line-height:1.4">Needs a BYO OAuth app — <a href="https://platform.composio.dev/auth-configs" target="_blank" style="color:inherit;text-decoration:underline">set up in Composio</a> first.</div>';
|
|
25889
|
+
}
|
|
25890
|
+
|
|
25891
|
+
html += '<button class="btn btn-sm btn-primary" onclick="connectComposio(\\'' + esc(t.slug) + '\\')" style="font-size:11px">' + (isConnected ? '+ Add account' : 'Connect') + '</button>';
|
|
25825
25892
|
html += '</div>';
|
|
25826
|
-
|
|
25827
|
-
|
|
25828
|
-
|
|
25829
|
-
|
|
25893
|
+
});
|
|
25894
|
+
html += '</div>';
|
|
25895
|
+
container.innerHTML = html;
|
|
25896
|
+
// Restore focus to the search input after re-render so users can keep typing.
|
|
25897
|
+
setTimeout(function() { var s = document.getElementById('composio-search'); if (s) { s.focus(); s.setSelectionRange(s.value.length, s.value.length); } }, 0);
|
|
25830
25898
|
}
|
|
25831
25899
|
|
|
25832
25900
|
async function saveComposioApiKey() {
|
|
@@ -54,6 +54,35 @@ export interface ToolkitMeta {
|
|
|
54
54
|
description?: string;
|
|
55
55
|
toolsCount?: number;
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Full catalog entry — derived directly from Composio's API. Replaces the
|
|
59
|
+
* hardcoded CURATED_TOOLKITS for UI rendering. The dashboard uses this so
|
|
60
|
+
* users can browse/search the entire catalog (1000+ services) instead of
|
|
61
|
+
* being limited to whatever slugs are pinned in code.
|
|
62
|
+
*/
|
|
63
|
+
export interface CatalogToolkit {
|
|
64
|
+
slug: string;
|
|
65
|
+
name: string;
|
|
66
|
+
logoUrl?: string;
|
|
67
|
+
description?: string;
|
|
68
|
+
toolsCount?: number;
|
|
69
|
+
/** managed = Composio hosts the OAuth app; byo = user must register their
|
|
70
|
+
* own; none = no auth required. Derived from composioManagedAuthSchemes. */
|
|
71
|
+
authMode: 'managed' | 'byo' | 'none';
|
|
72
|
+
categories: {
|
|
73
|
+
slug: string;
|
|
74
|
+
name: string;
|
|
75
|
+
}[];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Fetch the full Composio toolkit catalog (1000+ services). Replaces the
|
|
79
|
+
* static CURATED_TOOLKITS array as the source of truth for the dashboard
|
|
80
|
+
* Connections panel — slug typos are now impossible because we render from
|
|
81
|
+
* Composio's own data, and new services appear automatically as they're
|
|
82
|
+
* added. Cached at module level for 1 hour; resetComposioClient() clears
|
|
83
|
+
* the cache when the API key changes.
|
|
84
|
+
*/
|
|
85
|
+
export declare function listAllToolkits(): Promise<CatalogToolkit[]>;
|
|
57
86
|
export declare function listToolkitMeta(): Promise<Map<string, ToolkitMeta>>;
|
|
58
87
|
export declare function listToolkitSlugsWithAuthConfig(): Promise<Set<string>>;
|
|
59
88
|
export declare class ComposioNeedsAuthConfigError extends Error {
|
|
@@ -82,6 +82,7 @@ export function resetComposioClient() {
|
|
|
82
82
|
singleton = null;
|
|
83
83
|
identityCache.clear();
|
|
84
84
|
toolkitMetaCache = null;
|
|
85
|
+
catalogCache = null;
|
|
85
86
|
detectedPreferredUserId = null;
|
|
86
87
|
}
|
|
87
88
|
// Public: same logic as the internal detector, exposed for the MCP bridge so
|
|
@@ -334,6 +335,64 @@ export async function listConnectedToolkits() {
|
|
|
334
335
|
}
|
|
335
336
|
}
|
|
336
337
|
let toolkitMetaCache = null;
|
|
338
|
+
let catalogCache = null;
|
|
339
|
+
const CATALOG_TTL_MS = 60 * 60 * 1000; // 1h — catalog drifts very slowly
|
|
340
|
+
/**
|
|
341
|
+
* Fetch the full Composio toolkit catalog (1000+ services). Replaces the
|
|
342
|
+
* static CURATED_TOOLKITS array as the source of truth for the dashboard
|
|
343
|
+
* Connections panel — slug typos are now impossible because we render from
|
|
344
|
+
* Composio's own data, and new services appear automatically as they're
|
|
345
|
+
* added. Cached at module level for 1 hour; resetComposioClient() clears
|
|
346
|
+
* the cache when the API key changes.
|
|
347
|
+
*/
|
|
348
|
+
export async function listAllToolkits() {
|
|
349
|
+
const now = Date.now();
|
|
350
|
+
if (catalogCache && now - catalogCache.at < CATALOG_TTL_MS) {
|
|
351
|
+
return catalogCache.data;
|
|
352
|
+
}
|
|
353
|
+
const composio = getComposio();
|
|
354
|
+
if (!composio)
|
|
355
|
+
return [];
|
|
356
|
+
const out = [];
|
|
357
|
+
let cursor;
|
|
358
|
+
// Bounded loop — 30 pages × 500 items = 15K ceiling, far more than any
|
|
359
|
+
// realistic catalog. Composio currently returns ~1.5K items across 3
|
|
360
|
+
// pages, but the cap makes a runaway impossible.
|
|
361
|
+
for (let page = 0; page < 30; page++) {
|
|
362
|
+
try {
|
|
363
|
+
const rawClient = composio.client;
|
|
364
|
+
const resp = await rawClient.toolkits.list({
|
|
365
|
+
limit: 500,
|
|
366
|
+
...(cursor ? { cursor } : {}),
|
|
367
|
+
});
|
|
368
|
+
const items = (resp?.items ?? []);
|
|
369
|
+
for (const it of items) {
|
|
370
|
+
const managed = (it.composioManagedAuthSchemes ?? it.composio_managed_auth_schemes ?? []);
|
|
371
|
+
const schemes = (it.authSchemes ?? it.auth_schemes ?? []);
|
|
372
|
+
const noAuth = it.noAuth ?? it.no_auth ?? false;
|
|
373
|
+
out.push({
|
|
374
|
+
slug: it.slug,
|
|
375
|
+
name: it.name,
|
|
376
|
+
logoUrl: it.meta?.logo,
|
|
377
|
+
description: it.meta?.description,
|
|
378
|
+
toolsCount: it.meta?.toolsCount ?? it.meta?.tools_count,
|
|
379
|
+
authMode: noAuth ? 'none' : (managed.length > 0 ? 'managed' : (schemes.length > 0 ? 'byo' : 'none')),
|
|
380
|
+
categories: it.meta?.categories ?? [],
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
cursor = resp?.next_cursor ?? resp?.nextCursor;
|
|
384
|
+
if (!cursor || items.length === 0)
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
catch (err) {
|
|
388
|
+
logger.warn({ err, page }, 'listAllToolkits page failed — stopping pagination');
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
catalogCache = { at: now, data: out };
|
|
393
|
+
logger.info({ count: out.length }, 'Composio catalog fetched');
|
|
394
|
+
return out;
|
|
395
|
+
}
|
|
337
396
|
async function fetchAllToolkitMeta() {
|
|
338
397
|
const composio = getComposio();
|
|
339
398
|
if (!composio)
|