@larkup/cli 0.2.7 → 0.2.9

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.
@@ -0,0 +1,231 @@
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
+ async function discoverLocalManifests() {
19
+ const registry = {};
20
+ const searchPaths = [
21
+ // Monorepo development: packages/tools/*
22
+ path.resolve(process.cwd(), "packages", "tools"),
23
+ // Next.js dev server runs in apps/web
24
+ path.resolve(process.cwd(), "..", "..", "packages", "tools"),
25
+ // Installed tools: .larkup/tools/node_modules/@larkup
26
+ path.resolve(process.cwd(), ".larkup", "tools", "node_modules", "@larkup"),
27
+ path.resolve(process.cwd(), "..", "..", ".larkup", "tools", "node_modules", "@larkup")
28
+ ];
29
+ for (const searchPath of searchPaths) {
30
+ try {
31
+ const entries = await fs.readdir(searchPath, { withFileTypes: true });
32
+ for (const entry of entries) {
33
+ if (!entry.isDirectory()) continue;
34
+ const manifestPath = path.join(searchPath, entry.name, "tool.manifest.json");
35
+ try {
36
+ const raw = await fs.readFile(manifestPath, "utf8");
37
+ const manifest = JSON.parse(raw);
38
+ if (manifest.id) {
39
+ registry[manifest.id] = manifest;
40
+ }
41
+ } catch {
42
+ }
43
+ }
44
+ } catch {
45
+ }
46
+ }
47
+ return registry;
48
+ }
49
+ async function fetchHubCatalog(hubUrl) {
50
+ const baseUrl = hubUrl ?? DEFAULT_HUB_URL;
51
+ try {
52
+ const controller = new AbortController();
53
+ const timeout = setTimeout(() => controller.abort(), 5e3);
54
+ const res = await fetch(`${baseUrl}/v1/tools`, {
55
+ signal: controller.signal,
56
+ headers: { Accept: "application/json" }
57
+ });
58
+ clearTimeout(timeout);
59
+ if (!res.ok) return {};
60
+ const data = await res.json();
61
+ const registry = {};
62
+ for (const tool of data.tools ?? []) {
63
+ if (tool.id) registry[tool.id] = tool;
64
+ }
65
+ return registry;
66
+ } catch {
67
+ return {};
68
+ }
69
+ }
70
+ async function fetchToolFromHub(toolId, hubUrl) {
71
+ const baseUrl = hubUrl ?? DEFAULT_HUB_URL;
72
+ try {
73
+ const controller = new AbortController();
74
+ const timeout = setTimeout(() => controller.abort(), 5e3);
75
+ const res = await fetch(`${baseUrl}/v1/tools/${toolId}`, {
76
+ signal: controller.signal,
77
+ headers: { Accept: "application/json" }
78
+ });
79
+ clearTimeout(timeout);
80
+ if (!res.ok) return null;
81
+ const data = await res.json();
82
+ return data.tool ?? null;
83
+ } catch {
84
+ return null;
85
+ }
86
+ }
87
+ var FALLBACK_REGISTRY = {
88
+ "video-audio": {
89
+ id: "video-audio",
90
+ name: "Video & Audio",
91
+ description: "Index video and audio files with transcription and frame analysis.",
92
+ category: "media",
93
+ version: "0.2.1",
94
+ pricing: "free",
95
+ emoji: "\u{1F3AC}",
96
+ icon: "Film",
97
+ packageName: "@larkup/tool-video-audio",
98
+ installSize: "~15 MB",
99
+ systemDeps: ["ffmpeg"],
100
+ author: "Larkup",
101
+ capabilities: [
102
+ "video-indexing",
103
+ "audio-indexing",
104
+ "transcription",
105
+ "frame-extraction",
106
+ "youtube-import"
107
+ ],
108
+ tags: ["transcription", "ffmpeg", "video", "audio", "whisper"],
109
+ downloads: 0,
110
+ repositoryUrl: "https://github.com/Larkup-AI/larkup",
111
+ license: "Apache-2.0",
112
+ updatedAt: "2026-07-20",
113
+ configSchema: [
114
+ {
115
+ key: "frameInterval",
116
+ label: "Frame extraction interval (seconds)",
117
+ type: "text",
118
+ defaultValue: "10",
119
+ help: "Baseline cadence for duration-aware coverage frames; scene changes and a reserved ending pass are added separately."
120
+ },
121
+ {
122
+ key: "audioProvider",
123
+ label: "Audio Provider",
124
+ type: "select",
125
+ help: "Choose the provider used only for audio and video transcription.",
126
+ options: [
127
+ { label: "OpenAI", value: "openai" },
128
+ { label: "Groq", value: "groq" },
129
+ { label: "Deepgram", value: "deepgram" },
130
+ { label: "ElevenLabs", value: "elevenlabs" },
131
+ { label: "Local Whisper", value: "local" }
132
+ ]
133
+ },
134
+ {
135
+ key: "audioLanguage",
136
+ label: "Transcription language",
137
+ type: "text",
138
+ defaultValue: "auto",
139
+ help: "Use auto to infer Arabic from the media title and use provider detection otherwise. Enter a language code such as ar, ar-EG, en, or de to override it."
140
+ },
141
+ {
142
+ key: "audioApiKey",
143
+ label: "Audio API Key",
144
+ type: "password",
145
+ help: "Required for the selected audio provider and kept separate from the chat model key."
146
+ }
147
+ ]
148
+ },
149
+ "clip-embeddings": {
150
+ id: "clip-embeddings",
151
+ name: "CLIP Image Search",
152
+ description: "Direct image-to-image similarity search using CLIP/SigLIP embeddings.",
153
+ category: "embedding",
154
+ version: "0.1.0",
155
+ pricing: "free",
156
+ emoji: "\u{1F50D}",
157
+ icon: "ScanEye",
158
+ packageName: "@larkup/tool-clip-embeddings",
159
+ installSize: "~200 MB",
160
+ author: "Larkup",
161
+ capabilities: ["clip-embeddings", "image-similarity"],
162
+ tags: ["clip", "siglip", "image-search", "visual-similarity"],
163
+ downloads: 0,
164
+ repositoryUrl: "https://github.com/Larkup-AI/larkup",
165
+ license: "Apache-2.0",
166
+ updatedAt: "2026-07-20",
167
+ comingSoon: true
168
+ },
169
+ "doc-editor": {
170
+ id: "doc-editor",
171
+ name: "Document Editor",
172
+ description: "AI-powered form filling and document editing with Canvas-style live preview.",
173
+ category: "utility",
174
+ version: "0.2.3",
175
+ pricing: "free",
176
+ emoji: "\u{1F4DD}",
177
+ icon: "FileEdit",
178
+ packageName: "@larkup/tool-doc-editor",
179
+ installSize: "~5 MB",
180
+ systemDeps: ["docker"],
181
+ author: "Larkup",
182
+ capabilities: ["document-editing", "form-filling", "document-preview"],
183
+ tags: ["pdf", "docx", "pptx", "form", "canvas", "editor", "fill"],
184
+ downloads: 0,
185
+ repositoryUrl: "https://github.com/Larkup-AI/larkup",
186
+ license: "Apache-2.0",
187
+ updatedAt: "2026-07-20"
188
+ }
189
+ };
190
+ async function buildRegistry(opts) {
191
+ if (cachedRegistry) return cachedRegistry;
192
+ const registry = { ...FALLBACK_REGISTRY };
193
+ const localManifests = await discoverLocalManifests();
194
+ Object.assign(registry, localManifests);
195
+ if (!opts?.skipHub) {
196
+ const hubCatalog = await fetchHubCatalog(opts?.hubUrl);
197
+ Object.assign(registry, hubCatalog);
198
+ }
199
+ cachedRegistry = registry;
200
+ return registry;
201
+ }
202
+ function invalidateRegistryCache() {
203
+ cachedRegistry = null;
204
+ }
205
+ async function getAllTools() {
206
+ const registry = await buildRegistry();
207
+ return Object.values(registry).sort((a, b) => a.name.localeCompare(b.name));
208
+ }
209
+ async function getToolById(id) {
210
+ const registry = await buildRegistry();
211
+ return registry[id];
212
+ }
213
+ async function getToolsWithCapability(capability) {
214
+ const all = await getAllTools();
215
+ return all.filter((t) => t.capabilities.includes(capability));
216
+ }
217
+ async function getAllCategories() {
218
+ const all = await getAllTools();
219
+ const cats = new Set(all.map((t) => t.category));
220
+ return Array.from(cats).sort();
221
+ }
222
+
223
+ export {
224
+ fetchToolFromHub,
225
+ buildRegistry,
226
+ invalidateRegistryCache,
227
+ getAllTools,
228
+ getToolById,
229
+ getToolsWithCapability,
230
+ getAllCategories
231
+ };
@@ -0,0 +1,420 @@
1
+ import {
2
+ getToolById,
3
+ invalidateRegistryCache
4
+ } from "./chunk-UCYL66UI.js";
5
+ import {
6
+ __require
7
+ } from "./chunk-3RG5ZIWI.js";
8
+
9
+ // ../../packages/marketplace/src/tool-loader.ts
10
+ import { promises as fs2 } from "fs";
11
+ import path2 from "path";
12
+ import { pathToFileURL } from "url";
13
+
14
+ // ../../packages/marketplace/src/tool-installer.ts
15
+ import { promises as fs } from "fs";
16
+ import path from "path";
17
+ import { exec as execCb } from "child_process";
18
+ import { promisify } from "util";
19
+ var execAsync = promisify(execCb);
20
+ var packageMutationChain = Promise.resolve();
21
+ function serializePackageMutation(fn) {
22
+ const run = packageMutationChain.then(fn, fn);
23
+ packageMutationChain = run.catch(() => {
24
+ });
25
+ return run;
26
+ }
27
+ function getToolsDir() {
28
+ return path.join(process.cwd(), ".larkup", "tools");
29
+ }
30
+ function getManifestPath() {
31
+ return path.join(getToolsDir(), "installed.json");
32
+ }
33
+ async function ensureDir() {
34
+ await fs.mkdir(getToolsDir(), { recursive: true });
35
+ }
36
+ async function readManifest() {
37
+ try {
38
+ const raw = await fs.readFile(getManifestPath(), "utf8");
39
+ const parsed = JSON.parse(raw);
40
+ if (!parsed.downloadCounts) parsed.downloadCounts = {};
41
+ for (const tool of parsed.tools ?? []) {
42
+ if (tool.packagePath && !tool.packageName) {
43
+ tool.packageName = tool.packagePath;
44
+ delete tool.packagePath;
45
+ }
46
+ if (!tool.source) tool.source = "local";
47
+ if (!tool.resolvedPath) {
48
+ tool.resolvedPath = tool.packageName;
49
+ }
50
+ }
51
+ return parsed;
52
+ } catch {
53
+ return { tools: [], downloadCounts: {}, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
54
+ }
55
+ }
56
+ async function writeManifest(manifest) {
57
+ await ensureDir();
58
+ manifest.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
59
+ await fs.writeFile(getManifestPath(), JSON.stringify(manifest, null, 2), "utf8");
60
+ }
61
+ async function getInstalledTools() {
62
+ const manifest = await readManifest();
63
+ return [...manifest.tools];
64
+ }
65
+ async function isToolInstalled(toolId) {
66
+ const tools = await getInstalledTools();
67
+ return tools.some((t) => t.id === toolId);
68
+ }
69
+ async function getInstalledTool(toolId) {
70
+ const tools = await getInstalledTools();
71
+ return tools.find((t) => t.id === toolId);
72
+ }
73
+ async function checkSystemDeps(toolId) {
74
+ const descriptor = await getToolById(toolId);
75
+ if (!descriptor?.systemDeps?.length) return [];
76
+ const target = detectDeploymentTarget();
77
+ const missing = [];
78
+ for (const dep of descriptor.systemDeps) {
79
+ if (target === "docker" && dep === "docker") continue;
80
+ try {
81
+ await execAsync(`which ${dep}`);
82
+ } catch {
83
+ missing.push(dep);
84
+ }
85
+ }
86
+ return missing;
87
+ }
88
+ function detectDeploymentTarget() {
89
+ const explicit = process.env.LARKUP_DEPLOYMENT_TARGET;
90
+ if (explicit && ["local", "docker", "serverless", "sandbox"].includes(explicit)) {
91
+ return explicit;
92
+ }
93
+ if (process.env.VERCEL || process.env.AWS_LAMBDA_FUNCTION_NAME) return "serverless";
94
+ if (process.env.DOCKER_CONTAINER || isDockerEnvironment()) return "docker";
95
+ if (process.env.E2B_SANDBOX_ID || process.env.MODAL_TASK_ID) return "sandbox";
96
+ return "local";
97
+ }
98
+ function isDockerEnvironment() {
99
+ try {
100
+ const fs3 = __require("fs");
101
+ return fs3.existsSync("/.dockerenv");
102
+ } catch {
103
+ return false;
104
+ }
105
+ }
106
+ async function ensureToolsPackageJson() {
107
+ const toolsDir = getToolsDir();
108
+ const pkgPath = path.join(toolsDir, "package.json");
109
+ try {
110
+ await fs.access(pkgPath);
111
+ } catch {
112
+ await ensureDir();
113
+ await fs.writeFile(
114
+ pkgPath,
115
+ JSON.stringify(
116
+ {
117
+ name: "larkup-tools",
118
+ version: "1.0.0",
119
+ private: true,
120
+ description: "Isolated directory for installed Larkup marketplace tools."
121
+ },
122
+ null,
123
+ 2
124
+ ),
125
+ "utf8"
126
+ );
127
+ }
128
+ }
129
+ async function execInstall(packageName, version, target, onProgress) {
130
+ return serializePackageMutation(
131
+ () => execInstallUnlocked(packageName, version, target, onProgress)
132
+ );
133
+ }
134
+ async function execInstallUnlocked(packageName, version, target, onProgress) {
135
+ const toolsDir = getToolsDir();
136
+ switch (target) {
137
+ case "local":
138
+ case "docker": {
139
+ await ensureToolsPackageJson();
140
+ const install = async (specifier) => {
141
+ const installCmd = `npm install ${specifier} --prefix "${toolsDir}" --save --no-audit --no-fund --prefer-offline`;
142
+ onProgress?.(`Running: npm install ${specifier}`);
143
+ const { stderr } = await execAsync(installCmd, {
144
+ cwd: toolsDir,
145
+ // Native tool dependencies can take longer under Docker Desktop's
146
+ // amd64 emulation. Let the request finish instead of reporting a
147
+ // misleading connection error part way through installation.
148
+ timeout: target === "docker" ? 3e5 : 12e4,
149
+ env: {
150
+ ...process.env,
151
+ NODE_ENV: "production",
152
+ // The Docker image runs as an unprivileged user. Keep npm's cache
153
+ // beside the isolated install so a named .larkup volume is always
154
+ // writable and installing a Marketplace tool does not fail before
155
+ // it reaches the registry.
156
+ HOME: process.env.HOME && process.env.HOME !== "/nonexistent" ? process.env.HOME : toolsDir,
157
+ npm_config_cache: path.join(toolsDir, ".npm-cache"),
158
+ npm_config_update_notifier: "false"
159
+ }
160
+ });
161
+ if (stderr && !stderr.includes("npm warn")) {
162
+ console.warn(`[marketplace] Install stderr: ${stderr}`);
163
+ }
164
+ };
165
+ try {
166
+ await install(`${packageName}@${version}`);
167
+ } catch (err) {
168
+ const message = err instanceof Error ? err.message : "Install command failed";
169
+ if (!message.includes("ETARGET") && !message.includes("No matching version found")) {
170
+ throw new Error(`Failed to install ${packageName}: ${message}`);
171
+ }
172
+ onProgress?.(
173
+ `Catalog version ${version} is unavailable; installing the latest published version\u2026`
174
+ );
175
+ try {
176
+ await install(`${packageName}@latest`);
177
+ } catch (latestErr) {
178
+ const latestMessage = latestErr instanceof Error ? latestErr.message : "Install command failed";
179
+ throw new Error(`Failed to install ${packageName}: ${latestMessage}`);
180
+ }
181
+ }
182
+ const resolvedPath = path.join(toolsDir, "node_modules", packageName);
183
+ try {
184
+ await fs.access(resolvedPath);
185
+ } catch {
186
+ throw new Error(
187
+ `Package ${packageName} was installed but could not be found at ${resolvedPath}`
188
+ );
189
+ }
190
+ const packageJson = JSON.parse(
191
+ await fs.readFile(path.join(resolvedPath, "package.json"), "utf8")
192
+ );
193
+ return { resolvedPath, version: packageJson.version ?? version };
194
+ }
195
+ case "serverless": {
196
+ onProgress?.("Serverless environment detected \u2014 queuing for next deploy");
197
+ const pendingPath = path.join(toolsDir, "pending-tools.json");
198
+ let pending = { tools: [] };
199
+ try {
200
+ const raw = await fs.readFile(pendingPath, "utf8");
201
+ pending = JSON.parse(raw);
202
+ } catch {
203
+ }
204
+ pending.tools.push({ packageName, version });
205
+ await ensureDir();
206
+ await fs.writeFile(pendingPath, JSON.stringify(pending, null, 2), "utf8");
207
+ return { resolvedPath: path.join(toolsDir, "node_modules", packageName), version };
208
+ }
209
+ case "sandbox": {
210
+ onProgress?.("Installing in sandbox environment");
211
+ await ensureToolsPackageJson();
212
+ const installCmd = `npm install ${packageName}@${version} --prefix "${toolsDir}" --save --no-audit --no-fund`;
213
+ await execAsync(installCmd, { cwd: toolsDir, timeout: 12e4 });
214
+ return { resolvedPath: path.join(toolsDir, "node_modules", packageName), version };
215
+ }
216
+ default:
217
+ throw new Error(`Unsupported deployment target: ${target}`);
218
+ }
219
+ }
220
+ async function resolveManifest(toolId) {
221
+ const descriptor = await getToolById(toolId);
222
+ if (descriptor) return descriptor;
223
+ throw new Error(`Unknown tool: ${toolId}. Not found in local registry or Hub.`);
224
+ }
225
+ async function isWorkspaceTool(packageName) {
226
+ try {
227
+ const { stdout } = await execAsync(`pnpm ls -r --depth -1 --json 2>/dev/null || true`, {
228
+ cwd: process.cwd(),
229
+ timeout: 1e4
230
+ });
231
+ const data = JSON.parse(stdout || "[]");
232
+ return Array.isArray(data) && data.some((pkg) => pkg.name === packageName);
233
+ } catch {
234
+ return false;
235
+ }
236
+ }
237
+ async function installTool(toolId, onProgress) {
238
+ const report = (stage, percent, message) => {
239
+ onProgress?.({ toolId, stage, percent, message });
240
+ };
241
+ try {
242
+ report("checking-deps", 5, "Resolving tool manifest\u2026");
243
+ const descriptor = await resolveManifest(toolId);
244
+ if (descriptor.comingSoon) {
245
+ throw new Error(`${descriptor.name} is coming soon.`);
246
+ }
247
+ report("checking-deps", 15, "Checking system dependencies\u2026");
248
+ const missing = await checkSystemDeps(toolId);
249
+ if (missing.length > 0) {
250
+ const msg = `Missing system dependencies: ${missing.join(", ")}. Please install them first.`;
251
+ report("failed", 0, msg);
252
+ throw new Error(msg);
253
+ }
254
+ const isWorkspace = await isWorkspaceTool(descriptor.packageName);
255
+ const target = detectDeploymentTarget();
256
+ let resolvedPath;
257
+ let installedVersion = descriptor.version;
258
+ let source;
259
+ if (isWorkspace) {
260
+ report("downloading", 30, "Resolving workspace package\u2026");
261
+ try {
262
+ const modulePath = __require.resolve(descriptor.packageName, {
263
+ paths: [process.cwd()]
264
+ });
265
+ resolvedPath = path.dirname(modulePath);
266
+ } catch {
267
+ resolvedPath = path.join(process.cwd(), "node_modules", descriptor.packageName);
268
+ }
269
+ source = "local";
270
+ report("installing", 60, "Workspace package resolved.");
271
+ } else {
272
+ report("downloading", 30, `Downloading ${descriptor.packageName}@${descriptor.version}\u2026`);
273
+ const installed = await execInstall(
274
+ descriptor.packageName,
275
+ descriptor.version,
276
+ target,
277
+ (msg) => report("downloading", 50, msg)
278
+ );
279
+ resolvedPath = installed.resolvedPath;
280
+ installedVersion = installed.version;
281
+ source = target === "sandbox" ? "sandbox" : "registry";
282
+ report("installing", 70, "Package installed.");
283
+ }
284
+ report("installing", 80, "Registering tool\u2026");
285
+ await ensureDir();
286
+ const manifest = await readManifest();
287
+ const existing = manifest.tools.findIndex((t) => t.id === toolId);
288
+ const entry = {
289
+ id: toolId,
290
+ version: installedVersion,
291
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
292
+ packageName: descriptor.packageName,
293
+ resolvedPath,
294
+ source,
295
+ config: buildDefaultConfig(descriptor)
296
+ };
297
+ if (existing >= 0) {
298
+ manifest.tools[existing] = entry;
299
+ } else {
300
+ manifest.tools.push(entry);
301
+ }
302
+ manifest.downloadCounts[toolId] = (manifest.downloadCounts[toolId] ?? 0) + 1;
303
+ await writeManifest(manifest);
304
+ notifyHubInstall(toolId).catch(() => {
305
+ });
306
+ invalidateRegistryCache();
307
+ report("completed", 100, "Installed successfully.");
308
+ } catch (err) {
309
+ const message = err instanceof Error ? err.message : "Installation failed.";
310
+ report("failed", 0, message);
311
+ throw err;
312
+ }
313
+ }
314
+ async function uninstallTool(toolId) {
315
+ const manifest = await readManifest();
316
+ const tool = manifest.tools.find((t) => t.id === toolId);
317
+ manifest.tools = manifest.tools.filter((t) => t.id !== toolId);
318
+ await writeManifest(manifest);
319
+ if (tool?.source === "registry" && tool.packageName) {
320
+ try {
321
+ const toolsDir = getToolsDir();
322
+ await execAsync(`npm uninstall ${tool.packageName} --prefix "${toolsDir}"`, {
323
+ cwd: toolsDir,
324
+ timeout: 3e4
325
+ });
326
+ } catch {
327
+ }
328
+ }
329
+ invalidateRegistryCache();
330
+ const { unloadTool: unloadTool2 } = await import("./tool-loader-VGEHM33B.js");
331
+ unloadTool2(toolId);
332
+ }
333
+ async function notifyHubInstall(toolId) {
334
+ const hubUrl = process.env.LARKUP_HUB_URL ?? "https://hub.larkup.de";
335
+ try {
336
+ const controller = new AbortController();
337
+ const timeout = setTimeout(() => controller.abort(), 3e3);
338
+ await fetch(`${hubUrl}/v1/tools/${toolId}/installed`, {
339
+ method: "POST",
340
+ signal: controller.signal,
341
+ headers: { "Content-Type": "application/json" }
342
+ });
343
+ clearTimeout(timeout);
344
+ } catch {
345
+ }
346
+ }
347
+ function buildDefaultConfig(descriptor) {
348
+ const config = {};
349
+ for (const field of descriptor.configSchema ?? []) {
350
+ if (field.defaultValue !== void 0) {
351
+ config[field.key] = field.defaultValue;
352
+ }
353
+ }
354
+ return config;
355
+ }
356
+
357
+ // ../../packages/marketplace/src/tool-loader.ts
358
+ var moduleCache = /* @__PURE__ */ new Map();
359
+ async function loadTool(toolId) {
360
+ if (moduleCache.has(toolId)) return moduleCache.get(toolId);
361
+ const installed = await getInstalledTool(toolId);
362
+ if (!installed) return null;
363
+ try {
364
+ const importPath = await resolveImportPath(installed);
365
+ const mod = await import(
366
+ /* webpackIgnore: true */
367
+ importPath
368
+ );
369
+ moduleCache.set(toolId, mod);
370
+ return mod;
371
+ } catch (err) {
372
+ console.error(`[marketplace] Failed to load tool "${toolId}":`, err);
373
+ return null;
374
+ }
375
+ }
376
+ async function resolveImportPath(installed) {
377
+ switch (installed.source) {
378
+ case "local":
379
+ return installed.packageName;
380
+ case "registry":
381
+ case "sandbox":
382
+ return resolvePackageEntry(installed.resolvedPath);
383
+ default:
384
+ return installed.packageName;
385
+ }
386
+ }
387
+ async function resolvePackageEntry(packageDir) {
388
+ const manifestPath = path2.join(packageDir, "package.json");
389
+ const manifest = JSON.parse(await fs2.readFile(manifestPath, "utf8"));
390
+ const rootExport = typeof manifest.exports === "object" ? manifest.exports["."] : manifest.exports;
391
+ const entry = (typeof rootExport === "object" ? rootExport.import ?? rootExport.default : rootExport) ?? manifest.main ?? "index.js";
392
+ return pathToFileURL(path2.resolve(packageDir, entry)).href;
393
+ }
394
+ function isToolLoaded(toolId) {
395
+ return moduleCache.has(toolId);
396
+ }
397
+ function unloadTool(toolId) {
398
+ moduleCache.delete(toolId);
399
+ }
400
+ async function hasCapability(capability) {
401
+ const { getToolsWithCapability } = await import("./tool-registry-ED45DB5F.js");
402
+ const tools = await getToolsWithCapability(capability);
403
+ for (const t of tools) {
404
+ const installed = await getInstalledTool(t.id);
405
+ if (installed) return true;
406
+ }
407
+ return false;
408
+ }
409
+
410
+ export {
411
+ loadTool,
412
+ isToolLoaded,
413
+ unloadTool,
414
+ hasCapability,
415
+ getInstalledTools,
416
+ isToolInstalled,
417
+ getInstalledTool,
418
+ installTool,
419
+ uninstallTool
420
+ };
package/dist/index.js CHANGED
@@ -12,6 +12,18 @@ import {
12
12
  setActiveServer,
13
13
  trackUsageEvent
14
14
  } from "./chunk-ZJLA4SIG.js";
15
+ import {
16
+ getInstalledTool,
17
+ getInstalledTools,
18
+ installTool,
19
+ isToolInstalled,
20
+ loadTool,
21
+ uninstallTool
22
+ } from "./chunk-ZJLKBVV6.js";
23
+ import {
24
+ getAllTools,
25
+ getToolById
26
+ } from "./chunk-UCYL66UI.js";
15
27
  import {
16
28
  __require
17
29
  } from "./chunk-3RG5ZIWI.js";
@@ -22,7 +34,7 @@ import { Command } from "commander";
22
34
  // package.json
23
35
  var package_default = {
24
36
  name: "@larkup/cli",
25
- version: "0.2.7",
37
+ version: "0.2.9",
26
38
  publishConfig: {
27
39
  access: "public"
28
40
  },
@@ -939,12 +951,12 @@ function addChatProviderDependency(dependencies, provider) {
939
951
  }
940
952
 
941
953
  // ../../packages/core/src/generator/generate-server.ts
942
- function lang(path13) {
943
- if (path13.endsWith(".json")) return "json";
944
- if (path13.endsWith(".mjs") || path13.endsWith(".js")) return "javascript";
945
- if (path13.endsWith(".md")) return "markdown";
946
- if (path13.endsWith("Dockerfile")) return "dockerfile";
947
- if (path13.endsWith(".yml") || path13.endsWith(".yaml")) return "yaml";
954
+ function lang(path14) {
955
+ if (path14.endsWith(".json")) return "json";
956
+ if (path14.endsWith(".mjs") || path14.endsWith(".js")) return "javascript";
957
+ if (path14.endsWith(".md")) return "markdown";
958
+ if (path14.endsWith("Dockerfile")) return "dockerfile";
959
+ if (path14.endsWith(".yml") || path14.endsWith(".yaml")) return "yaml";
948
960
  return "text";
949
961
  }
950
962
  function lancedbStore() {
@@ -2261,22 +2273,22 @@ function generateServer(config) {
2261
2273
  { path: "README.md", contents: readme(config, server) }
2262
2274
  ].map((f) => ({ ...f, language: lang(f.path) }));
2263
2275
  try {
2264
- const fs12 = __require("fs");
2265
- const path13 = __require("path");
2266
- const faviconPath = path13.resolve(process.cwd(), "public/favicon2.ico");
2267
- if (fs12.existsSync(faviconPath)) {
2276
+ const fs13 = __require("fs");
2277
+ const path14 = __require("path");
2278
+ const faviconPath = path14.resolve(process.cwd(), "public/favicon2.ico");
2279
+ if (fs13.existsSync(faviconPath)) {
2268
2280
  files.push({
2269
2281
  path: "public/favicon2.ico",
2270
- contents: fs12.readFileSync(faviconPath).toString("base64"),
2282
+ contents: fs13.readFileSync(faviconPath).toString("base64"),
2271
2283
  language: "ico",
2272
2284
  encoding: "base64"
2273
2285
  });
2274
2286
  } else {
2275
- const webFaviconPath = path13.resolve(process.cwd(), "apps/web/public/favicon2.ico");
2276
- if (fs12.existsSync(webFaviconPath)) {
2287
+ const webFaviconPath = path14.resolve(process.cwd(), "apps/web/public/favicon2.ico");
2288
+ if (fs13.existsSync(webFaviconPath)) {
2277
2289
  files.push({
2278
2290
  path: "public/favicon2.ico",
2279
- contents: fs12.readFileSync(webFaviconPath).toString("base64"),
2291
+ contents: fs13.readFileSync(webFaviconPath).toString("base64"),
2280
2292
  language: "ico",
2281
2293
  encoding: "base64"
2282
2294
  });
@@ -2290,12 +2302,12 @@ function generateServer(config) {
2290
2302
  }
2291
2303
 
2292
2304
  // ../../packages/core/src/generator/generate-agent-server.ts
2293
- function lang2(path13) {
2294
- if (path13.endsWith(".json")) return "json";
2295
- if (path13.endsWith(".mjs") || path13.endsWith(".js")) return "javascript";
2296
- if (path13.endsWith(".md")) return "markdown";
2297
- if (path13.endsWith("Dockerfile")) return "dockerfile";
2298
- if (path13.endsWith(".yml") || path13.endsWith(".yaml")) return "yaml";
2305
+ function lang2(path14) {
2306
+ if (path14.endsWith(".json")) return "json";
2307
+ if (path14.endsWith(".mjs") || path14.endsWith(".js")) return "javascript";
2308
+ if (path14.endsWith(".md")) return "markdown";
2309
+ if (path14.endsWith("Dockerfile")) return "dockerfile";
2310
+ if (path14.endsWith(".yml") || path14.endsWith(".yaml")) return "yaml";
2299
2311
  return "text";
2300
2312
  }
2301
2313
  function lancedbStore2() {
@@ -3309,22 +3321,22 @@ function generateAgentServer(config) {
3309
3321
  { path: "README.md", contents: readme2(config, server) }
3310
3322
  ].map((f) => ({ ...f, language: lang2(f.path) }));
3311
3323
  try {
3312
- const fs12 = __require("fs");
3313
- const path13 = __require("path");
3314
- const faviconPath = path13.resolve(process.cwd(), "public/favicon2.ico");
3315
- if (fs12.existsSync(faviconPath)) {
3324
+ const fs13 = __require("fs");
3325
+ const path14 = __require("path");
3326
+ const faviconPath = path14.resolve(process.cwd(), "public/favicon2.ico");
3327
+ if (fs13.existsSync(faviconPath)) {
3316
3328
  files.push({
3317
3329
  path: "public/favicon2.ico",
3318
- contents: fs12.readFileSync(faviconPath).toString("base64"),
3330
+ contents: fs13.readFileSync(faviconPath).toString("base64"),
3319
3331
  language: "ico",
3320
3332
  encoding: "base64"
3321
3333
  });
3322
3334
  } else {
3323
- const webFaviconPath = path13.resolve(process.cwd(), "apps/web/public/favicon2.ico");
3324
- if (fs12.existsSync(webFaviconPath)) {
3335
+ const webFaviconPath = path14.resolve(process.cwd(), "apps/web/public/favicon2.ico");
3336
+ if (fs13.existsSync(webFaviconPath)) {
3325
3337
  files.push({
3326
3338
  path: "public/favicon2.ico",
3327
- contents: fs12.readFileSync(webFaviconPath).toString("base64"),
3339
+ contents: fs13.readFileSync(webFaviconPath).toString("base64"),
3328
3340
  language: "ico",
3329
3341
  encoding: "base64"
3330
3342
  });
@@ -4641,8 +4653,8 @@ async function readTextFiles(files) {
4641
4653
  }
4642
4654
 
4643
4655
  // src/commands/media.ts
4644
- import { promises as fs8 } from "fs";
4645
- import path9 from "path";
4656
+ import { promises as fs9 } from "fs";
4657
+ import path10 from "path";
4646
4658
 
4647
4659
  // ../../packages/core/src/media-store.ts
4648
4660
  import { promises as fs7 } from "fs";
@@ -4784,10 +4796,97 @@ function claimMediaAsset(id) {
4784
4796
  });
4785
4797
  }
4786
4798
 
4799
+ // ../../packages/marketplace/src/storage-provider.ts
4800
+ import { promises as fs8 } from "fs";
4801
+ import path9 from "path";
4802
+ var LocalStorageProvider = class {
4803
+ id = "local";
4804
+ name = "Local Storage";
4805
+ async mediaDir() {
4806
+ const dataDir = await requireDataDir();
4807
+ const dir = path9.join(dataDir, "media");
4808
+ await fs8.mkdir(dir, { recursive: true });
4809
+ return dir;
4810
+ }
4811
+ async store(key, data, _mimeType) {
4812
+ const dir = await this.mediaDir();
4813
+ const filePath = path9.join(dir, key);
4814
+ await fs8.mkdir(path9.dirname(filePath), { recursive: true });
4815
+ await fs8.writeFile(filePath, data);
4816
+ return `local://${key}`;
4817
+ }
4818
+ async storeFile(key, sourcePath, _mimeType) {
4819
+ const dir = await this.mediaDir();
4820
+ const filePath = path9.join(dir, key);
4821
+ await fs8.mkdir(path9.dirname(filePath), { recursive: true });
4822
+ await fs8.copyFile(sourcePath, filePath);
4823
+ return `local://${key}`;
4824
+ }
4825
+ async retrieve(uri) {
4826
+ const filePath = await this.resolvePath(uri);
4827
+ if (!filePath) throw new Error(`Unsupported local storage URI: ${uri}`);
4828
+ return fs8.readFile(filePath);
4829
+ }
4830
+ async resolvePath(uri) {
4831
+ if (!uri.startsWith("local://")) return void 0;
4832
+ const key = uri.slice("local://".length);
4833
+ const dir = await this.mediaDir();
4834
+ const filePath = path9.resolve(dir, key);
4835
+ const relative = path9.relative(dir, filePath);
4836
+ if (relative.startsWith("..") || path9.isAbsolute(relative)) {
4837
+ throw new Error("Invalid local storage URI");
4838
+ }
4839
+ return filePath;
4840
+ }
4841
+ async delete(uri) {
4842
+ const key = uri.replace("local://", "");
4843
+ const dir = await this.mediaDir();
4844
+ try {
4845
+ await fs8.unlink(path9.join(dir, key));
4846
+ } catch {
4847
+ }
4848
+ }
4849
+ async stats() {
4850
+ try {
4851
+ const dir = await this.mediaDir();
4852
+ return await computeDirStats(dir);
4853
+ } catch {
4854
+ return { usedBytes: 0, fileCount: 0 };
4855
+ }
4856
+ }
4857
+ };
4858
+ async function computeDirStats(dir) {
4859
+ let usedBytes = 0;
4860
+ let fileCount = 0;
4861
+ async function walk2(d) {
4862
+ let entries;
4863
+ try {
4864
+ entries = await fs8.readdir(d, { withFileTypes: true });
4865
+ } catch {
4866
+ return;
4867
+ }
4868
+ for (const entry of entries) {
4869
+ const full = path9.join(d, entry.name);
4870
+ if (entry.isDirectory()) {
4871
+ await walk2(full);
4872
+ } else {
4873
+ try {
4874
+ const stat = await fs8.stat(full);
4875
+ usedBytes += stat.size;
4876
+ fileCount++;
4877
+ } catch {
4878
+ }
4879
+ }
4880
+ }
4881
+ }
4882
+ await walk2(dir);
4883
+ return { usedBytes, fileCount };
4884
+ }
4885
+ function createStorageProvider(_config) {
4886
+ return new LocalStorageProvider();
4887
+ }
4888
+
4787
4889
  // src/commands/media.ts
4788
- import { isToolInstalled } from "@larkup/marketplace/installer";
4789
- import { loadTool } from "@larkup/marketplace/loader";
4790
- import { createStorageProvider } from "@larkup/marketplace/storage";
4791
4890
  var MIME_TYPES = {
4792
4891
  ".png": "image/png",
4793
4892
  ".jpg": "image/jpeg",
@@ -4829,12 +4928,12 @@ async function processMediaCommand(inputs, options) {
4829
4928
  const assets = await Promise.all(
4830
4929
  mediaFiles.map(async (filePath) => {
4831
4930
  const type = mediaType(filePath);
4832
- const stat = await fs8.stat(filePath);
4833
- const extension = path9.extname(filePath).toLowerCase() || ".bin";
4931
+ const stat = await fs9.stat(filePath);
4932
+ const extension = path10.extname(filePath).toLowerCase() || ".bin";
4834
4933
  const key = `${type}s/${Date.now()}_${Math.random().toString(36).slice(2)}${extension}`;
4835
4934
  const mimeType = MIME_TYPES[extension] || "application/octet-stream";
4836
- const storageUri = storage.storeFile ? await storage.storeFile(key, filePath, mimeType) : await storage.store(key, await fs8.readFile(filePath), mimeType);
4837
- return { type, fileName: path9.basename(filePath), mimeType, storageUri, fileSize: stat.size };
4935
+ const storageUri = storage.storeFile ? await storage.storeFile(key, filePath, mimeType) : await storage.store(key, await fs9.readFile(filePath), mimeType);
4936
+ return { type, fileName: path10.basename(filePath), mimeType, storageUri, fileSize: stat.size };
4838
4937
  })
4839
4938
  );
4840
4939
  const stored = await addMediaAssets(assets);
@@ -4916,7 +5015,7 @@ async function processMediaAsset(asset) {
4916
5015
  if (!tool2.processVideo)
4917
5016
  throw new Error("The installed Video & Audio tool cannot process videos.");
4918
5017
  await update("Extracting video audio", 20);
4919
- const outputDir = await fs8.mkdtemp(path9.join(process.cwd(), ".larkup", "tmp-media-"));
5018
+ const outputDir = await fs9.mkdtemp(path10.join(process.cwd(), ".larkup", "tmp-media-"));
4920
5019
  try {
4921
5020
  const video = await tool2.processVideo(localFile, {
4922
5021
  outputDir,
@@ -4937,7 +5036,7 @@ async function processMediaAsset(asset) {
4937
5036
  dimensions = { width: video.meta.width, height: video.meta.height };
4938
5037
  frameCount = video.frames.length;
4939
5038
  } finally {
4940
- await fs8.rm(outputDir, { recursive: true, force: true }).catch(() => {
5039
+ await fs9.rm(outputDir, { recursive: true, force: true }).catch(() => {
4941
5040
  });
4942
5041
  }
4943
5042
  } else {
@@ -5015,7 +5114,7 @@ async function indexPendingMedia() {
5015
5114
  spinner2.stop(`Indexed ${finalRun.totalChunks} chunks`);
5016
5115
  }
5017
5116
  function mediaType(filePath) {
5018
- const extension = path9.extname(filePath).toLowerCase();
5117
+ const extension = path10.extname(filePath).toLowerCase();
5019
5118
  if ([".png", ".jpg", ".jpeg", ".webp", ".gif"].includes(extension)) return "image";
5020
5119
  if ([".mp4", ".webm", ".mov", ".mkv"].includes(extension)) return "video";
5021
5120
  if ([".mp3", ".m4a", ".wav", ".ogg", ".flac"].includes(extension)) return "audio";
@@ -5102,20 +5201,20 @@ async function indexCommand(inputs, options) {
5102
5201
  }
5103
5202
 
5104
5203
  // src/commands/generate.ts
5105
- import { promises as fs9 } from "fs";
5106
- import path10 from "path";
5204
+ import { promises as fs10 } from "fs";
5205
+ import path11 from "path";
5107
5206
  async function generateCommand(options) {
5108
5207
  await inServerScope(options.server, async () => {
5109
5208
  await requireActive();
5110
5209
  const config = await readConfig();
5111
5210
  if (options.out) {
5112
- const dir = path10.isAbsolute(options.out) ? options.out : path10.join(process.cwd(), options.out);
5211
+ const dir = path11.isAbsolute(options.out) ? options.out : path11.join(process.cwd(), options.out);
5113
5212
  const server2 = generateServer(config);
5114
- await fs9.mkdir(dir, { recursive: true });
5213
+ await fs10.mkdir(dir, { recursive: true });
5115
5214
  for (const f of server2.files) {
5116
- const dest = path10.join(dir, f.path);
5117
- await fs9.mkdir(path10.dirname(dest), { recursive: true });
5118
- await fs9.writeFile(dest, f.contents, "utf8");
5215
+ const dest = path11.join(dir, f.path);
5216
+ await fs10.mkdir(path11.dirname(dest), { recursive: true });
5217
+ await fs10.writeFile(dest, f.contents, "utf8");
5119
5218
  }
5120
5219
  log.success(`Generated ${server2.files.length} files \u2192 ${dir}`);
5121
5220
  } else {
@@ -5420,13 +5519,6 @@ async function settingsCommand(options) {
5420
5519
  }
5421
5520
 
5422
5521
  // src/commands/marketplace.ts
5423
- import { getAllTools, getToolById } from "@larkup/marketplace/registry";
5424
- import {
5425
- getInstalledTool,
5426
- getInstalledTools,
5427
- installTool,
5428
- uninstallTool
5429
- } from "@larkup/marketplace/installer";
5430
5522
  async function marketplaceCommand(action = "list", toolId) {
5431
5523
  if (action === "list") {
5432
5524
  const [tools, installed] = await Promise.all([getAllTools(), getInstalledTools()]);
@@ -5470,8 +5562,8 @@ async function marketplaceCommand(action = "list", toolId) {
5470
5562
  }
5471
5563
 
5472
5564
  // src/commands/deploy.ts
5473
- import { promises as fs10 } from "fs";
5474
- import path11 from "path";
5565
+ import { promises as fs11 } from "fs";
5566
+ import path12 from "path";
5475
5567
  async function deployCommand(target, options) {
5476
5568
  await inServerScope(options.server, async () => {
5477
5569
  await requireActive();
@@ -5507,13 +5599,13 @@ async function deployCommand(target, options) {
5507
5599
  });
5508
5600
  }
5509
5601
  async function copyGenerated(source, output) {
5510
- const destination = path11.resolve(output);
5511
- const relative = path11.relative(source, destination);
5512
- if (relative === "" || !relative.startsWith("..") && !path11.isAbsolute(relative)) {
5602
+ const destination = path12.resolve(output);
5603
+ const relative = path12.relative(source, destination);
5604
+ if (relative === "" || !relative.startsWith("..") && !path12.isAbsolute(relative)) {
5513
5605
  throw new Error("Deployment output must be outside the generated server directory.");
5514
5606
  }
5515
- await fs10.mkdir(destination, { recursive: true });
5516
- await fs10.cp(source, destination, { recursive: true, force: true });
5607
+ await fs11.mkdir(destination, { recursive: true });
5608
+ await fs11.cp(source, destination, { recursive: true, force: true });
5517
5609
  return destination;
5518
5610
  }
5519
5611
 
@@ -5598,8 +5690,8 @@ function openCommandForPlatform(url) {
5598
5690
  }
5599
5691
 
5600
5692
  // src/commands/documents.ts
5601
- import { promises as fs11 } from "fs";
5602
- import path12 from "path";
5693
+ import { promises as fs12 } from "fs";
5694
+ import path13 from "path";
5603
5695
 
5604
5696
  // ../../packages/core/src/corpus-retriever.ts
5605
5697
  function applyFilters(docs, filter) {
@@ -5718,10 +5810,10 @@ async function documentsCommand(action = "list", idOrPath, options) {
5718
5810
  }
5719
5811
  if (action === "export") {
5720
5812
  const format = options.format === "jsonl" ? "jsonl" : "csv";
5721
- const output = path12.resolve(options.out || idOrPath || `corpus.${format}`);
5813
+ const output = path13.resolve(options.out || idOrPath || `corpus.${format}`);
5722
5814
  const content = format === "jsonl" ? await exportCorpusAsJSONL(void 0, Number.MAX_SAFE_INTEGER) : await exportCorpusAsCSV(void 0, Number.MAX_SAFE_INTEGER);
5723
- await fs11.mkdir(path12.dirname(output), { recursive: true });
5724
- await fs11.writeFile(output, content, "utf8");
5815
+ await fs12.mkdir(path13.dirname(output), { recursive: true });
5816
+ await fs12.writeFile(output, content, "utf8");
5725
5817
  log.success(`Exported corpus \u2192 ${output}`);
5726
5818
  return;
5727
5819
  }
@@ -5747,7 +5839,7 @@ ${document.content}`);
5747
5839
  return;
5748
5840
  }
5749
5841
  if (action === "update") {
5750
- const content = options.file ? await fs11.readFile(path12.resolve(options.file), "utf8") : options.text;
5842
+ const content = options.file ? await fs12.readFile(path13.resolve(options.file), "utf8") : options.text;
5751
5843
  const updated = await updateDocument(document.id, {
5752
5844
  title: options.title,
5753
5845
  content,
@@ -0,0 +1,14 @@
1
+ import {
2
+ hasCapability,
3
+ isToolLoaded,
4
+ loadTool,
5
+ unloadTool
6
+ } from "./chunk-ZJLKBVV6.js";
7
+ import "./chunk-UCYL66UI.js";
8
+ import "./chunk-3RG5ZIWI.js";
9
+ export {
10
+ hasCapability,
11
+ isToolLoaded,
12
+ loadTool,
13
+ unloadTool
14
+ };
@@ -0,0 +1,19 @@
1
+ import {
2
+ buildRegistry,
3
+ fetchToolFromHub,
4
+ getAllCategories,
5
+ getAllTools,
6
+ getToolById,
7
+ getToolsWithCapability,
8
+ invalidateRegistryCache
9
+ } from "./chunk-UCYL66UI.js";
10
+ import "./chunk-3RG5ZIWI.js";
11
+ export {
12
+ buildRegistry,
13
+ fetchToolFromHub,
14
+ getAllCategories,
15
+ getAllTools,
16
+ getToolById,
17
+ getToolsWithCapability,
18
+ invalidateRegistryCache
19
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@larkup/cli",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -28,9 +28,9 @@
28
28
  "cli-table3": "^0.6.5",
29
29
  "commander": "^15.0.0",
30
30
  "zod": "^4.4.3",
31
+ "@larkup/vector-stores": "0.1.23",
31
32
  "@larkup/core": "0.2.4",
32
- "@larkup/marketplace": "0.1.7",
33
- "@larkup/vector-stores": "0.1.23"
33
+ "@larkup/marketplace": "0.1.9"
34
34
  },
35
35
  "devDependencies": {
36
36
  "tsup": "^8.0.0",