@larkup/cli 0.2.28 → 0.2.29
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/{analytics-store-4NYU7DMO.js → analytics-store-5BPWULY4.js} +1 -1
- package/dist/{chunk-V3VNCOZ6.js → chunk-FY7KWENQ.js} +5 -4
- package/dist/{chunk-JZ3UCEGF.js → chunk-T2XKBURE.js} +42 -20
- package/dist/chunk-YUC4VMEE.js +776 -0
- package/dist/index.js +26 -19
- package/dist/{tool-loader-PEQZZWTB.js → tool-loader-2WHU3H77.js} +2 -2
- package/dist/{tool-registry-VJYLRXKJ.js → tool-registry-JS5NUNN6.js} +1 -1
- package/package.json +12 -12
- package/dist/chunk-5UWKP64Q.js +0 -212
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getToolById,
|
|
3
3
|
invalidateRegistryCache
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-YUC4VMEE.js";
|
|
5
5
|
import {
|
|
6
6
|
__require
|
|
7
7
|
} from "./chunk-QGM4M3NI.js";
|
|
@@ -27,7 +27,8 @@ function serializePackageMutation(fn) {
|
|
|
27
27
|
return run;
|
|
28
28
|
}
|
|
29
29
|
function getToolsDir() {
|
|
30
|
-
|
|
30
|
+
const dataDir = process.env.LARKUP_DATA_DIR?.trim();
|
|
31
|
+
return path.join(dataDir ? path.resolve(dataDir) : path.join(process.cwd(), ".larkup"), "tools");
|
|
31
32
|
}
|
|
32
33
|
function getManifestPath() {
|
|
33
34
|
return path.join(getToolsDir(), "installed.json");
|
|
@@ -451,7 +452,7 @@ async function uninstallTool(toolId) {
|
|
|
451
452
|
}
|
|
452
453
|
}
|
|
453
454
|
invalidateRegistryCache();
|
|
454
|
-
const { unloadTool: unloadTool2 } = await import("./tool-loader-
|
|
455
|
+
const { unloadTool: unloadTool2 } = await import("./tool-loader-2WHU3H77.js");
|
|
455
456
|
unloadTool2(toolId);
|
|
456
457
|
} finally {
|
|
457
458
|
uninstallingTools.delete(toolId);
|
|
@@ -575,7 +576,7 @@ function unloadTool(toolId) {
|
|
|
575
576
|
moduleCache.delete(toolId);
|
|
576
577
|
}
|
|
577
578
|
async function hasCapability(capability) {
|
|
578
|
-
const { getToolsWithCapability } = await import("./tool-registry-
|
|
579
|
+
const { getToolsWithCapability } = await import("./tool-registry-JS5NUNN6.js");
|
|
579
580
|
const tools = await getToolsWithCapability(capability);
|
|
580
581
|
for (const t of tools) {
|
|
581
582
|
const installed = await getInstalledTool(t.id);
|
|
@@ -55,12 +55,17 @@ var DEFAULT_CONFIG = {
|
|
|
55
55
|
};
|
|
56
56
|
|
|
57
57
|
// ../../packages/core/src/project-store.ts
|
|
58
|
-
var LARKUP_ROOT = path.join(process.cwd(), ".larkup");
|
|
59
|
-
var PROJECTS_DIR = path.join(LARKUP_ROOT, "projects");
|
|
60
58
|
var BASE_PORT = 8080;
|
|
61
59
|
var EMPTY_WORKSPACE = { activeProjectId: null, projects: [] };
|
|
62
60
|
var projectScope = new AsyncLocalStorage();
|
|
63
61
|
var writeChain = Promise.resolve();
|
|
62
|
+
function getLarkupDataDir() {
|
|
63
|
+
const configured = process.env.LARKUP_DATA_DIR?.trim();
|
|
64
|
+
return configured ? path.resolve(configured) : path.join(process.cwd(), ".larkup");
|
|
65
|
+
}
|
|
66
|
+
function projectsDir() {
|
|
67
|
+
return path.join(getLarkupDataDir(), "projects");
|
|
68
|
+
}
|
|
64
69
|
function serialize(operation) {
|
|
65
70
|
const run = writeChain.then(operation, operation);
|
|
66
71
|
writeChain = run.catch(() => void 0);
|
|
@@ -68,12 +73,12 @@ function serialize(operation) {
|
|
|
68
73
|
}
|
|
69
74
|
async function readWorkspaceFile() {
|
|
70
75
|
try {
|
|
71
|
-
const entries = await fs.readdir(
|
|
76
|
+
const entries = await fs.readdir(projectsDir(), { withFileTypes: true });
|
|
72
77
|
const projects = await Promise.all(
|
|
73
78
|
entries.filter((entry) => entry.isDirectory()).map(async (entry) => {
|
|
74
79
|
try {
|
|
75
80
|
return JSON.parse(
|
|
76
|
-
await fs.readFile(path.join(
|
|
81
|
+
await fs.readFile(path.join(projectsDir(), entry.name, "project.json"), "utf8")
|
|
77
82
|
);
|
|
78
83
|
} catch {
|
|
79
84
|
return null;
|
|
@@ -91,7 +96,7 @@ async function readWorkspaceFile() {
|
|
|
91
96
|
}
|
|
92
97
|
}
|
|
93
98
|
async function writeWorkspaceFile(workspace) {
|
|
94
|
-
await fs.mkdir(
|
|
99
|
+
await fs.mkdir(projectsDir(), { recursive: true });
|
|
95
100
|
await Promise.all(
|
|
96
101
|
workspace.projects.map(async (project) => {
|
|
97
102
|
const persisted = { ...project, active: project.id === workspace.activeProjectId };
|
|
@@ -122,13 +127,14 @@ function slugify(name) {
|
|
|
122
127
|
return name.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "my-larkup";
|
|
123
128
|
}
|
|
124
129
|
function initialConfig(project) {
|
|
130
|
+
const configuredDataRoot = process.env.LARKUP_DATA_DIR?.trim();
|
|
125
131
|
return {
|
|
126
132
|
...DEFAULT_CONFIG,
|
|
127
133
|
projectName: slugify(project.name),
|
|
128
134
|
storeConfig: {
|
|
129
135
|
...DEFAULT_CONFIG.storeConfig,
|
|
130
136
|
mode: "local",
|
|
131
|
-
dbPath: `./.larkup/projects/${project.id}/index`
|
|
137
|
+
dbPath: configuredDataRoot ? path.join(getLarkupDataDir(), "projects", project.id, "index") : `./.larkup/projects/${project.id}/index`
|
|
132
138
|
},
|
|
133
139
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
134
140
|
};
|
|
@@ -137,7 +143,7 @@ function runWithProject(projectId, operation) {
|
|
|
137
143
|
return projectScope.run({ projectId }, operation);
|
|
138
144
|
}
|
|
139
145
|
function projectDir(projectId) {
|
|
140
|
-
return path.join(
|
|
146
|
+
return path.join(projectsDir(), projectId);
|
|
141
147
|
}
|
|
142
148
|
async function getProjectWorkspace() {
|
|
143
149
|
return readWorkspaceFile();
|
|
@@ -177,21 +183,35 @@ function createProject(name) {
|
|
|
177
183
|
const dir = projectDir(project.id);
|
|
178
184
|
await fs.mkdir(path.join(dir, "releases"), { recursive: true });
|
|
179
185
|
await Promise.all([
|
|
180
|
-
fs.writeFile(
|
|
186
|
+
fs.writeFile(
|
|
187
|
+
path.join(dir, "project.json"),
|
|
188
|
+
JSON.stringify({ ...project, active: true }, null, 2),
|
|
189
|
+
"utf8"
|
|
190
|
+
),
|
|
181
191
|
fs.writeFile(
|
|
182
192
|
path.join(dir, "config.json"),
|
|
183
193
|
JSON.stringify(initialConfig(project), null, 2),
|
|
184
194
|
"utf8"
|
|
185
195
|
),
|
|
186
|
-
fs.writeFile(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
196
|
+
fs.writeFile(
|
|
197
|
+
path.join(dir, "groups.json"),
|
|
198
|
+
JSON.stringify(
|
|
199
|
+
[
|
|
200
|
+
{
|
|
201
|
+
id: "default",
|
|
202
|
+
name: "Default",
|
|
203
|
+
description: "Sources added without a specific group.",
|
|
204
|
+
icon: "\u{1F4DA}",
|
|
205
|
+
assistantEnabled: true,
|
|
206
|
+
createdAt: now,
|
|
207
|
+
updatedAt: now
|
|
208
|
+
}
|
|
209
|
+
],
|
|
210
|
+
null,
|
|
211
|
+
2
|
|
212
|
+
),
|
|
213
|
+
"utf8"
|
|
214
|
+
),
|
|
195
215
|
fs.writeFile(path.join(dir, "documents.json"), "[]\n", "utf8"),
|
|
196
216
|
fs.writeFile(path.join(dir, "deployments.json"), "[]\n", "utf8"),
|
|
197
217
|
fs.writeFile(path.join(dir, "automations.json"), "[]\n", "utf8"),
|
|
@@ -214,11 +234,12 @@ function setActiveProject(projectId) {
|
|
|
214
234
|
});
|
|
215
235
|
}
|
|
216
236
|
async function resetLocalProjects(confirmedPath) {
|
|
217
|
-
|
|
218
|
-
|
|
237
|
+
const target = projectsDir();
|
|
238
|
+
if (confirmedPath !== target) {
|
|
239
|
+
throw new Error(`Confirmation must exactly match ${target}.`);
|
|
219
240
|
}
|
|
220
241
|
await serialize(async () => {
|
|
221
|
-
await fs.rm(
|
|
242
|
+
await fs.rm(target, { recursive: true, force: true });
|
|
222
243
|
});
|
|
223
244
|
}
|
|
224
245
|
|
|
@@ -7010,6 +7031,7 @@ async function getAnalyticsSummary(days, runtime = "all") {
|
|
|
7010
7031
|
|
|
7011
7032
|
export {
|
|
7012
7033
|
DEFAULT_CONFIG,
|
|
7034
|
+
getLarkupDataDir,
|
|
7013
7035
|
runWithProject,
|
|
7014
7036
|
getProjectWorkspace,
|
|
7015
7037
|
getActiveProject,
|
|
@@ -0,0 +1,776 @@
|
|
|
1
|
+
// ../../packages/marketplace/src/tool-registry.ts
|
|
2
|
+
import { promises as fs } from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
// ../../packages/marketplace/src/types.ts
|
|
6
|
+
var DEFAULT_HUB_URL = process.env.LARKUP_HUB_URL ?? "https://hub.larkup.de";
|
|
7
|
+
var STORAGE_WARNING_THRESHOLDS = [
|
|
8
|
+
1 * 1024 * 1024 * 1024,
|
|
9
|
+
// 1 GB
|
|
10
|
+
5 * 1024 * 1024 * 1024,
|
|
11
|
+
// 5 GB
|
|
12
|
+
10 * 1024 * 1024 * 1024
|
|
13
|
+
// 10 GB
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
// ../../packages/marketplace/src/db/builtin-tools.ts
|
|
17
|
+
var BUILTIN_TOOLS = [
|
|
18
|
+
{
|
|
19
|
+
manifestVersion: "3.0",
|
|
20
|
+
kind: "tool",
|
|
21
|
+
id: "video-intelligence",
|
|
22
|
+
name: "Video Intelligence",
|
|
23
|
+
description: "Help your AI understand your videos, so you can ask questions and get answers from the right moments.",
|
|
24
|
+
longDescription: "Turn videos into knowledge your AI can understand. It listens to what is said, reads what appears on screen, and helps you find the moments that matter.",
|
|
25
|
+
category: "media",
|
|
26
|
+
version: "0.2.2",
|
|
27
|
+
pricing: "free",
|
|
28
|
+
emoji: "\u{1F3AC}",
|
|
29
|
+
icon: "Film",
|
|
30
|
+
packageName: "@larkup/tool-video-intelligence",
|
|
31
|
+
installSize: "~120 KB",
|
|
32
|
+
requiresSandbox: false,
|
|
33
|
+
author: "Larkup",
|
|
34
|
+
capabilities: [
|
|
35
|
+
"video-indexing",
|
|
36
|
+
"video-indexing-brief",
|
|
37
|
+
"agentic-video-indexing",
|
|
38
|
+
"cloud-video-runtime",
|
|
39
|
+
"timestamped-transcription",
|
|
40
|
+
"ocr",
|
|
41
|
+
"object-detection",
|
|
42
|
+
"anonymous-object-tracking",
|
|
43
|
+
"usage-metering"
|
|
44
|
+
],
|
|
45
|
+
entrypoints: { server: ".", agent: ".", ui: "./ui" },
|
|
46
|
+
runtime: {
|
|
47
|
+
protocolVersion: "1",
|
|
48
|
+
defaultMode: "managed-cloud",
|
|
49
|
+
healthPath: "/v1/health",
|
|
50
|
+
modes: [
|
|
51
|
+
{
|
|
52
|
+
id: "managed-cloud",
|
|
53
|
+
label: "Larkup Cloud",
|
|
54
|
+
description: "Index with managed private GPU capacity and your Larkup Cloud allowance.",
|
|
55
|
+
requiresGpu: true
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: "local",
|
|
59
|
+
label: "Local runtime",
|
|
60
|
+
icon: "/docker.png",
|
|
61
|
+
description: "Run the application locally on your computer.",
|
|
62
|
+
endpointConfigKey: "localRuntimeUrl",
|
|
63
|
+
credentialConfigKey: "localRuntimeApiKey",
|
|
64
|
+
image: "ghcr.io/larkup-ai/video-intelligence:0.1.0",
|
|
65
|
+
composeFile: "compose.yaml",
|
|
66
|
+
setupNotice: "The local application and its models download to this computer. The initial download can be large."
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: "custom-remote",
|
|
70
|
+
label: "Custom runtime",
|
|
71
|
+
description: "Connect a compatible Video Intelligence deployment you operate.",
|
|
72
|
+
endpointConfigKey: "customRuntimeUrl",
|
|
73
|
+
credentialConfigKey: "customRuntimeApiKey",
|
|
74
|
+
requiresGpu: true
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
},
|
|
78
|
+
billing: {
|
|
79
|
+
model: "hybrid",
|
|
80
|
+
entitlementVersion: "1",
|
|
81
|
+
meters: [
|
|
82
|
+
{ id: "source-minute", unit: "minute", description: "Minutes of source video." },
|
|
83
|
+
{
|
|
84
|
+
id: "thorough-inspection-minute",
|
|
85
|
+
unit: "minute",
|
|
86
|
+
description: "Minutes inspected with Thorough agent coverage."
|
|
87
|
+
},
|
|
88
|
+
{ id: "gpu-second", unit: "second", description: "GPU worker seconds." },
|
|
89
|
+
{ id: "ocr-frame", unit: "frame", description: "Frames processed by OCR." }
|
|
90
|
+
]
|
|
91
|
+
},
|
|
92
|
+
usage: {
|
|
93
|
+
label: "Usage this month",
|
|
94
|
+
visibleWhenRuntimeMode: ["managed-cloud"],
|
|
95
|
+
visualization: { usedKey: "sourceMinutesUsed", limitKey: "sourceMinutesLimit" },
|
|
96
|
+
support: {
|
|
97
|
+
contactLabel: "Request more usage",
|
|
98
|
+
description: "Each installation receives Cloud access automatically. Request more video-processing usage when your allowance is low.",
|
|
99
|
+
userIdConfigKey: "cloudInstallationId"
|
|
100
|
+
},
|
|
101
|
+
fields: [
|
|
102
|
+
{ key: "sourceMinutesUsed", label: "Used", format: "minutes" },
|
|
103
|
+
{ key: "sourceMinutesLimit", label: "Included", format: "minutes" },
|
|
104
|
+
{ key: "activeJobs", label: "Active jobs", format: "number" }
|
|
105
|
+
]
|
|
106
|
+
},
|
|
107
|
+
ui: {
|
|
108
|
+
isolation: "sandboxed-iframe",
|
|
109
|
+
surfaces: [
|
|
110
|
+
{
|
|
111
|
+
id: "video-indexing-brief",
|
|
112
|
+
slot: "data-indexing",
|
|
113
|
+
title: "Let your AI understand this video",
|
|
114
|
+
entrypoint: "./ui",
|
|
115
|
+
appliesTo: ["video"],
|
|
116
|
+
description: "Choose how deeply to analyze it and optionally point it to what matters most.",
|
|
117
|
+
estimate: {
|
|
118
|
+
modeField: "indexingMode",
|
|
119
|
+
variants: [
|
|
120
|
+
{
|
|
121
|
+
value: "fast",
|
|
122
|
+
analyzedFramesPerSourceMinute: 5,
|
|
123
|
+
ocrFramesPerSourceMinute: 3,
|
|
124
|
+
processingSecondsPerSourceMinute: 8,
|
|
125
|
+
maxProcessingSecondsPerSourceMinute: 15,
|
|
126
|
+
fixedOverheadSeconds: 90,
|
|
127
|
+
maxFixedOverheadSeconds: 180,
|
|
128
|
+
creditsPerSourceMinute: 1
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
value: "balanced",
|
|
132
|
+
analyzedFramesPerSourceMinute: 12,
|
|
133
|
+
ocrFramesPerSourceMinute: 8,
|
|
134
|
+
processingSecondsPerSourceMinute: 16,
|
|
135
|
+
maxProcessingSecondsPerSourceMinute: 30,
|
|
136
|
+
fixedOverheadSeconds: 120,
|
|
137
|
+
maxFixedOverheadSeconds: 240,
|
|
138
|
+
creditsPerSourceMinute: 2
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
value: "thorough",
|
|
142
|
+
analyzedFramesPerSourceMinute: 30,
|
|
143
|
+
ocrFramesPerSourceMinute: 20,
|
|
144
|
+
processingSecondsPerSourceMinute: 32,
|
|
145
|
+
maxProcessingSecondsPerSourceMinute: 60,
|
|
146
|
+
fixedOverheadSeconds: 180,
|
|
147
|
+
maxFixedOverheadSeconds: 360,
|
|
148
|
+
creditsPerSourceMinute: 4
|
|
149
|
+
}
|
|
150
|
+
]
|
|
151
|
+
},
|
|
152
|
+
form: {
|
|
153
|
+
submitLabel: "Start indexing",
|
|
154
|
+
fields: [
|
|
155
|
+
{
|
|
156
|
+
key: "goal",
|
|
157
|
+
type: "textarea",
|
|
158
|
+
label: "What should your AI look for? (optional)",
|
|
159
|
+
placeholder: "For example: find the final score, the moment the package is dropped, or every mention of pricing."
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
key: "indexingMode",
|
|
163
|
+
type: "select",
|
|
164
|
+
label: "Coverage",
|
|
165
|
+
defaultValue: "balanced",
|
|
166
|
+
options: [
|
|
167
|
+
{
|
|
168
|
+
label: "Fast",
|
|
169
|
+
value: "fast",
|
|
170
|
+
description: "Sample key frames for a quick overview.",
|
|
171
|
+
setValues: { processingAuthorityConfirmed: false }
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
label: "Balanced",
|
|
175
|
+
value: "balanced",
|
|
176
|
+
description: "Sample visual and OCR evidence across the video.",
|
|
177
|
+
setValues: { processingAuthorityConfirmed: false }
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
label: "Thorough",
|
|
181
|
+
value: "thorough",
|
|
182
|
+
description: "Let the agent inspect denser evidence around subtle or infrequent details.",
|
|
183
|
+
setValues: { processingAuthorityConfirmed: false }
|
|
184
|
+
}
|
|
185
|
+
]
|
|
186
|
+
}
|
|
187
|
+
]
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
id: "video-evidence-result",
|
|
192
|
+
slot: "chat-result",
|
|
193
|
+
title: "Video evidence",
|
|
194
|
+
entrypoint: "./ui"
|
|
195
|
+
}
|
|
196
|
+
]
|
|
197
|
+
},
|
|
198
|
+
tags: ["video", "ocr", "tracking", "local", "gpu", "private", "gdpr"],
|
|
199
|
+
downloads: 0,
|
|
200
|
+
repositoryUrl: "https://github.com/Larkup-AI/larkup",
|
|
201
|
+
license: "Apache-2.0",
|
|
202
|
+
updatedAt: "2026-08-20",
|
|
203
|
+
trustLevel: "elevated",
|
|
204
|
+
permissions: {
|
|
205
|
+
fsRead: true,
|
|
206
|
+
fsWrite: true,
|
|
207
|
+
exec: true,
|
|
208
|
+
httpAllow: ["127.0.0.1", "localhost", "video.larkup.de"]
|
|
209
|
+
},
|
|
210
|
+
configSchema: [
|
|
211
|
+
{
|
|
212
|
+
key: "runtimeMode",
|
|
213
|
+
label: "Runtime",
|
|
214
|
+
type: "select",
|
|
215
|
+
group: "Runtime",
|
|
216
|
+
defaultValue: "managed-cloud",
|
|
217
|
+
help: "Choose managed Larkup Cloud, a local runtime that auto-selects Docker or native, or your compatible deployment.",
|
|
218
|
+
options: [
|
|
219
|
+
{
|
|
220
|
+
label: "Larkup Cloud",
|
|
221
|
+
value: "managed-cloud",
|
|
222
|
+
icon: "/logo.png",
|
|
223
|
+
description: "Managed GPU capacity and automatic device access."
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
label: "Local runtime",
|
|
227
|
+
value: "local",
|
|
228
|
+
icon: "/docker.png",
|
|
229
|
+
description: "Run the application locally on your computer."
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
label: "Custom runtime",
|
|
233
|
+
value: "custom-remote",
|
|
234
|
+
icon: "/icons/cloudflare.png",
|
|
235
|
+
description: "Use your deployment URL and key."
|
|
236
|
+
}
|
|
237
|
+
]
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
key: "localRuntimeUrl",
|
|
241
|
+
label: "Local runtime URL",
|
|
242
|
+
type: "text",
|
|
243
|
+
group: "Local runtime",
|
|
244
|
+
defaultValue: "http://127.0.0.1:8787",
|
|
245
|
+
help: "Use a LAN address to share the runtime with trusted users.",
|
|
246
|
+
verification: {
|
|
247
|
+
endpoint: "/api/tools/video-intelligence/verify",
|
|
248
|
+
method: "POST",
|
|
249
|
+
fields: {
|
|
250
|
+
localRuntimeUrl: "localRuntimeUrl",
|
|
251
|
+
localRuntimeApiKey: "localRuntimeApiKey",
|
|
252
|
+
runtimeMode: "runtimeMode"
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
visibleWhen: { field: "runtimeMode", equals: "local" }
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
key: "localRuntimeApiKey",
|
|
259
|
+
label: "Shared local API key",
|
|
260
|
+
type: "password",
|
|
261
|
+
group: "Local runtime",
|
|
262
|
+
help: "Optional bearer key when local runtime authentication is enabled.",
|
|
263
|
+
visibleWhen: { field: "runtimeMode", equals: "local" }
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
key: "videoVisionProvider",
|
|
267
|
+
label: "Vision provider",
|
|
268
|
+
type: "select",
|
|
269
|
+
group: "Video understanding",
|
|
270
|
+
defaultValue: "vercel_ai_gateway",
|
|
271
|
+
defaultFromGlobalConfigKey: "visionProvider",
|
|
272
|
+
help: "Defaults to your saved AI Models vision provider. Choose another provider here only for this tool.",
|
|
273
|
+
options: [
|
|
274
|
+
{
|
|
275
|
+
label: "Use AI Models",
|
|
276
|
+
value: "auto",
|
|
277
|
+
description: "Follow the workspace Vision Model setting."
|
|
278
|
+
},
|
|
279
|
+
{ label: "Google Gemini", value: "google", icon: "/icons/gemini.svg" },
|
|
280
|
+
{
|
|
281
|
+
label: "Vercel AI Gateway",
|
|
282
|
+
value: "vercel_ai_gateway",
|
|
283
|
+
icon: "/icons/vercel.svg"
|
|
284
|
+
},
|
|
285
|
+
{ label: "OpenAI", value: "openai", icon: "/icons/openai.svg" }
|
|
286
|
+
]
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
key: "videoVisionApiKey",
|
|
290
|
+
label: "Vision provider API key",
|
|
291
|
+
type: "password",
|
|
292
|
+
group: "Video understanding",
|
|
293
|
+
help: "Optional tool override. Leave empty to use the matching provider key from AI Models; it is forwarded only for the active job."
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
key: "semanticVisionModel",
|
|
297
|
+
label: "Video vision model",
|
|
298
|
+
type: "select",
|
|
299
|
+
group: "Video understanding",
|
|
300
|
+
defaultValue: "google/gemini-3.6-flash",
|
|
301
|
+
defaultFromGlobalConfigKey: "visionModelId",
|
|
302
|
+
providerField: "videoVisionProvider",
|
|
303
|
+
defaultValueByProvider: {
|
|
304
|
+
auto: "auto",
|
|
305
|
+
google: "google/gemini-3.6-flash",
|
|
306
|
+
vercel_ai_gateway: "google/gemini-3.6-flash",
|
|
307
|
+
openai: "openai/gpt-4o-mini"
|
|
308
|
+
},
|
|
309
|
+
help: "Defaults to the Vision Model in AI Models. Choose another video-capable model to override it only for this tool.",
|
|
310
|
+
verification: {
|
|
311
|
+
endpoint: "/api/tools/video-intelligence/verify",
|
|
312
|
+
method: "POST",
|
|
313
|
+
fields: {
|
|
314
|
+
videoVisionProvider: "videoVisionProvider",
|
|
315
|
+
videoVisionApiKey: "videoVisionApiKey",
|
|
316
|
+
semanticVisionModel: "semanticVisionModel"
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
options: [
|
|
320
|
+
{
|
|
321
|
+
label: "Use AI Models",
|
|
322
|
+
value: "auto",
|
|
323
|
+
description: "Follow the workspace Vision Model setting."
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
label: "Gemini 3.6 Flash",
|
|
327
|
+
value: "google/gemini-3.6-flash",
|
|
328
|
+
icon: "/icons/gemini.svg"
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
label: "Gemini 3.5 Flash",
|
|
332
|
+
value: "google/gemini-3.5-flash",
|
|
333
|
+
icon: "/icons/gemini.svg"
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
label: "GPT-4o mini",
|
|
337
|
+
value: "openai/gpt-4o-mini",
|
|
338
|
+
icon: "/icons/openai.svg"
|
|
339
|
+
}
|
|
340
|
+
]
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
key: "videoAgentProvider",
|
|
344
|
+
label: "Agent provider",
|
|
345
|
+
type: "select",
|
|
346
|
+
group: "Agent planning",
|
|
347
|
+
defaultValue: "vercel_ai_gateway",
|
|
348
|
+
defaultFromGlobalConfigKey: "chatProvider",
|
|
349
|
+
help: "Defaults to your saved AI Models chat provider. Choose another provider here only for this tool.",
|
|
350
|
+
options: [
|
|
351
|
+
{
|
|
352
|
+
label: "Use AI Models",
|
|
353
|
+
value: "auto",
|
|
354
|
+
description: "Follow the workspace Chat Model setting."
|
|
355
|
+
},
|
|
356
|
+
{ label: "Google Gemini", value: "google", icon: "/icons/gemini.svg" },
|
|
357
|
+
{
|
|
358
|
+
label: "Vercel AI Gateway",
|
|
359
|
+
value: "vercel_ai_gateway",
|
|
360
|
+
icon: "/icons/vercel.svg"
|
|
361
|
+
},
|
|
362
|
+
{ label: "OpenAI", value: "openai", icon: "/icons/openai.svg" },
|
|
363
|
+
{
|
|
364
|
+
label: "DeepSeek",
|
|
365
|
+
value: "deepseek",
|
|
366
|
+
icon: "https://models.dev/logos/deepseek.svg"
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
label: "Anthropic",
|
|
370
|
+
value: "anthropic",
|
|
371
|
+
icon: "https://models.dev/logos/anthropic.svg"
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
label: "Mistral",
|
|
375
|
+
value: "mistral",
|
|
376
|
+
icon: "https://models.dev/logos/mistral.svg"
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
label: "Cohere",
|
|
380
|
+
value: "cohere",
|
|
381
|
+
icon: "https://models.dev/logos/cohere.svg"
|
|
382
|
+
}
|
|
383
|
+
]
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
key: "videoAgentApiKey",
|
|
387
|
+
label: "Agent provider API key",
|
|
388
|
+
type: "password",
|
|
389
|
+
group: "Agent planning",
|
|
390
|
+
help: "Optional tool override. Leave empty to use the matching chat-provider key from AI Models; it is forwarded only for the active job."
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
key: "agentModel",
|
|
394
|
+
label: "Agent / tool-brain model",
|
|
395
|
+
type: "select",
|
|
396
|
+
group: "Agent planning",
|
|
397
|
+
defaultValue: "openai/gpt-5-mini",
|
|
398
|
+
defaultFromGlobalConfigKey: "chatModelId",
|
|
399
|
+
providerField: "videoAgentProvider",
|
|
400
|
+
defaultValueByProvider: {
|
|
401
|
+
auto: "auto",
|
|
402
|
+
google: "google/gemini-3.5-flash-lite",
|
|
403
|
+
vercel_ai_gateway: "openai/gpt-5-mini",
|
|
404
|
+
openai: "openai/gpt-5-mini",
|
|
405
|
+
deepseek: "deepseek/deepseek-v3.2",
|
|
406
|
+
anthropic: "anthropic/claude-sonnet-4",
|
|
407
|
+
mistral: "mistral/mistral-large-3",
|
|
408
|
+
cohere: "cohere/command-a"
|
|
409
|
+
},
|
|
410
|
+
help: "Defaults to the Chat Model in AI Models. Choose another model to override it only for video planning and synthesis.",
|
|
411
|
+
verification: {
|
|
412
|
+
endpoint: "/api/tools/video-intelligence/verify",
|
|
413
|
+
method: "POST",
|
|
414
|
+
fields: {
|
|
415
|
+
videoAgentProvider: "videoAgentProvider",
|
|
416
|
+
videoAgentApiKey: "videoAgentApiKey",
|
|
417
|
+
agentModel: "agentModel"
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
options: [
|
|
421
|
+
{
|
|
422
|
+
label: "Use AI Models",
|
|
423
|
+
value: "auto",
|
|
424
|
+
description: "Follow the workspace Chat Model setting."
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
label: "Gemini 3.5 Flash Lite",
|
|
428
|
+
value: "google/gemini-3.5-flash-lite",
|
|
429
|
+
icon: "/icons/gemini.svg"
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
label: "Gemini 3.6 Flash",
|
|
433
|
+
value: "google/gemini-3.6-flash",
|
|
434
|
+
icon: "/icons/gemini.svg"
|
|
435
|
+
},
|
|
436
|
+
{ label: "GPT-5 mini", value: "openai/gpt-5-mini", icon: "/icons/openai.svg" },
|
|
437
|
+
{ label: "GPT-5.2", value: "openai/gpt-5.2", icon: "/icons/openai.svg" },
|
|
438
|
+
{
|
|
439
|
+
label: "DeepSeek V3.2",
|
|
440
|
+
value: "deepseek/deepseek-v3.2",
|
|
441
|
+
icon: "https://models.dev/logos/deepseek.svg"
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
label: "DeepSeek V4 Pro",
|
|
445
|
+
value: "deepseek/deepseek-v4-pro",
|
|
446
|
+
icon: "https://models.dev/logos/deepseek.svg"
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
label: "Claude Sonnet 4",
|
|
450
|
+
value: "anthropic/claude-sonnet-4",
|
|
451
|
+
icon: "https://models.dev/logos/anthropic.svg"
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
label: "Mistral Large 3",
|
|
455
|
+
value: "mistral/mistral-large-3",
|
|
456
|
+
icon: "https://models.dev/logos/mistral.svg"
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
label: "Command A",
|
|
460
|
+
value: "cohere/command-a",
|
|
461
|
+
icon: "https://models.dev/logos/cohere.svg"
|
|
462
|
+
}
|
|
463
|
+
]
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
key: "customRuntimeUrl",
|
|
467
|
+
label: "Custom runtime URL",
|
|
468
|
+
type: "text",
|
|
469
|
+
group: "Custom runtime",
|
|
470
|
+
required: true,
|
|
471
|
+
verification: {
|
|
472
|
+
endpoint: "/api/tools/video-intelligence/verify",
|
|
473
|
+
method: "POST",
|
|
474
|
+
fields: {
|
|
475
|
+
customRuntimeUrl: "customRuntimeUrl",
|
|
476
|
+
customRuntimeApiKey: "customRuntimeApiKey",
|
|
477
|
+
runtimeMode: "runtimeMode"
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
visibleWhen: { field: "runtimeMode", equals: "custom-remote" }
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
key: "customRuntimeApiKey",
|
|
484
|
+
label: "Custom runtime API key",
|
|
485
|
+
type: "password",
|
|
486
|
+
group: "Custom runtime",
|
|
487
|
+
required: true,
|
|
488
|
+
visibleWhen: { field: "runtimeMode", equals: "custom-remote" }
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
key: "audioProvider",
|
|
492
|
+
label: "Audio provider",
|
|
493
|
+
type: "select",
|
|
494
|
+
group: "Audio",
|
|
495
|
+
defaultValue: "deepgram",
|
|
496
|
+
help: "Required for every runtime. Transcription is billed directly to your selected provider; Larkup Cloud does not supply audio-model credits.",
|
|
497
|
+
options: [
|
|
498
|
+
{
|
|
499
|
+
label: "OpenAI",
|
|
500
|
+
value: "openai",
|
|
501
|
+
icon: "/icons/openai.svg",
|
|
502
|
+
description: "Use your OpenAI transcription allowance."
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
label: "Groq",
|
|
506
|
+
value: "groq",
|
|
507
|
+
icon: "https://models.dev/logos/groq.svg",
|
|
508
|
+
description: "Fast hosted Whisper inference."
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
label: "Deepgram",
|
|
512
|
+
value: "deepgram",
|
|
513
|
+
icon: "/icons/deepgram.png",
|
|
514
|
+
description: "Speech-to-text with language features."
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
label: "ElevenLabs",
|
|
518
|
+
value: "elevenlabs",
|
|
519
|
+
icon: "/icons/elevenlabs.png",
|
|
520
|
+
description: "Use your ElevenLabs speech-to-text account."
|
|
521
|
+
}
|
|
522
|
+
]
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
key: "audioApiKey",
|
|
526
|
+
label: "Audio provider API key",
|
|
527
|
+
type: "password",
|
|
528
|
+
group: "Audio",
|
|
529
|
+
help: "Required. It stays in this installation and is forwarded only for the active video job.",
|
|
530
|
+
verification: {
|
|
531
|
+
endpoint: "/api/tools/video-intelligence/verify",
|
|
532
|
+
method: "POST",
|
|
533
|
+
fields: {
|
|
534
|
+
audioProvider: "audioProvider",
|
|
535
|
+
audioApiKey: "audioApiKey"
|
|
536
|
+
}
|
|
537
|
+
},
|
|
538
|
+
visibleWhen: {
|
|
539
|
+
field: "audioProvider",
|
|
540
|
+
equals: ["openai", "groq", "deepgram", "elevenlabs"]
|
|
541
|
+
}
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
key: "cloudAccessKey",
|
|
545
|
+
label: "Larkup Cloud API key",
|
|
546
|
+
type: "password",
|
|
547
|
+
group: "Managed cloud connection",
|
|
548
|
+
help: "Paste the key Larkup provides for managed cloud access. Leave this empty only when automatic project provisioning is enabled.",
|
|
549
|
+
visibleWhen: { field: "runtimeMode", equals: "managed-cloud" }
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
key: "defaultIndexingMode",
|
|
553
|
+
label: "Default indexing mode",
|
|
554
|
+
type: "select",
|
|
555
|
+
group: "Indexing",
|
|
556
|
+
defaultValue: "balanced",
|
|
557
|
+
options: [
|
|
558
|
+
{ label: "Fast", value: "fast" },
|
|
559
|
+
{ label: "Balanced", value: "balanced" },
|
|
560
|
+
{ label: "Thorough", value: "thorough" }
|
|
561
|
+
]
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
key: "cloudInstallationId",
|
|
565
|
+
label: "Managed cloud installation ID",
|
|
566
|
+
type: "text",
|
|
567
|
+
group: "Managed cloud connection",
|
|
568
|
+
help: "Provisioned automatically for this project.",
|
|
569
|
+
serverManaged: true
|
|
570
|
+
}
|
|
571
|
+
]
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
id: "clip-embeddings",
|
|
575
|
+
name: "CLIP Image Search",
|
|
576
|
+
description: "Direct image-to-image similarity search using CLIP/SigLIP embeddings.",
|
|
577
|
+
longDescription: "Enable native visual similarity search by embedding images with CLIP or SigLIP models. Search your image library by uploading a query image or describing what you're looking for. Requires downloading a ~200MB model on first use.",
|
|
578
|
+
category: "embedding",
|
|
579
|
+
version: "0.1.0",
|
|
580
|
+
pricing: "free",
|
|
581
|
+
emoji: "\u{1F50D}",
|
|
582
|
+
icon: "ScanEye",
|
|
583
|
+
packageName: "@larkup/tool-clip-embeddings",
|
|
584
|
+
installSize: "~200 MB",
|
|
585
|
+
author: "Larkup",
|
|
586
|
+
capabilities: ["clip-embeddings", "image-similarity"],
|
|
587
|
+
tags: ["clip", "siglip", "image-search", "visual-similarity"],
|
|
588
|
+
repositoryUrl: "https://github.com/Larkup-AI/larkup",
|
|
589
|
+
license: "Apache-2.0",
|
|
590
|
+
updatedAt: "2026-07-20",
|
|
591
|
+
comingSoon: true
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
id: "doc-editor",
|
|
595
|
+
name: "Document Editor",
|
|
596
|
+
description: "AI-powered form filling and document editing with Canvas-style live preview.",
|
|
597
|
+
longDescription: "Open PDF, Word, PowerPoint, and text files in a split-view Canvas. AI fills forms using your knowledge base, edits content on request, and previews changes live. Supports PDF form fields (AcroForm), DOCX paragraphs/tables, PPTX slide text, and plain text editing. Uses pdf-lib (native) for PDF and the Docker sandbox for DOCX/PPTX via python-docx/python-pptx.",
|
|
598
|
+
category: "utility",
|
|
599
|
+
version: "0.2.3",
|
|
600
|
+
pricing: "free",
|
|
601
|
+
emoji: "\u{1F4DD}",
|
|
602
|
+
icon: "FileEdit",
|
|
603
|
+
packageName: "@larkup/tool-doc-editor",
|
|
604
|
+
installSize: "~5 MB",
|
|
605
|
+
systemDeps: ["docker"],
|
|
606
|
+
requiresSandbox: true,
|
|
607
|
+
author: "Larkup",
|
|
608
|
+
capabilities: ["document-editing", "form-filling", "document-preview"],
|
|
609
|
+
tags: ["pdf", "docx", "pptx", "form", "canvas", "editor", "fill"],
|
|
610
|
+
repositoryUrl: "https://github.com/Larkup-AI/larkup",
|
|
611
|
+
license: "Apache-2.0",
|
|
612
|
+
updatedAt: "2026-07-20"
|
|
613
|
+
}
|
|
614
|
+
];
|
|
615
|
+
|
|
616
|
+
// ../../packages/marketplace/src/tool-registry.ts
|
|
617
|
+
var cachedRegistry = null;
|
|
618
|
+
var RETIRED_CATALOG_TOOL_IDS = /* @__PURE__ */ new Set(["video-audio"]);
|
|
619
|
+
function normalizeToolDescriptor(descriptor) {
|
|
620
|
+
return { ...descriptor, requiresSandbox: descriptor.requiresSandbox !== false };
|
|
621
|
+
}
|
|
622
|
+
function compareToolVersions(left, right) {
|
|
623
|
+
const parse = (version) => {
|
|
624
|
+
const [core, suffix = ""] = version.split("-", 2);
|
|
625
|
+
const parts = core.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
626
|
+
return { parts, stable: suffix.length === 0 };
|
|
627
|
+
};
|
|
628
|
+
const a = parse(left);
|
|
629
|
+
const b = parse(right);
|
|
630
|
+
const length = Math.max(a.parts.length, b.parts.length);
|
|
631
|
+
for (let index = 0; index < length; index += 1) {
|
|
632
|
+
const difference = (a.parts[index] ?? 0) - (b.parts[index] ?? 0);
|
|
633
|
+
if (difference !== 0) return difference;
|
|
634
|
+
}
|
|
635
|
+
if (a.stable === b.stable) return 0;
|
|
636
|
+
return a.stable ? 1 : -1;
|
|
637
|
+
}
|
|
638
|
+
function mergeToolDescriptors(current, incoming) {
|
|
639
|
+
const merged = { ...current };
|
|
640
|
+
for (const [id, descriptor] of Object.entries(incoming)) {
|
|
641
|
+
const existing = merged[id];
|
|
642
|
+
if (!existing || compareToolVersions(descriptor.version, existing.version) >= 0) {
|
|
643
|
+
merged[id] = descriptor;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
return merged;
|
|
647
|
+
}
|
|
648
|
+
async function discoverLocalManifests() {
|
|
649
|
+
const registry = {};
|
|
650
|
+
const persistentDataDir = process.env.LARKUP_DATA_DIR?.trim();
|
|
651
|
+
const searchPaths = [
|
|
652
|
+
// Monorepo development: packages/marketplace-tools/*
|
|
653
|
+
path.resolve(process.cwd(), "packages", "marketplace-tools"),
|
|
654
|
+
// Next.js dev server runs in apps/web
|
|
655
|
+
path.resolve(process.cwd(), "..", "..", "packages", "marketplace-tools"),
|
|
656
|
+
// Installed tools: .larkup/tools/node_modules/@larkup
|
|
657
|
+
...persistentDataDir ? [path.resolve(persistentDataDir, "tools", "node_modules", "@larkup")] : [],
|
|
658
|
+
path.resolve(process.cwd(), ".larkup", "tools", "node_modules", "@larkup"),
|
|
659
|
+
path.resolve(process.cwd(), "..", "..", ".larkup", "tools", "node_modules", "@larkup")
|
|
660
|
+
];
|
|
661
|
+
for (const searchPath of searchPaths) {
|
|
662
|
+
try {
|
|
663
|
+
const entries = await fs.readdir(searchPath, { withFileTypes: true });
|
|
664
|
+
for (const entry of entries) {
|
|
665
|
+
if (!entry.isDirectory()) continue;
|
|
666
|
+
const manifestPath = path.join(searchPath, entry.name, "tool.manifest.json");
|
|
667
|
+
try {
|
|
668
|
+
const raw = await fs.readFile(manifestPath, "utf8");
|
|
669
|
+
const manifest = normalizeToolDescriptor(JSON.parse(raw));
|
|
670
|
+
if (manifest.id && !RETIRED_CATALOG_TOOL_IDS.has(manifest.id)) {
|
|
671
|
+
registry[manifest.id] = manifest;
|
|
672
|
+
}
|
|
673
|
+
} catch {
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
} catch {
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
return registry;
|
|
680
|
+
}
|
|
681
|
+
async function fetchHubCatalog(hubUrl) {
|
|
682
|
+
const baseUrl = hubUrl ?? DEFAULT_HUB_URL;
|
|
683
|
+
try {
|
|
684
|
+
const controller = new AbortController();
|
|
685
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
686
|
+
const res = await fetch(`${baseUrl}/v1/tools`, {
|
|
687
|
+
signal: controller.signal,
|
|
688
|
+
headers: { Accept: "application/json" }
|
|
689
|
+
});
|
|
690
|
+
clearTimeout(timeout);
|
|
691
|
+
if (!res.ok) return {};
|
|
692
|
+
const data = await res.json();
|
|
693
|
+
const registry = {};
|
|
694
|
+
for (const tool of data.tools ?? []) {
|
|
695
|
+
if (tool.id && !RETIRED_CATALOG_TOOL_IDS.has(tool.id)) {
|
|
696
|
+
registry[tool.id] = normalizeToolDescriptor(tool);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
return registry;
|
|
700
|
+
} catch {
|
|
701
|
+
return {};
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
async function fetchToolFromHub(toolId, hubUrl) {
|
|
705
|
+
if (RETIRED_CATALOG_TOOL_IDS.has(toolId)) return null;
|
|
706
|
+
const baseUrl = hubUrl ?? DEFAULT_HUB_URL;
|
|
707
|
+
try {
|
|
708
|
+
const controller = new AbortController();
|
|
709
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
710
|
+
const res = await fetch(`${baseUrl}/v1/tools/${toolId}`, {
|
|
711
|
+
signal: controller.signal,
|
|
712
|
+
headers: { Accept: "application/json" }
|
|
713
|
+
});
|
|
714
|
+
clearTimeout(timeout);
|
|
715
|
+
if (!res.ok) return null;
|
|
716
|
+
const data = await res.json();
|
|
717
|
+
return data.tool ?? null;
|
|
718
|
+
} catch {
|
|
719
|
+
return null;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
var FALLBACK_REGISTRY = Object.fromEntries(
|
|
723
|
+
BUILTIN_TOOLS.map((tool) => {
|
|
724
|
+
const descriptor = normalizeToolDescriptor(tool);
|
|
725
|
+
return [descriptor.id, descriptor];
|
|
726
|
+
})
|
|
727
|
+
);
|
|
728
|
+
async function buildRegistry(opts) {
|
|
729
|
+
if (cachedRegistry) return cachedRegistry;
|
|
730
|
+
let registry = { ...FALLBACK_REGISTRY };
|
|
731
|
+
let hubAvailable = false;
|
|
732
|
+
if (!opts?.skipHub) {
|
|
733
|
+
const hubCatalog = await fetchHubCatalog(opts?.hubUrl);
|
|
734
|
+
if (Object.keys(hubCatalog).length > 0) {
|
|
735
|
+
registry = mergeToolDescriptors(registry, hubCatalog);
|
|
736
|
+
hubAvailable = true;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
if (!hubAvailable || opts?.preferLocal) {
|
|
740
|
+
const localManifests = await discoverLocalManifests();
|
|
741
|
+
registry = mergeToolDescriptors(registry, localManifests);
|
|
742
|
+
}
|
|
743
|
+
cachedRegistry = registry;
|
|
744
|
+
return registry;
|
|
745
|
+
}
|
|
746
|
+
function invalidateRegistryCache() {
|
|
747
|
+
cachedRegistry = null;
|
|
748
|
+
}
|
|
749
|
+
async function getAllTools() {
|
|
750
|
+
const registry = await buildRegistry();
|
|
751
|
+
return Object.values(registry).sort((a, b) => a.name.localeCompare(b.name));
|
|
752
|
+
}
|
|
753
|
+
async function getToolById(id) {
|
|
754
|
+
const registry = await buildRegistry();
|
|
755
|
+
return registry[id];
|
|
756
|
+
}
|
|
757
|
+
async function getToolsWithCapability(capability) {
|
|
758
|
+
const all = await getAllTools();
|
|
759
|
+
return all.filter((t) => t.capabilities.includes(capability));
|
|
760
|
+
}
|
|
761
|
+
async function getAllCategories() {
|
|
762
|
+
const all = await getAllTools();
|
|
763
|
+
const cats = new Set(all.map((t) => t.category));
|
|
764
|
+
return Array.from(cats).sort();
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
export {
|
|
768
|
+
mergeToolDescriptors,
|
|
769
|
+
fetchToolFromHub,
|
|
770
|
+
buildRegistry,
|
|
771
|
+
invalidateRegistryCache,
|
|
772
|
+
getAllTools,
|
|
773
|
+
getToolById,
|
|
774
|
+
getToolsWithCapability,
|
|
775
|
+
getAllCategories
|
|
776
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -6,13 +6,14 @@ import {
|
|
|
6
6
|
isToolInstalled,
|
|
7
7
|
loadTool,
|
|
8
8
|
uninstallTool
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-FY7KWENQ.js";
|
|
10
10
|
import {
|
|
11
11
|
ALL_MODELS,
|
|
12
12
|
DEFAULT_CONFIG,
|
|
13
13
|
createProject,
|
|
14
14
|
estimateCost,
|
|
15
15
|
getActiveProject,
|
|
16
|
+
getLarkupDataDir,
|
|
16
17
|
getProjectDataDir,
|
|
17
18
|
getProjectWorkspace,
|
|
18
19
|
requireProjectDataDir,
|
|
@@ -20,11 +21,11 @@ import {
|
|
|
20
21
|
runWithProject,
|
|
21
22
|
setActiveProject,
|
|
22
23
|
trackUsageEvent
|
|
23
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-T2XKBURE.js";
|
|
24
25
|
import {
|
|
25
26
|
getAllTools,
|
|
26
27
|
getToolById
|
|
27
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-YUC4VMEE.js";
|
|
28
29
|
import {
|
|
29
30
|
__require
|
|
30
31
|
} from "./chunk-QGM4M3NI.js";
|
|
@@ -35,7 +36,7 @@ import { Command } from "commander";
|
|
|
35
36
|
// package.json
|
|
36
37
|
var package_default = {
|
|
37
38
|
name: "@larkup/cli",
|
|
38
|
-
version: "0.2.
|
|
39
|
+
version: "0.2.29",
|
|
39
40
|
engines: {
|
|
40
41
|
node: ">=22"
|
|
41
42
|
},
|
|
@@ -56,20 +57,20 @@ var package_default = {
|
|
|
56
57
|
"type-check": "tsc --noEmit"
|
|
57
58
|
},
|
|
58
59
|
dependencies: {
|
|
59
|
-
"@ai-sdk/cohere": "^4.0.
|
|
60
|
-
"@ai-sdk/deepseek": "^3.0.
|
|
61
|
-
"@ai-sdk/gateway": "^4.0.
|
|
62
|
-
"@ai-sdk/google": "^4.0.
|
|
63
|
-
"@ai-sdk/mistral": "^4.0.
|
|
64
|
-
"@ai-sdk/openai": "^4.0.
|
|
65
|
-
"@ai-sdk/openai-compatible": "^3.0.
|
|
60
|
+
"@ai-sdk/cohere": "^4.0.35",
|
|
61
|
+
"@ai-sdk/deepseek": "^3.0.37",
|
|
62
|
+
"@ai-sdk/gateway": "^4.0.70",
|
|
63
|
+
"@ai-sdk/google": "^4.0.59",
|
|
64
|
+
"@ai-sdk/mistral": "^4.0.37",
|
|
65
|
+
"@ai-sdk/openai": "^4.0.53",
|
|
66
|
+
"@ai-sdk/openai-compatible": "^3.0.41",
|
|
66
67
|
"@clack/prompts": "^1.7.0",
|
|
67
|
-
"@lancedb/lancedb": "^0.
|
|
68
|
+
"@lancedb/lancedb": "^0.38.0",
|
|
68
69
|
"@larkup/core": "workspace:*",
|
|
69
70
|
"@larkup/marketplace": "workspace:*",
|
|
70
71
|
"@larkup/vector-stores": "workspace:*",
|
|
71
72
|
"@pinecone-database/pinecone": "^8.1.0",
|
|
72
|
-
ai: "^7.0.
|
|
73
|
+
ai: "^7.0.87",
|
|
73
74
|
"apache-arrow": "18.1.0",
|
|
74
75
|
chalk: "^6.0.0",
|
|
75
76
|
chromadb: "^3.5.0",
|
|
@@ -224,13 +225,22 @@ async function readConfig() {
|
|
|
224
225
|
migratedEmbeddingId = `custom:${legacy.modelName}`;
|
|
225
226
|
}
|
|
226
227
|
}
|
|
228
|
+
const storedDbPath = parsed.storeConfig?.dbPath;
|
|
229
|
+
const usesLegacyRelativeProjectPath = typeof storedDbPath === "string" && /^(?:\.\/)?\.larkup\/projects\/[^/]+\/index\/?$/.test(storedDbPath);
|
|
227
230
|
const result = {
|
|
228
231
|
...DEFAULT_CONFIG,
|
|
229
232
|
...parsed,
|
|
230
233
|
embeddingModelId: migratedEmbeddingId ?? DEFAULT_CONFIG.embeddingModelId,
|
|
231
234
|
customEmbeddings: migratedEmbeddings,
|
|
232
235
|
chunking: { ...DEFAULT_CONFIG.chunking, ...parsed.chunking },
|
|
233
|
-
storeConfig: {
|
|
236
|
+
storeConfig: {
|
|
237
|
+
...DEFAULT_CONFIG.storeConfig,
|
|
238
|
+
...parsed.storeConfig,
|
|
239
|
+
// A copied packaged workspace used to point its vector index back into
|
|
240
|
+
// node_modules via a relative path. Keep the index alongside the
|
|
241
|
+
// migrated project whenever a durable data root is configured.
|
|
242
|
+
...process.env.LARKUP_DATA_DIR?.trim() && usesLegacyRelativeProjectPath ? { dbPath: path.join(dir, "index") } : {}
|
|
243
|
+
}
|
|
234
244
|
};
|
|
235
245
|
delete result["customEmbedding"];
|
|
236
246
|
return result;
|
|
@@ -3261,10 +3271,7 @@ function resolveRuntimePluginVersion(plugin, portable = false) {
|
|
|
3261
3271
|
async function loadEnabledPlugins(config, portable = false) {
|
|
3262
3272
|
if (config.runtimeProfile !== "assistant") return [];
|
|
3263
3273
|
try {
|
|
3264
|
-
const raw = await fs3.readFile(
|
|
3265
|
-
path3.join(process.cwd(), ".larkup", "tools", "installed.json"),
|
|
3266
|
-
"utf8"
|
|
3267
|
-
);
|
|
3274
|
+
const raw = await fs3.readFile(path3.join(getLarkupDataDir(), "tools", "installed.json"), "utf8");
|
|
3268
3275
|
const parsed = JSON.parse(raw);
|
|
3269
3276
|
const enabled = new Set(config.enabledTools ?? []);
|
|
3270
3277
|
const installed = (parsed.tools ?? []).filter(
|
|
@@ -4174,7 +4181,7 @@ async function embedTexts(config, texts, abortSignal) {
|
|
|
4174
4181
|
...abortSignal ? { abortSignal } : {}
|
|
4175
4182
|
});
|
|
4176
4183
|
const totalTokens = texts.reduce((acc, t) => acc + Math.ceil(t.length / 4), 0);
|
|
4177
|
-
const { trackUsageEvent: trackUsageEvent2, estimateCost: estimateCost2 } = await import("./analytics-store-
|
|
4184
|
+
const { trackUsageEvent: trackUsageEvent2, estimateCost: estimateCost2 } = await import("./analytics-store-5BPWULY4.js");
|
|
4178
4185
|
void trackUsageEvent2({
|
|
4179
4186
|
type: "embedding",
|
|
4180
4187
|
embeddingModelId: config.embeddingModelId,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@larkup/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.29",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": ">=22"
|
|
6
6
|
},
|
|
@@ -15,25 +15,25 @@
|
|
|
15
15
|
"dist"
|
|
16
16
|
],
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@ai-sdk/cohere": "^4.0.
|
|
19
|
-
"@ai-sdk/deepseek": "^3.0.
|
|
20
|
-
"@ai-sdk/gateway": "^4.0.
|
|
21
|
-
"@ai-sdk/google": "^4.0.
|
|
22
|
-
"@ai-sdk/mistral": "^4.0.
|
|
23
|
-
"@ai-sdk/openai": "^4.0.
|
|
24
|
-
"@ai-sdk/openai-compatible": "^3.0.
|
|
18
|
+
"@ai-sdk/cohere": "^4.0.35",
|
|
19
|
+
"@ai-sdk/deepseek": "^3.0.37",
|
|
20
|
+
"@ai-sdk/gateway": "^4.0.70",
|
|
21
|
+
"@ai-sdk/google": "^4.0.59",
|
|
22
|
+
"@ai-sdk/mistral": "^4.0.37",
|
|
23
|
+
"@ai-sdk/openai": "^4.0.53",
|
|
24
|
+
"@ai-sdk/openai-compatible": "^3.0.41",
|
|
25
25
|
"@clack/prompts": "^1.7.0",
|
|
26
|
-
"@lancedb/lancedb": "^0.
|
|
26
|
+
"@lancedb/lancedb": "^0.38.0",
|
|
27
27
|
"@pinecone-database/pinecone": "^8.1.0",
|
|
28
|
-
"ai": "^7.0.
|
|
28
|
+
"ai": "^7.0.87",
|
|
29
29
|
"apache-arrow": "18.1.0",
|
|
30
30
|
"chalk": "^6.0.0",
|
|
31
31
|
"chromadb": "^3.5.0",
|
|
32
32
|
"cli-table3": "^0.6.5",
|
|
33
33
|
"commander": "^15.0.0",
|
|
34
34
|
"zod": "^4.4.3",
|
|
35
|
-
"@larkup/core": "0.5.
|
|
36
|
-
"@larkup/marketplace": "0.2.
|
|
35
|
+
"@larkup/core": "0.5.2",
|
|
36
|
+
"@larkup/marketplace": "0.2.2",
|
|
37
37
|
"@larkup/vector-stores": "0.1.27"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
package/dist/chunk-5UWKP64Q.js
DELETED
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
// ../../packages/marketplace/src/tool-registry.ts
|
|
2
|
-
import { promises as fs } from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
|
|
5
|
-
// ../../packages/marketplace/src/types.ts
|
|
6
|
-
var DEFAULT_HUB_URL = process.env.LARKUP_HUB_URL ?? "https://hub.larkup.de";
|
|
7
|
-
var STORAGE_WARNING_THRESHOLDS = [
|
|
8
|
-
1 * 1024 * 1024 * 1024,
|
|
9
|
-
// 1 GB
|
|
10
|
-
5 * 1024 * 1024 * 1024,
|
|
11
|
-
// 5 GB
|
|
12
|
-
10 * 1024 * 1024 * 1024
|
|
13
|
-
// 10 GB
|
|
14
|
-
];
|
|
15
|
-
|
|
16
|
-
// ../../packages/marketplace/src/tool-registry.ts
|
|
17
|
-
var cachedRegistry = null;
|
|
18
|
-
var RETIRED_CATALOG_TOOL_IDS = /* @__PURE__ */ new Set(["video-audio"]);
|
|
19
|
-
function normalizeToolDescriptor(descriptor) {
|
|
20
|
-
return { ...descriptor, requiresSandbox: descriptor.requiresSandbox !== false };
|
|
21
|
-
}
|
|
22
|
-
function compareToolVersions(left, right) {
|
|
23
|
-
const parse = (version) => {
|
|
24
|
-
const [core, suffix = ""] = version.split("-", 2);
|
|
25
|
-
const parts = core.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
26
|
-
return { parts, stable: suffix.length === 0 };
|
|
27
|
-
};
|
|
28
|
-
const a = parse(left);
|
|
29
|
-
const b = parse(right);
|
|
30
|
-
const length = Math.max(a.parts.length, b.parts.length);
|
|
31
|
-
for (let index = 0; index < length; index += 1) {
|
|
32
|
-
const difference = (a.parts[index] ?? 0) - (b.parts[index] ?? 0);
|
|
33
|
-
if (difference !== 0) return difference;
|
|
34
|
-
}
|
|
35
|
-
if (a.stable === b.stable) return 0;
|
|
36
|
-
return a.stable ? 1 : -1;
|
|
37
|
-
}
|
|
38
|
-
function mergeToolDescriptors(current, incoming) {
|
|
39
|
-
const merged = { ...current };
|
|
40
|
-
for (const [id, descriptor] of Object.entries(incoming)) {
|
|
41
|
-
const existing = merged[id];
|
|
42
|
-
if (!existing || compareToolVersions(descriptor.version, existing.version) >= 0) {
|
|
43
|
-
merged[id] = descriptor;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return merged;
|
|
47
|
-
}
|
|
48
|
-
async function discoverLocalManifests() {
|
|
49
|
-
const registry = {};
|
|
50
|
-
const searchPaths = [
|
|
51
|
-
// Monorepo development: packages/marketplace-tools/*
|
|
52
|
-
path.resolve(process.cwd(), "packages", "marketplace-tools"),
|
|
53
|
-
// Next.js dev server runs in apps/web
|
|
54
|
-
path.resolve(process.cwd(), "..", "..", "packages", "marketplace-tools"),
|
|
55
|
-
// Installed tools: .larkup/tools/node_modules/@larkup
|
|
56
|
-
path.resolve(process.cwd(), ".larkup", "tools", "node_modules", "@larkup"),
|
|
57
|
-
path.resolve(process.cwd(), "..", "..", ".larkup", "tools", "node_modules", "@larkup")
|
|
58
|
-
];
|
|
59
|
-
for (const searchPath of searchPaths) {
|
|
60
|
-
try {
|
|
61
|
-
const entries = await fs.readdir(searchPath, { withFileTypes: true });
|
|
62
|
-
for (const entry of entries) {
|
|
63
|
-
if (!entry.isDirectory()) continue;
|
|
64
|
-
const manifestPath = path.join(searchPath, entry.name, "tool.manifest.json");
|
|
65
|
-
try {
|
|
66
|
-
const raw = await fs.readFile(manifestPath, "utf8");
|
|
67
|
-
const manifest = normalizeToolDescriptor(JSON.parse(raw));
|
|
68
|
-
if (manifest.id && !RETIRED_CATALOG_TOOL_IDS.has(manifest.id)) {
|
|
69
|
-
registry[manifest.id] = manifest;
|
|
70
|
-
}
|
|
71
|
-
} catch {
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
} catch {
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return registry;
|
|
78
|
-
}
|
|
79
|
-
async function fetchHubCatalog(hubUrl) {
|
|
80
|
-
const baseUrl = hubUrl ?? DEFAULT_HUB_URL;
|
|
81
|
-
try {
|
|
82
|
-
const controller = new AbortController();
|
|
83
|
-
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
84
|
-
const res = await fetch(`${baseUrl}/v1/tools`, {
|
|
85
|
-
signal: controller.signal,
|
|
86
|
-
headers: { Accept: "application/json" }
|
|
87
|
-
});
|
|
88
|
-
clearTimeout(timeout);
|
|
89
|
-
if (!res.ok) return {};
|
|
90
|
-
const data = await res.json();
|
|
91
|
-
const registry = {};
|
|
92
|
-
for (const tool of data.tools ?? []) {
|
|
93
|
-
if (tool.id && !RETIRED_CATALOG_TOOL_IDS.has(tool.id)) {
|
|
94
|
-
registry[tool.id] = normalizeToolDescriptor(tool);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return registry;
|
|
98
|
-
} catch {
|
|
99
|
-
return {};
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
async function fetchToolFromHub(toolId, hubUrl) {
|
|
103
|
-
if (RETIRED_CATALOG_TOOL_IDS.has(toolId)) return null;
|
|
104
|
-
const baseUrl = hubUrl ?? DEFAULT_HUB_URL;
|
|
105
|
-
try {
|
|
106
|
-
const controller = new AbortController();
|
|
107
|
-
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
108
|
-
const res = await fetch(`${baseUrl}/v1/tools/${toolId}`, {
|
|
109
|
-
signal: controller.signal,
|
|
110
|
-
headers: { Accept: "application/json" }
|
|
111
|
-
});
|
|
112
|
-
clearTimeout(timeout);
|
|
113
|
-
if (!res.ok) return null;
|
|
114
|
-
const data = await res.json();
|
|
115
|
-
return data.tool ?? null;
|
|
116
|
-
} catch {
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
var FALLBACK_REGISTRY = {
|
|
121
|
-
"clip-embeddings": {
|
|
122
|
-
id: "clip-embeddings",
|
|
123
|
-
name: "CLIP Image Search",
|
|
124
|
-
description: "Direct image-to-image similarity search using CLIP/SigLIP embeddings.",
|
|
125
|
-
category: "embedding",
|
|
126
|
-
version: "0.1.0",
|
|
127
|
-
pricing: "free",
|
|
128
|
-
emoji: "\u{1F50D}",
|
|
129
|
-
icon: "ScanEye",
|
|
130
|
-
packageName: "@larkup/tool-clip-embeddings",
|
|
131
|
-
installSize: "~200 MB",
|
|
132
|
-
requiresSandbox: true,
|
|
133
|
-
author: "Larkup",
|
|
134
|
-
capabilities: ["clip-embeddings", "image-similarity"],
|
|
135
|
-
tags: ["clip", "siglip", "image-search", "visual-similarity"],
|
|
136
|
-
downloads: 0,
|
|
137
|
-
repositoryUrl: "https://github.com/Larkup-AI/larkup",
|
|
138
|
-
license: "Apache-2.0",
|
|
139
|
-
updatedAt: "2026-07-20",
|
|
140
|
-
comingSoon: true
|
|
141
|
-
},
|
|
142
|
-
"doc-editor": {
|
|
143
|
-
id: "doc-editor",
|
|
144
|
-
name: "Document Editor",
|
|
145
|
-
description: "AI-powered form filling and document editing with Canvas-style live preview.",
|
|
146
|
-
category: "utility",
|
|
147
|
-
version: "0.2.3",
|
|
148
|
-
pricing: "free",
|
|
149
|
-
emoji: "\u{1F4DD}",
|
|
150
|
-
icon: "FileEdit",
|
|
151
|
-
packageName: "@larkup/tool-doc-editor",
|
|
152
|
-
installSize: "~5 MB",
|
|
153
|
-
systemDeps: ["docker"],
|
|
154
|
-
requiresSandbox: true,
|
|
155
|
-
author: "Larkup",
|
|
156
|
-
capabilities: ["document-editing", "form-filling", "document-preview"],
|
|
157
|
-
tags: ["pdf", "docx", "pptx", "form", "canvas", "editor", "fill"],
|
|
158
|
-
downloads: 0,
|
|
159
|
-
repositoryUrl: "https://github.com/Larkup-AI/larkup",
|
|
160
|
-
license: "Apache-2.0",
|
|
161
|
-
updatedAt: "2026-07-20"
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
async function buildRegistry(opts) {
|
|
165
|
-
if (cachedRegistry) return cachedRegistry;
|
|
166
|
-
let registry = { ...FALLBACK_REGISTRY };
|
|
167
|
-
let hubAvailable = false;
|
|
168
|
-
if (!opts?.skipHub) {
|
|
169
|
-
const hubCatalog = await fetchHubCatalog(opts?.hubUrl);
|
|
170
|
-
if (Object.keys(hubCatalog).length > 0) {
|
|
171
|
-
registry = mergeToolDescriptors(registry, hubCatalog);
|
|
172
|
-
hubAvailable = true;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
if (!hubAvailable || opts?.preferLocal) {
|
|
176
|
-
const localManifests = await discoverLocalManifests();
|
|
177
|
-
registry = mergeToolDescriptors(registry, localManifests);
|
|
178
|
-
}
|
|
179
|
-
cachedRegistry = registry;
|
|
180
|
-
return registry;
|
|
181
|
-
}
|
|
182
|
-
function invalidateRegistryCache() {
|
|
183
|
-
cachedRegistry = null;
|
|
184
|
-
}
|
|
185
|
-
async function getAllTools() {
|
|
186
|
-
const registry = await buildRegistry();
|
|
187
|
-
return Object.values(registry).sort((a, b) => a.name.localeCompare(b.name));
|
|
188
|
-
}
|
|
189
|
-
async function getToolById(id) {
|
|
190
|
-
const registry = await buildRegistry();
|
|
191
|
-
return registry[id];
|
|
192
|
-
}
|
|
193
|
-
async function getToolsWithCapability(capability) {
|
|
194
|
-
const all = await getAllTools();
|
|
195
|
-
return all.filter((t) => t.capabilities.includes(capability));
|
|
196
|
-
}
|
|
197
|
-
async function getAllCategories() {
|
|
198
|
-
const all = await getAllTools();
|
|
199
|
-
const cats = new Set(all.map((t) => t.category));
|
|
200
|
-
return Array.from(cats).sort();
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
export {
|
|
204
|
-
mergeToolDescriptors,
|
|
205
|
-
fetchToolFromHub,
|
|
206
|
-
buildRegistry,
|
|
207
|
-
invalidateRegistryCache,
|
|
208
|
-
getAllTools,
|
|
209
|
-
getToolById,
|
|
210
|
-
getToolsWithCapability,
|
|
211
|
-
getAllCategories
|
|
212
|
-
};
|