@veolab/discoverylab 1.4.4 → 1.6.3
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +70 -211
- package/dist/{chunk-HB3YPWF3.js → chunk-5AISGCS4.js} +166 -8
- package/dist/{chunk-CUBQRT5L.js → chunk-HFN6BTVO.js} +110 -1
- package/dist/{chunk-2UUMLAVR.js → chunk-IVX2OSOJ.js} +251 -1
- package/dist/cli.js +158 -27
- package/dist/export/infographic-template.html +254 -0
- package/dist/import-W2JEW254.js +180 -0
- package/dist/index.html +420 -9
- package/dist/index.js +4 -4
- package/dist/infographic-GQAHEOAA.js +183 -0
- package/dist/{server-QFNKZCOJ.js → server-W3JQ5RG7.js} +1 -1
- package/dist/{setup-6JJYKKBS.js → setup-F7MGEFIM.js} +4 -1
- package/dist/{tools-Q7OZO732.js → tools-VYFNRUS4.js} +5 -3
- package/doc/esvp-protocol.md +116 -0
- package/package.json +3 -3
- package/skills/knowledge-brain/SKILL.md +44 -43
|
@@ -2,6 +2,11 @@ import {
|
|
|
2
2
|
createJsonResult,
|
|
3
3
|
createTextResult
|
|
4
4
|
} from "./chunk-XKX6NBHF.js";
|
|
5
|
+
import {
|
|
6
|
+
LOCAL_ESVP_SERVER_URL,
|
|
7
|
+
getESVPHealth,
|
|
8
|
+
listESVPDevices
|
|
9
|
+
} from "./chunk-GRU332L4.js";
|
|
5
10
|
import {
|
|
6
11
|
DATA_DIR,
|
|
7
12
|
DB_PATH
|
|
@@ -144,6 +149,24 @@ function checkMaestro(dep) {
|
|
|
144
149
|
error: "Maestro not found in PATH or common installation directories"
|
|
145
150
|
};
|
|
146
151
|
}
|
|
152
|
+
function buildReplayExecutorStatus(input) {
|
|
153
|
+
return {
|
|
154
|
+
available: input.dependencyReady && input.devices.length > 0,
|
|
155
|
+
dependencyReady: input.dependencyReady,
|
|
156
|
+
deviceCount: input.devices.length,
|
|
157
|
+
devices: input.devices,
|
|
158
|
+
missing: input.missing
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function createLocalReplayMessage(input) {
|
|
162
|
+
if (input.ready && input.recommendedExecutor) {
|
|
163
|
+
return `Local replay is ready. Recommended executor: ${input.recommendedExecutor}.`;
|
|
164
|
+
}
|
|
165
|
+
if (input.androidReady || input.iosReady) {
|
|
166
|
+
return "A mobile runtime is partially available, but the full local replay path still needs one or more dependencies.";
|
|
167
|
+
}
|
|
168
|
+
return "Local replay is not ready yet. Install the missing mobile dependencies and boot at least one iOS Simulator or Android device/emulator.";
|
|
169
|
+
}
|
|
147
170
|
var setupStatusTool = {
|
|
148
171
|
name: "dlab.setup.status",
|
|
149
172
|
description: "Check the status of DiscoveryLab setup and all dependencies.",
|
|
@@ -218,6 +241,91 @@ Install with: ${dep.installHint}`);
|
|
|
218
241
|
}
|
|
219
242
|
}
|
|
220
243
|
};
|
|
244
|
+
var setupReplayStatusTool = {
|
|
245
|
+
name: "dlab.setup.replay.status",
|
|
246
|
+
description: "Check whether this machine can run local ESVP replay for Claude Desktop using iOS Simulator or Android.",
|
|
247
|
+
inputSchema: z.object({}),
|
|
248
|
+
handler: async () => {
|
|
249
|
+
const adbStatus = checkDependency(dependencies[4]);
|
|
250
|
+
const xcodeStatus = platform() === "darwin" ? checkDependency(dependencies[3]) : { installed: false, version: null, error: "Xcode CLI Tools are only available on macOS" };
|
|
251
|
+
const maestroStatus = checkDependency(dependencies[1]);
|
|
252
|
+
const deviceEnvelope = await listESVPDevices("all").catch((error) => ({
|
|
253
|
+
adb: { devices: [], error: error instanceof Error ? error.message : String(error) },
|
|
254
|
+
iosSim: { devices: [], error: error instanceof Error ? error.message : String(error) },
|
|
255
|
+
maestroIos: { devices: [], error: error instanceof Error ? error.message : String(error) }
|
|
256
|
+
}));
|
|
257
|
+
const localEntropyHealth = await getESVPHealth(LOCAL_ESVP_SERVER_URL).catch((error) => ({
|
|
258
|
+
ok: false,
|
|
259
|
+
error: error instanceof Error ? error.message : String(error)
|
|
260
|
+
}));
|
|
261
|
+
const androidDevices = Array.isArray(deviceEnvelope?.adb?.devices) ? deviceEnvelope.adb.devices : [];
|
|
262
|
+
const iosSimDevices = Array.isArray(deviceEnvelope?.iosSim?.devices) ? deviceEnvelope.iosSim.devices : [];
|
|
263
|
+
const maestroIosDevices = Array.isArray(deviceEnvelope?.maestroIos?.devices) ? deviceEnvelope.maestroIos.devices : [];
|
|
264
|
+
const android = buildReplayExecutorStatus({
|
|
265
|
+
devices: androidDevices,
|
|
266
|
+
dependencyReady: adbStatus.installed,
|
|
267
|
+
missing: adbStatus.installed ? [] : ["adb"]
|
|
268
|
+
});
|
|
269
|
+
const iosSimulator = buildReplayExecutorStatus({
|
|
270
|
+
devices: iosSimDevices,
|
|
271
|
+
dependencyReady: xcodeStatus.installed,
|
|
272
|
+
missing: xcodeStatus.installed ? [] : ["xcode"]
|
|
273
|
+
});
|
|
274
|
+
const iosMaestro = buildReplayExecutorStatus({
|
|
275
|
+
devices: maestroIosDevices,
|
|
276
|
+
dependencyReady: maestroStatus.installed && xcodeStatus.installed,
|
|
277
|
+
missing: [
|
|
278
|
+
...maestroStatus.installed ? [] : ["maestro"],
|
|
279
|
+
...xcodeStatus.installed ? [] : ["xcode"]
|
|
280
|
+
]
|
|
281
|
+
});
|
|
282
|
+
const recommendedExecutor = android.available ? "adb" : iosSimulator.available ? "ios-sim" : iosMaestro.available ? "maestro-ios" : null;
|
|
283
|
+
const ready = Boolean(localEntropyHealth?.ok) && Boolean(recommendedExecutor);
|
|
284
|
+
return createJsonResult({
|
|
285
|
+
ready,
|
|
286
|
+
minimumMobileReady: android.available || iosSimulator.available || iosMaestro.available,
|
|
287
|
+
recommendedExecutor,
|
|
288
|
+
message: createLocalReplayMessage({
|
|
289
|
+
ready,
|
|
290
|
+
recommendedExecutor,
|
|
291
|
+
androidReady: android.available,
|
|
292
|
+
iosReady: iosSimulator.available || iosMaestro.available
|
|
293
|
+
}),
|
|
294
|
+
entropyLocal: {
|
|
295
|
+
available: localEntropyHealth?.ok === true,
|
|
296
|
+
kind: "embedded-app-lab-runtime",
|
|
297
|
+
serverUrl: LOCAL_ESVP_SERVER_URL,
|
|
298
|
+
service: typeof localEntropyHealth?.service === "string" ? localEntropyHealth.service : "applab-esvp-local",
|
|
299
|
+
version: typeof localEntropyHealth?.version === "string" ? localEntropyHealth.version : null,
|
|
300
|
+
note: "Entropy local is the in-process AppLab ESVP runtime. It runs on this machine, stores runs under the local data directory, and uses local emulators/devices instead of a remote control-plane.",
|
|
301
|
+
error: localEntropyHealth?.ok === true ? null : typeof localEntropyHealth?.error === "string" ? localEntropyHealth.error : null
|
|
302
|
+
},
|
|
303
|
+
executors: {
|
|
304
|
+
android,
|
|
305
|
+
iosSimulator,
|
|
306
|
+
iosMaestro
|
|
307
|
+
},
|
|
308
|
+
dependencies: {
|
|
309
|
+
adb: {
|
|
310
|
+
installed: adbStatus.installed,
|
|
311
|
+
version: adbStatus.version,
|
|
312
|
+
installHint: adbStatus.installed ? null : dependencies[4].installHint
|
|
313
|
+
},
|
|
314
|
+
xcode: {
|
|
315
|
+
installed: xcodeStatus.installed,
|
|
316
|
+
version: xcodeStatus.version,
|
|
317
|
+
installHint: xcodeStatus.installed ? null : dependencies[3].installHint
|
|
318
|
+
},
|
|
319
|
+
maestro: {
|
|
320
|
+
installed: maestroStatus.installed,
|
|
321
|
+
version: maestroStatus.version,
|
|
322
|
+
installHint: maestroStatus.installed ? null : dependencies[1].installHint
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
dataDirectory: DATA_DIR
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
};
|
|
221
329
|
var setupInitTool = {
|
|
222
330
|
name: "dlab.setup.init",
|
|
223
331
|
description: "Initialize DiscoveryLab data directories and database.",
|
|
@@ -323,11 +431,12 @@ Please run the following commands to install missing dependencies:
|
|
|
323
431
|
);
|
|
324
432
|
}
|
|
325
433
|
};
|
|
326
|
-
var setupTools = [setupStatusTool, setupCheckTool, setupInitTool, setupInstallTool];
|
|
434
|
+
var setupTools = [setupStatusTool, setupCheckTool, setupReplayStatusTool, setupInitTool, setupInstallTool];
|
|
327
435
|
|
|
328
436
|
export {
|
|
329
437
|
setupStatusTool,
|
|
330
438
|
setupCheckTool,
|
|
439
|
+
setupReplayStatusTool,
|
|
331
440
|
setupInitTool,
|
|
332
441
|
setupInstallTool,
|
|
333
442
|
setupTools
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
createNotionPage
|
|
4
4
|
} from "./chunk-34GGYFXX.js";
|
|
5
5
|
import {
|
|
6
|
+
getCachedRender,
|
|
6
7
|
getRenderJob,
|
|
7
8
|
startRender
|
|
8
9
|
} from "./chunk-6GK5K6CS.js";
|
|
@@ -5418,6 +5419,23 @@ app.post("/api/export", async (c) => {
|
|
|
5418
5419
|
if (esvpSnapshot) {
|
|
5419
5420
|
writeExportJson(join6(bundleRoot, "esvp", "snapshot.json"), esvpSnapshot);
|
|
5420
5421
|
}
|
|
5422
|
+
const projectExportsDir = join6(EXPORTS_DIR, projectId);
|
|
5423
|
+
if (existsSync5(projectExportsDir) && statSync3(projectExportsDir).isDirectory()) {
|
|
5424
|
+
const rendersDir = join6(bundleRoot, "renders");
|
|
5425
|
+
mkdirSync4(rendersDir, { recursive: true });
|
|
5426
|
+
const exportFiles = readdirSync4(projectExportsDir);
|
|
5427
|
+
for (const f of exportFiles) {
|
|
5428
|
+
const src = join6(projectExportsDir, f);
|
|
5429
|
+
if (statSync3(src).isFile()) {
|
|
5430
|
+
cpSync(src, join6(rendersDir, f));
|
|
5431
|
+
}
|
|
5432
|
+
}
|
|
5433
|
+
}
|
|
5434
|
+
const templateContentPath = join6(PROJECTS_DIR, projectId, "template-content.json");
|
|
5435
|
+
if (existsSync5(templateContentPath)) {
|
|
5436
|
+
mkdirSync4(join6(bundleRoot, "templates"), { recursive: true });
|
|
5437
|
+
cpSync(templateContentPath, join6(bundleRoot, "templates", "content.json"));
|
|
5438
|
+
}
|
|
5421
5439
|
writeExportText(join6(bundleRoot, "README.txt"), [
|
|
5422
5440
|
`${rawProject.name}`,
|
|
5423
5441
|
`Exported from DiscoveryLab ${APP_VERSION} on ${new Date(timestamp).toISOString()}.`,
|
|
@@ -5913,6 +5931,72 @@ app.post("/api/visualization/screenshot", async (c) => {
|
|
|
5913
5931
|
return c.json({ error: message }, 500);
|
|
5914
5932
|
}
|
|
5915
5933
|
});
|
|
5934
|
+
app.post("/api/export/infographic", async (c) => {
|
|
5935
|
+
try {
|
|
5936
|
+
const body = await c.req.json();
|
|
5937
|
+
const { projectId, open } = body;
|
|
5938
|
+
if (!projectId) return c.json({ error: "projectId required" }, 400);
|
|
5939
|
+
const db = getDatabase();
|
|
5940
|
+
const [project] = await db.select().from(projects).where(eq(projects.id, projectId)).limit(1);
|
|
5941
|
+
if (!project) return c.json({ error: "Project not found" }, 404);
|
|
5942
|
+
const { FRAMES_DIR: fDir, EXPORTS_DIR: eDir, PROJECTS_DIR: pDir } = await import("./db-5ECN3O7F.js");
|
|
5943
|
+
const { collectFrameImages, buildInfographicData, generateInfographicHtml } = await import("./infographic-GQAHEOAA.js");
|
|
5944
|
+
const dbFrames = await db.select().from(frames).where(eq(frames.projectId, projectId)).orderBy(frames.frameNumber).limit(20);
|
|
5945
|
+
let frameFiles;
|
|
5946
|
+
let frameOcr;
|
|
5947
|
+
if (dbFrames.length > 0) {
|
|
5948
|
+
frameFiles = dbFrames.map((f) => f.imagePath);
|
|
5949
|
+
frameOcr = dbFrames;
|
|
5950
|
+
} else {
|
|
5951
|
+
frameFiles = collectFrameImages(join6(fDir, projectId), project.videoPath, pDir, projectId);
|
|
5952
|
+
frameOcr = frameFiles.map(() => ({ ocrText: null }));
|
|
5953
|
+
}
|
|
5954
|
+
if (frameFiles.length === 0) {
|
|
5955
|
+
return c.json({ error: "No frames found. Run analyzer first." }, 400);
|
|
5956
|
+
}
|
|
5957
|
+
const cached = annotationCache.get(projectId);
|
|
5958
|
+
const annotations = cached?.steps?.map((s) => ({ label: s }));
|
|
5959
|
+
const data = buildInfographicData(project, frameFiles, frameOcr, annotations);
|
|
5960
|
+
const slug = (project.marketingTitle || project.name || projectId).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
5961
|
+
const outputPath = join6(eDir, `${slug}-infographic.html`);
|
|
5962
|
+
const result = generateInfographicHtml(data, outputPath);
|
|
5963
|
+
if (!result.success) return c.json({ error: result.error }, 500);
|
|
5964
|
+
if (open) {
|
|
5965
|
+
const { exec: exec2 } = await import("child_process");
|
|
5966
|
+
exec2(`open "${result.outputPath}"`);
|
|
5967
|
+
}
|
|
5968
|
+
return c.json({
|
|
5969
|
+
success: true,
|
|
5970
|
+
path: result.outputPath,
|
|
5971
|
+
downloadUrl: `/api/file?path=${encodeURIComponent(result.outputPath)}&download=true`,
|
|
5972
|
+
size: result.size,
|
|
5973
|
+
frameCount: result.frameCount
|
|
5974
|
+
});
|
|
5975
|
+
} catch (error) {
|
|
5976
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
5977
|
+
return c.json({ error: message }, 500);
|
|
5978
|
+
}
|
|
5979
|
+
});
|
|
5980
|
+
app.post("/api/import", async (c) => {
|
|
5981
|
+
try {
|
|
5982
|
+
const body = await c.req.json();
|
|
5983
|
+
const { filePath } = body;
|
|
5984
|
+
if (!filePath) return c.json({ error: "filePath required" }, 400);
|
|
5985
|
+
const { importApplabBundle } = await import("./import-W2JEW254.js");
|
|
5986
|
+
const { FRAMES_DIR: fDir, PROJECTS_DIR: pDir } = await import("./db-5ECN3O7F.js");
|
|
5987
|
+
const db = getDatabase();
|
|
5988
|
+
const result = await importApplabBundle(filePath, db, { projects, frames }, {
|
|
5989
|
+
dataDir: DATA_DIR,
|
|
5990
|
+
framesDir: fDir,
|
|
5991
|
+
projectsDir: pDir
|
|
5992
|
+
});
|
|
5993
|
+
if (!result.success) return c.json({ error: result.error }, 400);
|
|
5994
|
+
return c.json(result);
|
|
5995
|
+
} catch (error) {
|
|
5996
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
5997
|
+
return c.json({ error: message }, 500);
|
|
5998
|
+
}
|
|
5999
|
+
});
|
|
5916
6000
|
app.get("/api/export/document/:projectId", async (c) => {
|
|
5917
6001
|
try {
|
|
5918
6002
|
const projectId = c.req.param("projectId");
|
|
@@ -6253,6 +6337,158 @@ app.get("/api/testing/status", async (c) => {
|
|
|
6253
6337
|
return c.json({ error: message }, 500);
|
|
6254
6338
|
}
|
|
6255
6339
|
});
|
|
6340
|
+
function getClaudeDesktopConfigPath() {
|
|
6341
|
+
const home = homedir3();
|
|
6342
|
+
return process.platform === "win32" ? join6(process.env.APPDATA || join6(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json") : join6(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
6343
|
+
}
|
|
6344
|
+
function getClaudeDesktopAppCandidates() {
|
|
6345
|
+
const home = homedir3();
|
|
6346
|
+
if (process.platform === "darwin") {
|
|
6347
|
+
return [
|
|
6348
|
+
"/Applications/Claude.app",
|
|
6349
|
+
join6(home, "Applications", "Claude.app")
|
|
6350
|
+
];
|
|
6351
|
+
}
|
|
6352
|
+
if (process.platform === "win32") {
|
|
6353
|
+
return [
|
|
6354
|
+
process.env.LOCALAPPDATA ? join6(process.env.LOCALAPPDATA, "Programs", "Claude", "Claude.exe") : "",
|
|
6355
|
+
process.env.PROGRAMFILES ? join6(process.env.PROGRAMFILES, "Claude", "Claude.exe") : "",
|
|
6356
|
+
process.env["PROGRAMFILES(X86)"] ? join6(process.env["PROGRAMFILES(X86)"], "Claude", "Claude.exe") : "",
|
|
6357
|
+
process.env.APPDATA ? join6(process.env.APPDATA, "Claude", "Claude.exe") : ""
|
|
6358
|
+
].filter(Boolean);
|
|
6359
|
+
}
|
|
6360
|
+
return [];
|
|
6361
|
+
}
|
|
6362
|
+
function findClaudeDesktopApp() {
|
|
6363
|
+
if (process.platform === "darwin") {
|
|
6364
|
+
try {
|
|
6365
|
+
execSync2('open -Ra "Claude"', { stdio: "pipe", timeout: 2e3 });
|
|
6366
|
+
return {
|
|
6367
|
+
detected: true,
|
|
6368
|
+
launchTarget: "Claude",
|
|
6369
|
+
installPath: getClaudeDesktopAppCandidates().find((candidate2) => existsSync5(candidate2)) || null
|
|
6370
|
+
};
|
|
6371
|
+
} catch {
|
|
6372
|
+
const candidate2 = getClaudeDesktopAppCandidates().find((path) => existsSync5(path));
|
|
6373
|
+
return {
|
|
6374
|
+
detected: Boolean(candidate2),
|
|
6375
|
+
launchTarget: candidate2 ? "Claude" : null,
|
|
6376
|
+
installPath: candidate2 || null
|
|
6377
|
+
};
|
|
6378
|
+
}
|
|
6379
|
+
}
|
|
6380
|
+
const candidate = getClaudeDesktopAppCandidates().find((path) => existsSync5(path));
|
|
6381
|
+
return {
|
|
6382
|
+
detected: Boolean(candidate),
|
|
6383
|
+
launchTarget: candidate || null,
|
|
6384
|
+
installPath: candidate || null
|
|
6385
|
+
};
|
|
6386
|
+
}
|
|
6387
|
+
function detectDiscoveryLabClaudeDesktopMcp() {
|
|
6388
|
+
const configPath = getClaudeDesktopConfigPath();
|
|
6389
|
+
if (!existsSync5(configPath)) {
|
|
6390
|
+
return {
|
|
6391
|
+
configured: false,
|
|
6392
|
+
serverName: null,
|
|
6393
|
+
source: "none",
|
|
6394
|
+
configPath
|
|
6395
|
+
};
|
|
6396
|
+
}
|
|
6397
|
+
try {
|
|
6398
|
+
const raw = readFileSync2(configPath, "utf8");
|
|
6399
|
+
const parsed = JSON.parse(raw);
|
|
6400
|
+
const servers = parsed?.mcpServers;
|
|
6401
|
+
if (!servers || typeof servers !== "object") {
|
|
6402
|
+
return {
|
|
6403
|
+
configured: false,
|
|
6404
|
+
serverName: null,
|
|
6405
|
+
source: "none",
|
|
6406
|
+
configPath
|
|
6407
|
+
};
|
|
6408
|
+
}
|
|
6409
|
+
for (const [name, config] of Object.entries(servers)) {
|
|
6410
|
+
const configString = [
|
|
6411
|
+
name,
|
|
6412
|
+
config?.command || "",
|
|
6413
|
+
...Array.isArray(config?.args) ? config.args : [],
|
|
6414
|
+
config?.url || ""
|
|
6415
|
+
].join(" ").toLowerCase();
|
|
6416
|
+
if (name === "discoverylab" || configString.includes("@veolab/discoverylab") || configString.includes("discoverylab") || configString.includes("applab-discovery")) {
|
|
6417
|
+
return {
|
|
6418
|
+
configured: true,
|
|
6419
|
+
serverName: name || "discoverylab",
|
|
6420
|
+
source: "settings",
|
|
6421
|
+
configPath
|
|
6422
|
+
};
|
|
6423
|
+
}
|
|
6424
|
+
}
|
|
6425
|
+
} catch {
|
|
6426
|
+
}
|
|
6427
|
+
return {
|
|
6428
|
+
configured: false,
|
|
6429
|
+
serverName: null,
|
|
6430
|
+
source: "none",
|
|
6431
|
+
configPath
|
|
6432
|
+
};
|
|
6433
|
+
}
|
|
6434
|
+
app.get("/api/integrations/claude-desktop/status", async (c) => {
|
|
6435
|
+
try {
|
|
6436
|
+
const { platform } = await import("os");
|
|
6437
|
+
const app2 = findClaudeDesktopApp();
|
|
6438
|
+
const mcp = detectDiscoveryLabClaudeDesktopMcp();
|
|
6439
|
+
const launcherSupported = platform() === "darwin" || platform() === "win32";
|
|
6440
|
+
const ready = app2.detected && launcherSupported && mcp.configured;
|
|
6441
|
+
let message = "Claude Desktop launcher unavailable on this platform.";
|
|
6442
|
+
if (platform() === "darwin" || platform() === "win32") {
|
|
6443
|
+
if (!app2.detected) {
|
|
6444
|
+
message = "Claude Desktop was not detected on this machine.";
|
|
6445
|
+
} else if (!mcp.configured) {
|
|
6446
|
+
message = "Claude Desktop is installed, but the DiscoveryLab local MCP is not configured yet.";
|
|
6447
|
+
} else {
|
|
6448
|
+
message = "Claude Desktop is ready to open this project with the local DiscoveryLab MCP.";
|
|
6449
|
+
}
|
|
6450
|
+
}
|
|
6451
|
+
return c.json({
|
|
6452
|
+
ready,
|
|
6453
|
+
appDetected: app2.detected,
|
|
6454
|
+
launcherSupported,
|
|
6455
|
+
launchTarget: app2.launchTarget,
|
|
6456
|
+
installPath: app2.installPath,
|
|
6457
|
+
mcpConfigured: mcp.configured,
|
|
6458
|
+
serverName: mcp.serverName,
|
|
6459
|
+
source: mcp.source,
|
|
6460
|
+
configPath: mcp.configPath,
|
|
6461
|
+
installCommand: "npx -y @veolab/discoverylab@latest install --target desktop",
|
|
6462
|
+
message
|
|
6463
|
+
});
|
|
6464
|
+
} catch (error) {
|
|
6465
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
6466
|
+
return c.json({ error: message }, 500);
|
|
6467
|
+
}
|
|
6468
|
+
});
|
|
6469
|
+
app.post("/api/integrations/claude-desktop/launch", async (c) => {
|
|
6470
|
+
try {
|
|
6471
|
+
const { platform } = await import("os");
|
|
6472
|
+
const { exec: exec2 } = await import("child_process");
|
|
6473
|
+
const { promisify } = await import("util");
|
|
6474
|
+
const app2 = findClaudeDesktopApp();
|
|
6475
|
+
if (!app2.detected || !app2.launchTarget) {
|
|
6476
|
+
return c.json({ success: false, error: "Claude Desktop was not detected on this machine." }, 404);
|
|
6477
|
+
}
|
|
6478
|
+
const execAsync = promisify(exec2);
|
|
6479
|
+
if (platform() === "darwin") {
|
|
6480
|
+
await execAsync(`open -a "${app2.launchTarget}"`);
|
|
6481
|
+
} else if (platform() === "win32") {
|
|
6482
|
+
await execAsync(`cmd /c start "" "${app2.launchTarget}"`);
|
|
6483
|
+
} else {
|
|
6484
|
+
return c.json({ success: false, error: "Claude Desktop launcher is not supported on this platform." }, 400);
|
|
6485
|
+
}
|
|
6486
|
+
return c.json({ success: true });
|
|
6487
|
+
} catch (error) {
|
|
6488
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
6489
|
+
return c.json({ success: false, error: message }, 500);
|
|
6490
|
+
}
|
|
6491
|
+
});
|
|
6256
6492
|
app.get("/api/integrations/jira-mcp/status", async (c) => {
|
|
6257
6493
|
try {
|
|
6258
6494
|
const { execSync: execSync3 } = await import("child_process");
|
|
@@ -9876,7 +10112,7 @@ app.get("/api/mobile-chat/providers", async (c) => {
|
|
|
9876
10112
|
});
|
|
9877
10113
|
app.get("/api/setup/status", async (c) => {
|
|
9878
10114
|
try {
|
|
9879
|
-
const { setupStatusTool } = await import("./setup-
|
|
10115
|
+
const { setupStatusTool } = await import("./setup-F7MGEFIM.js");
|
|
9880
10116
|
const result = await setupStatusTool.handler({});
|
|
9881
10117
|
const data = JSON.parse(result.content[0].text);
|
|
9882
10118
|
const idbInstalled = await isIdbInstalled().catch(() => false);
|
|
@@ -11286,6 +11522,20 @@ app.post("/api/templates/render", async (c) => {
|
|
|
11286
11522
|
return c.json({ error: templateState.eligibility.reason || `Templates are limited to videos up to ${TEMPLATE_MAX_DURATION_SECONDS} seconds.` }, 400);
|
|
11287
11523
|
}
|
|
11288
11524
|
const { props } = templateState;
|
|
11525
|
+
const forceRender = body.force === true;
|
|
11526
|
+
if (!forceRender) {
|
|
11527
|
+
const cached = getCachedRender(projectId, templateId);
|
|
11528
|
+
if (cached && existsSync5(cached)) {
|
|
11529
|
+
return c.json({
|
|
11530
|
+
jobId: "cached",
|
|
11531
|
+
status: "completed",
|
|
11532
|
+
outputPath: cached,
|
|
11533
|
+
downloadUrl: `/api/file?path=${encodeURIComponent(cached)}&download=true`,
|
|
11534
|
+
previewUrl: `/api/file?path=${encodeURIComponent(cached)}`,
|
|
11535
|
+
cached: true
|
|
11536
|
+
});
|
|
11537
|
+
}
|
|
11538
|
+
}
|
|
11289
11539
|
const job = await startRender(projectId, templateId, props, (progress) => {
|
|
11290
11540
|
broadcastToClients({
|
|
11291
11541
|
type: "templateRenderProgress",
|
package/dist/cli.js
CHANGED
|
@@ -389,7 +389,7 @@ program.command("serve").alias("server").description("Start the DiscoveryLab web
|
|
|
389
389
|
console.log(chalk.cyan("\n DiscoveryLab"));
|
|
390
390
|
console.log(chalk.gray(" AI-powered app testing & evidence generator\n"));
|
|
391
391
|
try {
|
|
392
|
-
const { startServer } = await import("./server-
|
|
392
|
+
const { startServer } = await import("./server-W3JQ5RG7.js");
|
|
393
393
|
await startServer(port);
|
|
394
394
|
console.log(chalk.green(` Server running at http://localhost:${port}`));
|
|
395
395
|
console.log(chalk.gray(" Press Ctrl+C to stop\n"));
|
|
@@ -404,7 +404,7 @@ program.command("serve").alias("server").description("Start the DiscoveryLab web
|
|
|
404
404
|
program.command("setup").description("Check and configure DiscoveryLab dependencies").action(async () => {
|
|
405
405
|
console.log(chalk.cyan("\n DiscoveryLab Setup\n"));
|
|
406
406
|
try {
|
|
407
|
-
const { setupStatusTool } = await import("./setup-
|
|
407
|
+
const { setupStatusTool } = await import("./setup-F7MGEFIM.js");
|
|
408
408
|
const result = await setupStatusTool.handler({});
|
|
409
409
|
if (result.isError) {
|
|
410
410
|
console.error(chalk.red(" Setup check failed"));
|
|
@@ -454,35 +454,78 @@ program.command("init").description("Initialize DiscoveryLab data directories").
|
|
|
454
454
|
process.exit(1);
|
|
455
455
|
}
|
|
456
456
|
});
|
|
457
|
-
program.command("install").description("Install DiscoveryLab as Claude Code
|
|
458
|
-
const { homedir } = await import("os");
|
|
459
|
-
const { existsSync, readFileSync, writeFileSync } = await import("fs");
|
|
460
|
-
const { join } = await import("path");
|
|
461
|
-
const
|
|
457
|
+
program.command("install").description("Install DiscoveryLab as MCP server for Claude Code and/or Claude Desktop").option("--target <target>", "Installation target: code, desktop, all (default: auto-detect)", "").action(async (opts) => {
|
|
458
|
+
const { homedir, platform } = await import("os");
|
|
459
|
+
const { existsSync, readFileSync, writeFileSync, mkdirSync } = await import("fs");
|
|
460
|
+
const { join, dirname } = await import("path");
|
|
461
|
+
const home = homedir();
|
|
462
|
+
const mcpEntry = {
|
|
463
|
+
command: "npx",
|
|
464
|
+
args: ["-y", "@veolab/discoverylab@latest", "mcp"]
|
|
465
|
+
};
|
|
466
|
+
const targets = {
|
|
467
|
+
code: {
|
|
468
|
+
name: "Claude Code",
|
|
469
|
+
path: join(home, ".claude.json"),
|
|
470
|
+
restart: "Restart Claude Code to activate."
|
|
471
|
+
},
|
|
472
|
+
desktop: {
|
|
473
|
+
name: "Claude Desktop",
|
|
474
|
+
path: platform() === "win32" ? join(process.env.APPDATA || join(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json") : join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json"),
|
|
475
|
+
restart: "Restart Claude Desktop to activate."
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
let selectedTargets = [];
|
|
479
|
+
const target = opts.target?.toLowerCase() || "";
|
|
480
|
+
if (target === "code") {
|
|
481
|
+
selectedTargets = ["code"];
|
|
482
|
+
} else if (target === "desktop") {
|
|
483
|
+
selectedTargets = ["desktop"];
|
|
484
|
+
} else if (target === "all") {
|
|
485
|
+
selectedTargets = ["code", "desktop"];
|
|
486
|
+
} else {
|
|
487
|
+
selectedTargets = ["code"];
|
|
488
|
+
const desktopDir = dirname(targets.desktop.path);
|
|
489
|
+
if (existsSync(desktopDir)) {
|
|
490
|
+
selectedTargets.push("desktop");
|
|
491
|
+
}
|
|
492
|
+
}
|
|
462
493
|
console.log(chalk.cyan("\n Installing DiscoveryLab MCP...\n"));
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
494
|
+
let installed = 0;
|
|
495
|
+
for (const key of selectedTargets) {
|
|
496
|
+
const t = targets[key];
|
|
497
|
+
try {
|
|
498
|
+
const dir = dirname(t.path);
|
|
499
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
500
|
+
let config = {};
|
|
501
|
+
if (existsSync(t.path)) {
|
|
502
|
+
const content = readFileSync(t.path, "utf-8");
|
|
503
|
+
try {
|
|
504
|
+
config = JSON.parse(content);
|
|
505
|
+
} catch {
|
|
506
|
+
config = {};
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
510
|
+
config.mcpServers.discoverylab = mcpEntry;
|
|
511
|
+
writeFileSync(t.path, JSON.stringify(config, null, 2));
|
|
512
|
+
console.log(chalk.green(` \u2713 ${t.name} configured`));
|
|
513
|
+
console.log(chalk.gray(` ${t.path}`));
|
|
514
|
+
installed++;
|
|
515
|
+
} catch (error) {
|
|
516
|
+
console.log(chalk.yellow(` \u2717 ${t.name} skipped: ${error instanceof Error ? error.message : String(error)}`));
|
|
468
517
|
}
|
|
469
|
-
|
|
470
|
-
|
|
518
|
+
}
|
|
519
|
+
console.log();
|
|
520
|
+
if (installed > 0) {
|
|
521
|
+
for (const key of selectedTargets) {
|
|
522
|
+
console.log(chalk.white(` ${targets[key].restart}`));
|
|
471
523
|
}
|
|
472
|
-
config.mcpServers.discoverylab = {
|
|
473
|
-
command: "npx",
|
|
474
|
-
args: ["-y", "@veolab/discoverylab@latest", "mcp"]
|
|
475
|
-
};
|
|
476
|
-
writeFileSync(claudeConfigPath, JSON.stringify(config, null, 2));
|
|
477
|
-
console.log(chalk.green(" \u2713 Added to ~/.claude.json"));
|
|
478
|
-
console.log();
|
|
479
|
-
console.log(chalk.white(" Restart Claude Code to activate."));
|
|
480
524
|
console.log(chalk.gray(" Or run: discoverylab serve"));
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
console.error(chalk.red(` Failed to install: ${error}`));
|
|
484
|
-
process.exit(1);
|
|
525
|
+
} else {
|
|
526
|
+
console.log(chalk.red(" No targets configured."));
|
|
485
527
|
}
|
|
528
|
+
console.log();
|
|
486
529
|
});
|
|
487
530
|
program.command("mcp").description("Run as MCP server (for Claude Code integration)").action(async () => {
|
|
488
531
|
try {
|
|
@@ -502,7 +545,7 @@ program.command("mcp").description("Run as MCP server (for Claude Code integrati
|
|
|
502
545
|
taskHubTools,
|
|
503
546
|
esvpTools,
|
|
504
547
|
knowledgeTools
|
|
505
|
-
} = await import("./tools-
|
|
548
|
+
} = await import("./tools-VYFNRUS4.js");
|
|
506
549
|
mcpServer.registerTools([
|
|
507
550
|
...uiTools,
|
|
508
551
|
...projectTools,
|
|
@@ -540,6 +583,94 @@ program.command("info").description("Show version and configuration info").actio
|
|
|
540
583
|
console.log();
|
|
541
584
|
}
|
|
542
585
|
});
|
|
586
|
+
program.command("export").description("Export project in various formats").argument("<project-id>", "Project ID or slug").option("--format <format>", "Export format (infographic, applab, esvp)", "infographic").option("--output <path>", "Custom output path").option("--open", "Open file after generation").option("--compress", "Force image compression").option("--no-baseline", "Omit baseline info").action(async (projectId, opts) => {
|
|
587
|
+
try {
|
|
588
|
+
if (opts.format === "infographic") {
|
|
589
|
+
console.log(chalk.cyan(`
|
|
590
|
+
Exporting infographic for: ${projectId}
|
|
591
|
+
`));
|
|
592
|
+
const { join: pathJoin } = await import("path");
|
|
593
|
+
const { getDatabase, projects, frames: framesTable, FRAMES_DIR, EXPORTS_DIR, PROJECTS_DIR } = await import("./db-5ECN3O7F.js");
|
|
594
|
+
const { eq } = await import("drizzle-orm");
|
|
595
|
+
const { collectFrameImages, buildInfographicData, generateInfographicHtml } = await import("./infographic-GQAHEOAA.js");
|
|
596
|
+
const db = getDatabase();
|
|
597
|
+
const allProjects = await db.select().from(projects);
|
|
598
|
+
const project = allProjects.find((p) => p.id === projectId || p.id.startsWith(projectId) || p.name.toLowerCase().includes(projectId.toLowerCase()));
|
|
599
|
+
if (!project) {
|
|
600
|
+
console.log(chalk.red(` Project not found: ${projectId}`));
|
|
601
|
+
console.log(chalk.gray(" Available projects:"));
|
|
602
|
+
for (const p of allProjects.slice(0, 10)) {
|
|
603
|
+
console.log(chalk.gray(` ${p.id.slice(0, 12)} - ${p.marketingTitle || p.name}`));
|
|
604
|
+
}
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
console.log(chalk.green(` \u2714 Found project: ${project.marketingTitle || project.name}`));
|
|
608
|
+
const dbFrames = await db.select().from(framesTable).where(eq(framesTable.projectId, project.id)).orderBy(framesTable.frameNumber).limit(20);
|
|
609
|
+
let frameFiles;
|
|
610
|
+
let frameOcr;
|
|
611
|
+
if (dbFrames.length > 0) {
|
|
612
|
+
frameFiles = dbFrames.map((f) => f.imagePath);
|
|
613
|
+
frameOcr = dbFrames;
|
|
614
|
+
} else {
|
|
615
|
+
frameFiles = collectFrameImages(pathJoin(FRAMES_DIR, project.id), project.videoPath, PROJECTS_DIR, project.id);
|
|
616
|
+
frameOcr = frameFiles.map(() => ({ ocrText: null }));
|
|
617
|
+
}
|
|
618
|
+
console.log(chalk.green(` \u2714 ${frameFiles.length} frames found`));
|
|
619
|
+
if (frameFiles.length === 0) {
|
|
620
|
+
console.log(chalk.red(" No frames found. Run analyzer first."));
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
const data = buildInfographicData(project, frameFiles, frameOcr);
|
|
624
|
+
console.log(chalk.green(` \u2714 ${project.aiSummary ? "AI analysis loaded" : "No analysis (basic labels)"}`));
|
|
625
|
+
const slug = (project.marketingTitle || project.name || project.id).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
626
|
+
const outputPath = opts.output ? pathJoin(opts.output, `${slug}-infographic.html`) : pathJoin(EXPORTS_DIR, `${slug}-infographic.html`);
|
|
627
|
+
const result = generateInfographicHtml(data, outputPath);
|
|
628
|
+
if (result.success) {
|
|
629
|
+
const sizeKb = ((result.size || 0) / 1024).toFixed(1);
|
|
630
|
+
console.log(chalk.green(` \u2714 Exported: ${result.outputPath} (${sizeKb}KB, ${result.frameCount} frames)`));
|
|
631
|
+
if (opts.open) {
|
|
632
|
+
const { exec } = await import("child_process");
|
|
633
|
+
exec(`open "${result.outputPath}"`);
|
|
634
|
+
}
|
|
635
|
+
} else {
|
|
636
|
+
console.log(chalk.red(` Export failed: ${result.error}`));
|
|
637
|
+
}
|
|
638
|
+
} else {
|
|
639
|
+
console.log(chalk.yellow(` Format "${opts.format}" - use the web UI for applab/esvp exports.`));
|
|
640
|
+
}
|
|
641
|
+
} catch (error) {
|
|
642
|
+
console.log(chalk.red(` Export failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
program.command("import").description("Import a shared .applab project bundle").argument("<file>", "Path to .applab file").action(async (file) => {
|
|
646
|
+
try {
|
|
647
|
+
const { resolve } = await import("path");
|
|
648
|
+
const filePath = resolve(file);
|
|
649
|
+
console.log(chalk.cyan(`
|
|
650
|
+
Importing: ${filePath}
|
|
651
|
+
`));
|
|
652
|
+
const { getDatabase, projects, frames: framesTable, DATA_DIR, FRAMES_DIR, PROJECTS_DIR } = await import("./db-5ECN3O7F.js");
|
|
653
|
+
const { importApplabBundle } = await import("./import-W2JEW254.js");
|
|
654
|
+
const db = getDatabase();
|
|
655
|
+
const result = await importApplabBundle(filePath, db, { projects, frames: framesTable }, {
|
|
656
|
+
dataDir: DATA_DIR,
|
|
657
|
+
framesDir: FRAMES_DIR,
|
|
658
|
+
projectsDir: PROJECTS_DIR
|
|
659
|
+
});
|
|
660
|
+
if (result.success) {
|
|
661
|
+
console.log(chalk.green(` \u2714 Imported: ${result.projectName}`));
|
|
662
|
+
console.log(chalk.green(` \u2714 ${result.frameCount} frames`));
|
|
663
|
+
console.log(chalk.gray(` ID: ${result.projectId}
|
|
664
|
+
`));
|
|
665
|
+
} else {
|
|
666
|
+
console.log(chalk.red(` Import failed: ${result.error}
|
|
667
|
+
`));
|
|
668
|
+
}
|
|
669
|
+
} catch (error) {
|
|
670
|
+
console.log(chalk.red(` Import failed: ${error instanceof Error ? error.message : String(error)}
|
|
671
|
+
`));
|
|
672
|
+
}
|
|
673
|
+
});
|
|
543
674
|
program.parse();
|
|
544
675
|
if (!process.argv.slice(2).length) {
|
|
545
676
|
program.outputHelp();
|