@larkup/cli 0.2.7 → 0.2.8
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/chunk-KMVYMOQV.js +436 -0
- package/dist/chunk-UCYL66UI.js +231 -0
- package/dist/index.js +160 -68
- package/dist/tool-loader-VBH7DQU3.js +14 -0
- package/dist/tool-registry-ED45DB5F.js +19 -0
- package/package.json +3 -3
|
@@ -0,0 +1,436 @@
|
|
|
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
|
+
const tools = [...manifest.tools];
|
|
64
|
+
const bundledIds = (process.env.LARKUP_BUNDLED_TOOLS ?? "doc-editor,video-audio").split(",").map((id) => id.trim()).filter(Boolean);
|
|
65
|
+
for (const toolId of bundledIds) {
|
|
66
|
+
if (tools.some((tool) => tool.id === toolId)) continue;
|
|
67
|
+
const descriptor = await getToolById(toolId);
|
|
68
|
+
if (!descriptor) continue;
|
|
69
|
+
tools.push({
|
|
70
|
+
id: toolId,
|
|
71
|
+
version: descriptor.version,
|
|
72
|
+
installedAt: "bundled",
|
|
73
|
+
packageName: descriptor.packageName,
|
|
74
|
+
resolvedPath: descriptor.packageName,
|
|
75
|
+
source: "local",
|
|
76
|
+
config: buildDefaultConfig(descriptor)
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return tools;
|
|
80
|
+
}
|
|
81
|
+
async function isToolInstalled(toolId) {
|
|
82
|
+
const tools = await getInstalledTools();
|
|
83
|
+
return tools.some((t) => t.id === toolId);
|
|
84
|
+
}
|
|
85
|
+
async function getInstalledTool(toolId) {
|
|
86
|
+
const tools = await getInstalledTools();
|
|
87
|
+
return tools.find((t) => t.id === toolId);
|
|
88
|
+
}
|
|
89
|
+
async function checkSystemDeps(toolId) {
|
|
90
|
+
const descriptor = await getToolById(toolId);
|
|
91
|
+
if (!descriptor?.systemDeps?.length) return [];
|
|
92
|
+
const target = detectDeploymentTarget();
|
|
93
|
+
const missing = [];
|
|
94
|
+
for (const dep of descriptor.systemDeps) {
|
|
95
|
+
if (target === "docker" && dep === "docker") continue;
|
|
96
|
+
try {
|
|
97
|
+
await execAsync(`which ${dep}`);
|
|
98
|
+
} catch {
|
|
99
|
+
missing.push(dep);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return missing;
|
|
103
|
+
}
|
|
104
|
+
function detectDeploymentTarget() {
|
|
105
|
+
const explicit = process.env.LARKUP_DEPLOYMENT_TARGET;
|
|
106
|
+
if (explicit && ["local", "docker", "serverless", "sandbox"].includes(explicit)) {
|
|
107
|
+
return explicit;
|
|
108
|
+
}
|
|
109
|
+
if (process.env.VERCEL || process.env.AWS_LAMBDA_FUNCTION_NAME) return "serverless";
|
|
110
|
+
if (process.env.DOCKER_CONTAINER || isDockerEnvironment()) return "docker";
|
|
111
|
+
if (process.env.E2B_SANDBOX_ID || process.env.MODAL_TASK_ID) return "sandbox";
|
|
112
|
+
return "local";
|
|
113
|
+
}
|
|
114
|
+
function isDockerEnvironment() {
|
|
115
|
+
try {
|
|
116
|
+
const fs3 = __require("fs");
|
|
117
|
+
return fs3.existsSync("/.dockerenv");
|
|
118
|
+
} catch {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function ensureToolsPackageJson() {
|
|
123
|
+
const toolsDir = getToolsDir();
|
|
124
|
+
const pkgPath = path.join(toolsDir, "package.json");
|
|
125
|
+
try {
|
|
126
|
+
await fs.access(pkgPath);
|
|
127
|
+
} catch {
|
|
128
|
+
await ensureDir();
|
|
129
|
+
await fs.writeFile(
|
|
130
|
+
pkgPath,
|
|
131
|
+
JSON.stringify(
|
|
132
|
+
{
|
|
133
|
+
name: "larkup-tools",
|
|
134
|
+
version: "1.0.0",
|
|
135
|
+
private: true,
|
|
136
|
+
description: "Isolated directory for installed Larkup marketplace tools."
|
|
137
|
+
},
|
|
138
|
+
null,
|
|
139
|
+
2
|
|
140
|
+
),
|
|
141
|
+
"utf8"
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async function execInstall(packageName, version, target, onProgress) {
|
|
146
|
+
return serializePackageMutation(
|
|
147
|
+
() => execInstallUnlocked(packageName, version, target, onProgress)
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
async function execInstallUnlocked(packageName, version, target, onProgress) {
|
|
151
|
+
const toolsDir = getToolsDir();
|
|
152
|
+
switch (target) {
|
|
153
|
+
case "local":
|
|
154
|
+
case "docker": {
|
|
155
|
+
await ensureToolsPackageJson();
|
|
156
|
+
const install = async (specifier) => {
|
|
157
|
+
const installCmd = `npm install ${specifier} --prefix "${toolsDir}" --save --no-audit --no-fund --prefer-offline`;
|
|
158
|
+
onProgress?.(`Running: npm install ${specifier}`);
|
|
159
|
+
const { stderr } = await execAsync(installCmd, {
|
|
160
|
+
cwd: toolsDir,
|
|
161
|
+
// Native tool dependencies can take longer under Docker Desktop's
|
|
162
|
+
// amd64 emulation. Let the request finish instead of reporting a
|
|
163
|
+
// misleading connection error part way through installation.
|
|
164
|
+
timeout: target === "docker" ? 3e5 : 12e4,
|
|
165
|
+
env: {
|
|
166
|
+
...process.env,
|
|
167
|
+
NODE_ENV: "production",
|
|
168
|
+
// The Docker image runs as an unprivileged user. Keep npm's cache
|
|
169
|
+
// beside the isolated install so a named .larkup volume is always
|
|
170
|
+
// writable and installing a Marketplace tool does not fail before
|
|
171
|
+
// it reaches the registry.
|
|
172
|
+
HOME: process.env.HOME && process.env.HOME !== "/nonexistent" ? process.env.HOME : toolsDir,
|
|
173
|
+
npm_config_cache: path.join(toolsDir, ".npm-cache"),
|
|
174
|
+
npm_config_update_notifier: "false"
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
if (stderr && !stderr.includes("npm warn")) {
|
|
178
|
+
console.warn(`[marketplace] Install stderr: ${stderr}`);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
try {
|
|
182
|
+
await install(`${packageName}@${version}`);
|
|
183
|
+
} catch (err) {
|
|
184
|
+
const message = err instanceof Error ? err.message : "Install command failed";
|
|
185
|
+
if (!message.includes("ETARGET") && !message.includes("No matching version found")) {
|
|
186
|
+
throw new Error(`Failed to install ${packageName}: ${message}`);
|
|
187
|
+
}
|
|
188
|
+
onProgress?.(
|
|
189
|
+
`Catalog version ${version} is unavailable; installing the latest published version\u2026`
|
|
190
|
+
);
|
|
191
|
+
try {
|
|
192
|
+
await install(`${packageName}@latest`);
|
|
193
|
+
} catch (latestErr) {
|
|
194
|
+
const latestMessage = latestErr instanceof Error ? latestErr.message : "Install command failed";
|
|
195
|
+
throw new Error(`Failed to install ${packageName}: ${latestMessage}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const resolvedPath = path.join(toolsDir, "node_modules", packageName);
|
|
199
|
+
try {
|
|
200
|
+
await fs.access(resolvedPath);
|
|
201
|
+
} catch {
|
|
202
|
+
throw new Error(
|
|
203
|
+
`Package ${packageName} was installed but could not be found at ${resolvedPath}`
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
const packageJson = JSON.parse(
|
|
207
|
+
await fs.readFile(path.join(resolvedPath, "package.json"), "utf8")
|
|
208
|
+
);
|
|
209
|
+
return { resolvedPath, version: packageJson.version ?? version };
|
|
210
|
+
}
|
|
211
|
+
case "serverless": {
|
|
212
|
+
onProgress?.("Serverless environment detected \u2014 queuing for next deploy");
|
|
213
|
+
const pendingPath = path.join(toolsDir, "pending-tools.json");
|
|
214
|
+
let pending = { tools: [] };
|
|
215
|
+
try {
|
|
216
|
+
const raw = await fs.readFile(pendingPath, "utf8");
|
|
217
|
+
pending = JSON.parse(raw);
|
|
218
|
+
} catch {
|
|
219
|
+
}
|
|
220
|
+
pending.tools.push({ packageName, version });
|
|
221
|
+
await ensureDir();
|
|
222
|
+
await fs.writeFile(pendingPath, JSON.stringify(pending, null, 2), "utf8");
|
|
223
|
+
return { resolvedPath: path.join(toolsDir, "node_modules", packageName), version };
|
|
224
|
+
}
|
|
225
|
+
case "sandbox": {
|
|
226
|
+
onProgress?.("Installing in sandbox environment");
|
|
227
|
+
await ensureToolsPackageJson();
|
|
228
|
+
const installCmd = `npm install ${packageName}@${version} --prefix "${toolsDir}" --save --no-audit --no-fund`;
|
|
229
|
+
await execAsync(installCmd, { cwd: toolsDir, timeout: 12e4 });
|
|
230
|
+
return { resolvedPath: path.join(toolsDir, "node_modules", packageName), version };
|
|
231
|
+
}
|
|
232
|
+
default:
|
|
233
|
+
throw new Error(`Unsupported deployment target: ${target}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
async function resolveManifest(toolId) {
|
|
237
|
+
const descriptor = await getToolById(toolId);
|
|
238
|
+
if (descriptor) return descriptor;
|
|
239
|
+
throw new Error(`Unknown tool: ${toolId}. Not found in local registry or Hub.`);
|
|
240
|
+
}
|
|
241
|
+
async function isWorkspaceTool(packageName) {
|
|
242
|
+
try {
|
|
243
|
+
const { stdout } = await execAsync(`pnpm ls -r --depth -1 --json 2>/dev/null || true`, {
|
|
244
|
+
cwd: process.cwd(),
|
|
245
|
+
timeout: 1e4
|
|
246
|
+
});
|
|
247
|
+
const data = JSON.parse(stdout || "[]");
|
|
248
|
+
return Array.isArray(data) && data.some((pkg) => pkg.name === packageName);
|
|
249
|
+
} catch {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
async function installTool(toolId, onProgress) {
|
|
254
|
+
const report = (stage, percent, message) => {
|
|
255
|
+
onProgress?.({ toolId, stage, percent, message });
|
|
256
|
+
};
|
|
257
|
+
try {
|
|
258
|
+
report("checking-deps", 5, "Resolving tool manifest\u2026");
|
|
259
|
+
const descriptor = await resolveManifest(toolId);
|
|
260
|
+
if (descriptor.comingSoon) {
|
|
261
|
+
throw new Error(`${descriptor.name} is coming soon.`);
|
|
262
|
+
}
|
|
263
|
+
report("checking-deps", 15, "Checking system dependencies\u2026");
|
|
264
|
+
const missing = await checkSystemDeps(toolId);
|
|
265
|
+
if (missing.length > 0) {
|
|
266
|
+
const msg = `Missing system dependencies: ${missing.join(", ")}. Please install them first.`;
|
|
267
|
+
report("failed", 0, msg);
|
|
268
|
+
throw new Error(msg);
|
|
269
|
+
}
|
|
270
|
+
const isWorkspace = await isWorkspaceTool(descriptor.packageName);
|
|
271
|
+
const target = detectDeploymentTarget();
|
|
272
|
+
let resolvedPath;
|
|
273
|
+
let installedVersion = descriptor.version;
|
|
274
|
+
let source;
|
|
275
|
+
if (isWorkspace) {
|
|
276
|
+
report("downloading", 30, "Resolving workspace package\u2026");
|
|
277
|
+
try {
|
|
278
|
+
const modulePath = __require.resolve(descriptor.packageName, {
|
|
279
|
+
paths: [process.cwd()]
|
|
280
|
+
});
|
|
281
|
+
resolvedPath = path.dirname(modulePath);
|
|
282
|
+
} catch {
|
|
283
|
+
resolvedPath = path.join(process.cwd(), "node_modules", descriptor.packageName);
|
|
284
|
+
}
|
|
285
|
+
source = "local";
|
|
286
|
+
report("installing", 60, "Workspace package resolved.");
|
|
287
|
+
} else {
|
|
288
|
+
report("downloading", 30, `Downloading ${descriptor.packageName}@${descriptor.version}\u2026`);
|
|
289
|
+
const installed = await execInstall(
|
|
290
|
+
descriptor.packageName,
|
|
291
|
+
descriptor.version,
|
|
292
|
+
target,
|
|
293
|
+
(msg) => report("downloading", 50, msg)
|
|
294
|
+
);
|
|
295
|
+
resolvedPath = installed.resolvedPath;
|
|
296
|
+
installedVersion = installed.version;
|
|
297
|
+
source = target === "sandbox" ? "sandbox" : "registry";
|
|
298
|
+
report("installing", 70, "Package installed.");
|
|
299
|
+
}
|
|
300
|
+
report("installing", 80, "Registering tool\u2026");
|
|
301
|
+
await ensureDir();
|
|
302
|
+
const manifest = await readManifest();
|
|
303
|
+
const existing = manifest.tools.findIndex((t) => t.id === toolId);
|
|
304
|
+
const entry = {
|
|
305
|
+
id: toolId,
|
|
306
|
+
version: installedVersion,
|
|
307
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
308
|
+
packageName: descriptor.packageName,
|
|
309
|
+
resolvedPath,
|
|
310
|
+
source,
|
|
311
|
+
config: buildDefaultConfig(descriptor)
|
|
312
|
+
};
|
|
313
|
+
if (existing >= 0) {
|
|
314
|
+
manifest.tools[existing] = entry;
|
|
315
|
+
} else {
|
|
316
|
+
manifest.tools.push(entry);
|
|
317
|
+
}
|
|
318
|
+
manifest.downloadCounts[toolId] = (manifest.downloadCounts[toolId] ?? 0) + 1;
|
|
319
|
+
await writeManifest(manifest);
|
|
320
|
+
notifyHubInstall(toolId).catch(() => {
|
|
321
|
+
});
|
|
322
|
+
invalidateRegistryCache();
|
|
323
|
+
report("completed", 100, "Installed successfully.");
|
|
324
|
+
} catch (err) {
|
|
325
|
+
const message = err instanceof Error ? err.message : "Installation failed.";
|
|
326
|
+
report("failed", 0, message);
|
|
327
|
+
throw err;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
async function uninstallTool(toolId) {
|
|
331
|
+
const manifest = await readManifest();
|
|
332
|
+
const tool = manifest.tools.find((t) => t.id === toolId);
|
|
333
|
+
manifest.tools = manifest.tools.filter((t) => t.id !== toolId);
|
|
334
|
+
await writeManifest(manifest);
|
|
335
|
+
if (tool?.source === "registry" && tool.packageName) {
|
|
336
|
+
try {
|
|
337
|
+
const toolsDir = getToolsDir();
|
|
338
|
+
await execAsync(`npm uninstall ${tool.packageName} --prefix "${toolsDir}"`, {
|
|
339
|
+
cwd: toolsDir,
|
|
340
|
+
timeout: 3e4
|
|
341
|
+
});
|
|
342
|
+
} catch {
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
invalidateRegistryCache();
|
|
346
|
+
const { unloadTool: unloadTool2 } = await import("./tool-loader-VBH7DQU3.js");
|
|
347
|
+
unloadTool2(toolId);
|
|
348
|
+
}
|
|
349
|
+
async function notifyHubInstall(toolId) {
|
|
350
|
+
const hubUrl = process.env.LARKUP_HUB_URL ?? "https://hub.larkup.de";
|
|
351
|
+
try {
|
|
352
|
+
const controller = new AbortController();
|
|
353
|
+
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
354
|
+
await fetch(`${hubUrl}/v1/tools/${toolId}/installed`, {
|
|
355
|
+
method: "POST",
|
|
356
|
+
signal: controller.signal,
|
|
357
|
+
headers: { "Content-Type": "application/json" }
|
|
358
|
+
});
|
|
359
|
+
clearTimeout(timeout);
|
|
360
|
+
} catch {
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function buildDefaultConfig(descriptor) {
|
|
364
|
+
const config = {};
|
|
365
|
+
for (const field of descriptor.configSchema ?? []) {
|
|
366
|
+
if (field.defaultValue !== void 0) {
|
|
367
|
+
config[field.key] = field.defaultValue;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return config;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// ../../packages/marketplace/src/tool-loader.ts
|
|
374
|
+
var moduleCache = /* @__PURE__ */ new Map();
|
|
375
|
+
async function loadTool(toolId) {
|
|
376
|
+
if (moduleCache.has(toolId)) return moduleCache.get(toolId);
|
|
377
|
+
const installed = await getInstalledTool(toolId);
|
|
378
|
+
if (!installed) return null;
|
|
379
|
+
try {
|
|
380
|
+
const importPath = await resolveImportPath(installed);
|
|
381
|
+
const mod = await import(
|
|
382
|
+
/* webpackIgnore: true */
|
|
383
|
+
importPath
|
|
384
|
+
);
|
|
385
|
+
moduleCache.set(toolId, mod);
|
|
386
|
+
return mod;
|
|
387
|
+
} catch (err) {
|
|
388
|
+
console.error(`[marketplace] Failed to load tool "${toolId}":`, err);
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
async function resolveImportPath(installed) {
|
|
393
|
+
switch (installed.source) {
|
|
394
|
+
case "local":
|
|
395
|
+
return installed.packageName;
|
|
396
|
+
case "registry":
|
|
397
|
+
case "sandbox":
|
|
398
|
+
return resolvePackageEntry(installed.resolvedPath);
|
|
399
|
+
default:
|
|
400
|
+
return installed.packageName;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
async function resolvePackageEntry(packageDir) {
|
|
404
|
+
const manifestPath = path2.join(packageDir, "package.json");
|
|
405
|
+
const manifest = JSON.parse(await fs2.readFile(manifestPath, "utf8"));
|
|
406
|
+
const rootExport = typeof manifest.exports === "object" ? manifest.exports["."] : manifest.exports;
|
|
407
|
+
const entry = (typeof rootExport === "object" ? rootExport.import ?? rootExport.default : rootExport) ?? manifest.main ?? "index.js";
|
|
408
|
+
return pathToFileURL(path2.resolve(packageDir, entry)).href;
|
|
409
|
+
}
|
|
410
|
+
function isToolLoaded(toolId) {
|
|
411
|
+
return moduleCache.has(toolId);
|
|
412
|
+
}
|
|
413
|
+
function unloadTool(toolId) {
|
|
414
|
+
moduleCache.delete(toolId);
|
|
415
|
+
}
|
|
416
|
+
async function hasCapability(capability) {
|
|
417
|
+
const { getToolsWithCapability } = await import("./tool-registry-ED45DB5F.js");
|
|
418
|
+
const tools = await getToolsWithCapability(capability);
|
|
419
|
+
for (const t of tools) {
|
|
420
|
+
const installed = await getInstalledTool(t.id);
|
|
421
|
+
if (installed) return true;
|
|
422
|
+
}
|
|
423
|
+
return false;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
export {
|
|
427
|
+
loadTool,
|
|
428
|
+
isToolLoaded,
|
|
429
|
+
unloadTool,
|
|
430
|
+
hasCapability,
|
|
431
|
+
getInstalledTools,
|
|
432
|
+
isToolInstalled,
|
|
433
|
+
getInstalledTool,
|
|
434
|
+
installTool,
|
|
435
|
+
uninstallTool
|
|
436
|
+
};
|
|
@@ -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
|
+
};
|
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-KMVYMOQV.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.
|
|
37
|
+
version: "0.2.8",
|
|
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(
|
|
943
|
-
if (
|
|
944
|
-
if (
|
|
945
|
-
if (
|
|
946
|
-
if (
|
|
947
|
-
if (
|
|
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
|
|
2265
|
-
const
|
|
2266
|
-
const faviconPath =
|
|
2267
|
-
if (
|
|
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:
|
|
2282
|
+
contents: fs13.readFileSync(faviconPath).toString("base64"),
|
|
2271
2283
|
language: "ico",
|
|
2272
2284
|
encoding: "base64"
|
|
2273
2285
|
});
|
|
2274
2286
|
} else {
|
|
2275
|
-
const webFaviconPath =
|
|
2276
|
-
if (
|
|
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:
|
|
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(
|
|
2294
|
-
if (
|
|
2295
|
-
if (
|
|
2296
|
-
if (
|
|
2297
|
-
if (
|
|
2298
|
-
if (
|
|
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
|
|
3313
|
-
const
|
|
3314
|
-
const faviconPath =
|
|
3315
|
-
if (
|
|
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:
|
|
3330
|
+
contents: fs13.readFileSync(faviconPath).toString("base64"),
|
|
3319
3331
|
language: "ico",
|
|
3320
3332
|
encoding: "base64"
|
|
3321
3333
|
});
|
|
3322
3334
|
} else {
|
|
3323
|
-
const webFaviconPath =
|
|
3324
|
-
if (
|
|
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:
|
|
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
|
|
4645
|
-
import
|
|
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
|
|
4833
|
-
const extension =
|
|
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
|
|
4837
|
-
return { type, fileName:
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
5106
|
-
import
|
|
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 =
|
|
5211
|
+
const dir = path11.isAbsolute(options.out) ? options.out : path11.join(process.cwd(), options.out);
|
|
5113
5212
|
const server2 = generateServer(config);
|
|
5114
|
-
await
|
|
5213
|
+
await fs10.mkdir(dir, { recursive: true });
|
|
5115
5214
|
for (const f of server2.files) {
|
|
5116
|
-
const dest =
|
|
5117
|
-
await
|
|
5118
|
-
await
|
|
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
|
|
5474
|
-
import
|
|
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 =
|
|
5511
|
-
const relative =
|
|
5512
|
-
if (relative === "" || !relative.startsWith("..") && !
|
|
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
|
|
5516
|
-
await
|
|
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
|
|
5602
|
-
import
|
|
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 =
|
|
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
|
|
5724
|
-
await
|
|
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
|
|
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,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.
|
|
3
|
+
"version": "0.2.8",
|
|
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.
|
|
33
|
-
"@larkup/vector-stores": "0.1.23"
|
|
33
|
+
"@larkup/marketplace": "0.1.8"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"tsup": "^8.0.0",
|