@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
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import "./chunk-R5U7XKVJ.js";
|
|
2
|
+
|
|
3
|
+
// src/core/export/infographic.ts
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
5
|
+
import { join, dirname, extname } from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
function findTemplate() {
|
|
8
|
+
const __dir = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const possiblePaths = [
|
|
10
|
+
join(__dir, "infographic-template.html"),
|
|
11
|
+
join(__dir, "..", "export", "infographic-template.html"),
|
|
12
|
+
join(process.cwd(), "dist", "export", "infographic-template.html"),
|
|
13
|
+
join(process.cwd(), "src", "core", "export", "infographic-template.html")
|
|
14
|
+
];
|
|
15
|
+
for (const p of possiblePaths) {
|
|
16
|
+
if (existsSync(p)) return p;
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
function imageToBase64(imagePath) {
|
|
21
|
+
if (!existsSync(imagePath)) return "";
|
|
22
|
+
const buffer = readFileSync(imagePath);
|
|
23
|
+
const ext = extname(imagePath).toLowerCase();
|
|
24
|
+
const mimeTypes = {
|
|
25
|
+
".png": "image/png",
|
|
26
|
+
".jpg": "image/jpeg",
|
|
27
|
+
".jpeg": "image/jpeg",
|
|
28
|
+
".webp": "image/webp",
|
|
29
|
+
".gif": "image/gif"
|
|
30
|
+
};
|
|
31
|
+
const mime = mimeTypes[ext] || "image/png";
|
|
32
|
+
return `data:${mime};base64,${buffer.toString("base64")}`;
|
|
33
|
+
}
|
|
34
|
+
function stripMarkdown(text) {
|
|
35
|
+
return text.replace(/\*\*(.+?)\*\*/g, "$1").replace(/\*(.+?)\*/g, "$1").replace(/__(.+?)__/g, "$1").replace(/_(.+?)_/g, "$1").replace(/`(.+?)`/g, "$1").replace(/^#{1,3}\s+/gm, "").replace(/^-\s+/gm, "").replace(/^\d+\.\s+/gm, "").replace(/\[(.+?)\]\(.*?\)/g, "$1").trim();
|
|
36
|
+
}
|
|
37
|
+
function collectFrameImages(framesDir, videoPath, projectsDir, projectId) {
|
|
38
|
+
const imageExts = /\.(png|jpg|jpeg|webp|gif)$/i;
|
|
39
|
+
const dirs = [
|
|
40
|
+
framesDir,
|
|
41
|
+
...videoPath ? [join(videoPath, "screenshots"), videoPath] : [],
|
|
42
|
+
...projectsDir && projectId ? [
|
|
43
|
+
join(projectsDir, "maestro-recordings", projectId, "screenshots"),
|
|
44
|
+
join(projectsDir, "web-recordings", projectId, "screenshots")
|
|
45
|
+
] : []
|
|
46
|
+
];
|
|
47
|
+
for (const dir of dirs) {
|
|
48
|
+
if (!existsSync(dir) || !statSync(dir).isDirectory()) continue;
|
|
49
|
+
const files = readdirSync(dir).filter((f) => imageExts.test(f)).sort().map((f) => join(dir, f));
|
|
50
|
+
if (files.length > 0) return files;
|
|
51
|
+
}
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
function buildInfographicData(project, frameFiles, frameOcr, annotations) {
|
|
55
|
+
let flowSteps = [];
|
|
56
|
+
let uiElements = [];
|
|
57
|
+
if (project.aiSummary) {
|
|
58
|
+
const flowMatch = project.aiSummary.match(/## (?:User Flow|Likely User Flow)\n([\s\S]*?)(?=\n##|\n$|$)/);
|
|
59
|
+
if (flowMatch) {
|
|
60
|
+
flowSteps = (flowMatch[1].match(/^\d+\.\s+(.+)$/gm) || []).map((s) => s.replace(/^\d+\.\s+/, ""));
|
|
61
|
+
}
|
|
62
|
+
const uiMatch = project.aiSummary.match(/## (?:UI Elements Found|Key UI Elements)\n([\s\S]*?)(?=\n##|\n$|$)/);
|
|
63
|
+
if (uiMatch) {
|
|
64
|
+
uiElements = (uiMatch[1].match(/^-\s+(.+)$/gm) || []).map((s) => s.replace(/^-\s+/, ""));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const elementsPerFrame = Math.max(1, Math.ceil(uiElements.length / Math.max(frameFiles.length, 1)));
|
|
68
|
+
const hotspotColors = ["#818CF8", "#34D399", "#F59E0B", "#EC4899", "#06B6D4", "#8B5CF6"];
|
|
69
|
+
const hotspotPositions = [
|
|
70
|
+
{ x: 50, y: 8 },
|
|
71
|
+
// top center (nav bar)
|
|
72
|
+
{ x: 15, y: 30 },
|
|
73
|
+
// left middle
|
|
74
|
+
{ x: 85, y: 30 },
|
|
75
|
+
// right middle
|
|
76
|
+
{ x: 50, y: 50 },
|
|
77
|
+
// center
|
|
78
|
+
{ x: 50, y: 85 },
|
|
79
|
+
// bottom center (tab bar)
|
|
80
|
+
{ x: 15, y: 70 }
|
|
81
|
+
// bottom left
|
|
82
|
+
];
|
|
83
|
+
const frames = frameFiles.map((filePath, i) => {
|
|
84
|
+
const rawStepName = annotations?.[i]?.label || flowSteps[i] || `Step ${i + 1}`;
|
|
85
|
+
const stepName = stripMarkdown(rawStepName);
|
|
86
|
+
const ocr = frameOcr[i]?.ocrText || "";
|
|
87
|
+
const rawDesc = flowSteps[i] || ocr.slice(0, 100) || `Screen ${i + 1}`;
|
|
88
|
+
const description = stripMarkdown(rawDesc);
|
|
89
|
+
const hotspots = [];
|
|
90
|
+
const startIdx = i * elementsPerFrame;
|
|
91
|
+
const frameElements = uiElements.slice(startIdx, startIdx + elementsPerFrame).slice(0, 4);
|
|
92
|
+
frameElements.forEach((el, j) => {
|
|
93
|
+
const cleanEl = stripMarkdown(el);
|
|
94
|
+
const pos = hotspotPositions[j % hotspotPositions.length];
|
|
95
|
+
hotspots.push({
|
|
96
|
+
id: String.fromCharCode(65 + j),
|
|
97
|
+
x_percent: pos.x,
|
|
98
|
+
y_percent: pos.y,
|
|
99
|
+
label: cleanEl.slice(0, 20),
|
|
100
|
+
title: cleanEl.slice(0, 40),
|
|
101
|
+
description: cleanEl,
|
|
102
|
+
color: hotspotColors[j % hotspotColors.length]
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
if (hotspots.length === 0 && ocr) {
|
|
106
|
+
const ocrWords = ocr.split(/\s+/).filter((w) => w.length > 3).slice(0, 3);
|
|
107
|
+
ocrWords.forEach((w, j) => {
|
|
108
|
+
const pos = hotspotPositions[j % hotspotPositions.length];
|
|
109
|
+
hotspots.push({
|
|
110
|
+
id: String.fromCharCode(65 + j),
|
|
111
|
+
x_percent: pos.x,
|
|
112
|
+
y_percent: pos.y,
|
|
113
|
+
label: w.slice(0, 15),
|
|
114
|
+
title: w,
|
|
115
|
+
description: `Text found: "${w}"`,
|
|
116
|
+
color: hotspotColors[j % hotspotColors.length]
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
id: `frame-${i}`,
|
|
122
|
+
step_name: stepName,
|
|
123
|
+
description,
|
|
124
|
+
base64: imageToBase64(filePath),
|
|
125
|
+
baseline_status: "not_validated",
|
|
126
|
+
hotspots
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
return {
|
|
130
|
+
name: project.marketingTitle || project.name,
|
|
131
|
+
platform: project.platform || "unknown",
|
|
132
|
+
recorded_at: project.createdAt instanceof Date ? project.createdAt.toISOString() : typeof project.createdAt === "string" ? project.createdAt : (/* @__PURE__ */ new Date()).toISOString(),
|
|
133
|
+
frames
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function generateInfographicHtmlString(data) {
|
|
137
|
+
const templatePath = findTemplate();
|
|
138
|
+
if (!templatePath) return null;
|
|
139
|
+
let html = readFileSync(templatePath, "utf-8");
|
|
140
|
+
html = html.replace("__TITLE__", data.name);
|
|
141
|
+
const dataJson = JSON.stringify(data);
|
|
142
|
+
html = html.replace(
|
|
143
|
+
"window.FLOW_DATA || { name: 'Flow', frames: [] }",
|
|
144
|
+
`window.FLOW_DATA || ${dataJson}`
|
|
145
|
+
);
|
|
146
|
+
return html;
|
|
147
|
+
}
|
|
148
|
+
function generateInfographicHtml(data, outputPath) {
|
|
149
|
+
try {
|
|
150
|
+
const templatePath = findTemplate();
|
|
151
|
+
if (!templatePath) {
|
|
152
|
+
return { success: false, error: "Infographic template not found" };
|
|
153
|
+
}
|
|
154
|
+
let html = readFileSync(templatePath, "utf-8");
|
|
155
|
+
html = html.replace("__TITLE__", data.name);
|
|
156
|
+
const dataJson = JSON.stringify(data);
|
|
157
|
+
html = html.replace(
|
|
158
|
+
"window.FLOW_DATA || { name: 'Flow', frames: [] }",
|
|
159
|
+
`window.FLOW_DATA || ${dataJson}`
|
|
160
|
+
);
|
|
161
|
+
const outDir = dirname(outputPath);
|
|
162
|
+
if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
|
|
163
|
+
writeFileSync(outputPath, html, "utf-8");
|
|
164
|
+
const stat = statSync(outputPath);
|
|
165
|
+
return {
|
|
166
|
+
success: true,
|
|
167
|
+
outputPath,
|
|
168
|
+
size: stat.size,
|
|
169
|
+
frameCount: data.frames.length
|
|
170
|
+
};
|
|
171
|
+
} catch (error) {
|
|
172
|
+
return {
|
|
173
|
+
success: false,
|
|
174
|
+
error: error instanceof Error ? error.message : String(error)
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
export {
|
|
179
|
+
buildInfographicData,
|
|
180
|
+
collectFrameImages,
|
|
181
|
+
generateInfographicHtml,
|
|
182
|
+
generateInfographicHtmlString
|
|
183
|
+
};
|
|
@@ -2,10 +2,12 @@ import {
|
|
|
2
2
|
setupCheckTool,
|
|
3
3
|
setupInitTool,
|
|
4
4
|
setupInstallTool,
|
|
5
|
+
setupReplayStatusTool,
|
|
5
6
|
setupStatusTool,
|
|
6
7
|
setupTools
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-HFN6BTVO.js";
|
|
8
9
|
import "./chunk-XKX6NBHF.js";
|
|
10
|
+
import "./chunk-GRU332L4.js";
|
|
9
11
|
import "./chunk-6EGBXRDK.js";
|
|
10
12
|
import "./chunk-YYOK2RF7.js";
|
|
11
13
|
import "./chunk-R5U7XKVJ.js";
|
|
@@ -13,6 +15,7 @@ export {
|
|
|
13
15
|
setupCheckTool,
|
|
14
16
|
setupInitTool,
|
|
15
17
|
setupInstallTool,
|
|
18
|
+
setupReplayStatusTool,
|
|
16
19
|
setupStatusTool,
|
|
17
20
|
setupTools
|
|
18
21
|
};
|
|
@@ -107,16 +107,17 @@ import {
|
|
|
107
107
|
uiStatusTool,
|
|
108
108
|
uiTools,
|
|
109
109
|
videoInfoTool
|
|
110
|
-
} from "./chunk-
|
|
110
|
+
} from "./chunk-5AISGCS4.js";
|
|
111
|
+
import "./chunk-34GGYFXX.js";
|
|
111
112
|
import "./chunk-PMCXEA7J.js";
|
|
112
113
|
import {
|
|
113
114
|
setupCheckTool,
|
|
114
115
|
setupInitTool,
|
|
116
|
+
setupReplayStatusTool,
|
|
115
117
|
setupStatusTool,
|
|
116
118
|
setupTools
|
|
117
|
-
} from "./chunk-
|
|
119
|
+
} from "./chunk-HFN6BTVO.js";
|
|
118
120
|
import "./chunk-XKX6NBHF.js";
|
|
119
|
-
import "./chunk-34GGYFXX.js";
|
|
120
121
|
import "./chunk-7R5YNOXE.js";
|
|
121
122
|
import "./chunk-3ERJNXYM.js";
|
|
122
123
|
import "./chunk-LB3RNE3O.js";
|
|
@@ -217,6 +218,7 @@ export {
|
|
|
217
218
|
projectTools,
|
|
218
219
|
setupCheckTool,
|
|
219
220
|
setupInitTool,
|
|
221
|
+
setupReplayStatusTool,
|
|
220
222
|
setupStatusTool,
|
|
221
223
|
setupTools,
|
|
222
224
|
startRecordingTool,
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# ESVP Protocol Integration
|
|
2
|
+
|
|
3
|
+
DiscoveryLab includes a built-in client for the open [ESVP protocol](https://esvp.dev) by Entropy Lab — enabling reproducible mobile sessions, automated replay, and network-aware validation.
|
|
4
|
+
|
|
5
|
+
## Configuration
|
|
6
|
+
|
|
7
|
+
| Mode | How |
|
|
8
|
+
|------|-----|
|
|
9
|
+
| **Remote** | Set `ESVP_BASE_URL=http://your-esvp-host:8787` |
|
|
10
|
+
| **Local** | Leave `ESVP_BASE_URL` unset — DiscoveryLab boots an embedded local runtime |
|
|
11
|
+
| **Dev (custom module)** | Set `DISCOVERYLAB_ESVP_LOCAL_MODULE=/path/to/esvp-server-reference/server.js` |
|
|
12
|
+
|
|
13
|
+
## MCP Tools
|
|
14
|
+
|
|
15
|
+
| Tool | Description |
|
|
16
|
+
|------|-------------|
|
|
17
|
+
| `dlab.esvp.status` | Check control-plane health |
|
|
18
|
+
| `dlab.esvp.devices` | List available devices |
|
|
19
|
+
| `dlab.esvp.sessions.list` | List sessions |
|
|
20
|
+
| `dlab.esvp.session.create` | Create session (ios-sim, maestro-ios, adb) |
|
|
21
|
+
| `dlab.esvp.session.get` | Get session details |
|
|
22
|
+
| `dlab.esvp.session.inspect` | Inspect session state |
|
|
23
|
+
| `dlab.esvp.session.transcript` | Get session transcript |
|
|
24
|
+
| `dlab.esvp.session.artifacts.list` | List artifacts |
|
|
25
|
+
| `dlab.esvp.session.artifact.get` | Get specific artifact |
|
|
26
|
+
| `dlab.esvp.session.actions` | Run actions on session |
|
|
27
|
+
| `dlab.esvp.session.checkpoint` | Create checkpoint |
|
|
28
|
+
| `dlab.esvp.session.finish` | Finish session |
|
|
29
|
+
| `dlab.esvp.replay.run` | Replay recording |
|
|
30
|
+
| `dlab.esvp.replay.validate` | Validate replay |
|
|
31
|
+
| `dlab.esvp.session.network` | Get network data |
|
|
32
|
+
| `dlab.esvp.network.configure` | Configure network proxy |
|
|
33
|
+
| `dlab.esvp.network.trace.attach` | Attach network trace |
|
|
34
|
+
| `dlab.project.esvp.current` | Get current project ESVP state |
|
|
35
|
+
| `dlab.project.esvp.validate` | Validate project recording |
|
|
36
|
+
| `dlab.project.esvp.replay` | Replay project recording |
|
|
37
|
+
| `dlab.project.esvp.sync_network` | Sync network traces |
|
|
38
|
+
| `dlab.project.esvp.app_trace_bootstrap` | Bootstrap app tracing |
|
|
39
|
+
|
|
40
|
+
## CLI Commands
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
discoverylab esvp status
|
|
44
|
+
discoverylab esvp devices
|
|
45
|
+
discoverylab esvp sessions
|
|
46
|
+
discoverylab esvp create
|
|
47
|
+
discoverylab esvp get <sessionId>
|
|
48
|
+
discoverylab esvp inspect <sessionId>
|
|
49
|
+
discoverylab esvp transcript <sessionId>
|
|
50
|
+
discoverylab esvp artifacts <sessionId>
|
|
51
|
+
discoverylab esvp artifact <sessionId> <artifactPath>
|
|
52
|
+
discoverylab esvp actions <sessionId>
|
|
53
|
+
discoverylab esvp checkpoint <sessionId>
|
|
54
|
+
discoverylab esvp finish <sessionId>
|
|
55
|
+
discoverylab esvp replay-run <sessionId>
|
|
56
|
+
discoverylab esvp replay-validate <sessionId>
|
|
57
|
+
discoverylab esvp replay-consistency <sessionId>
|
|
58
|
+
discoverylab esvp network <sessionId>
|
|
59
|
+
discoverylab esvp network-configure <sessionId>
|
|
60
|
+
discoverylab esvp network-clear <sessionId>
|
|
61
|
+
discoverylab esvp trace-attach <sessionId>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Network Proxy
|
|
65
|
+
|
|
66
|
+
DiscoveryLab defaults to `external-proxy` mode — the proxy runs locally and ESVP only persists traces.
|
|
67
|
+
|
|
68
|
+
### Host Rules
|
|
69
|
+
|
|
70
|
+
| Setup | Proxy Address |
|
|
71
|
+
|-------|--------------|
|
|
72
|
+
| macOS + iOS Simulator | `127.0.0.1` |
|
|
73
|
+
| macOS/Linux + Android Emulator | `10.0.2.2` |
|
|
74
|
+
| Physical Android | Host LAN IP |
|
|
75
|
+
|
|
76
|
+
### Environment Variables
|
|
77
|
+
|
|
78
|
+
| Variable | Purpose |
|
|
79
|
+
|----------|---------|
|
|
80
|
+
| `DISCOVERYLAB_NETWORK_PROXY_PORT` | Proxy port |
|
|
81
|
+
| `DISCOVERYLAB_NETWORK_PROXY_HOST` | Proxy host for device |
|
|
82
|
+
| `DISCOVERYLAB_NETWORK_PROXY_BIND_HOST` | Bind address (LAN) |
|
|
83
|
+
| `DISCOVERYLAB_NETWORK_PROXY_PROTOCOL` | Protocol (http/https) |
|
|
84
|
+
| `DISCOVERYLAB_NETWORK_PROXY_BYPASS` | Bypass patterns |
|
|
85
|
+
| `DISCOVERYLAB_NETWORK_PROXY_MAX_DURATION_MS` | Auto-finalize timeout (default: 15min) |
|
|
86
|
+
|
|
87
|
+
### Safety
|
|
88
|
+
|
|
89
|
+
- Proxies auto-finalize after 15 minutes to prevent stale proxy state
|
|
90
|
+
- Settings UI has an emergency lock and "Disable Active Proxy Now" button
|
|
91
|
+
- Server shutdown auto-cleans active proxies
|
|
92
|
+
|
|
93
|
+
## Example Prompts
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
Check my ESVP control-plane health
|
|
97
|
+
Create an ios-sim ESVP session and take a screenshot
|
|
98
|
+
Replay this iOS recording with an external proxy
|
|
99
|
+
Configure a proxy on this session and attach the HTTP trace
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Programmatic Usage
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { createESVPSession, runESVPActions } from '@veolab/discoverylab';
|
|
106
|
+
|
|
107
|
+
const created = await createESVPSession({
|
|
108
|
+
executor: 'ios-sim',
|
|
109
|
+
meta: { source: 'demo' },
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
await runESVPActions(created.session.id, {
|
|
113
|
+
actions: [{ name: 'screenshot' }],
|
|
114
|
+
finish: true,
|
|
115
|
+
});
|
|
116
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veolab/discoverylab",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.3",
|
|
4
4
|
"description": "AI-powered app testing & evidence generator - Claude Code Plugin",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
12
|
"dev": "tsx watch src/cli.ts serve",
|
|
13
|
-
"build": "tsup src/index.ts src/cli.ts --format esm --dts --clean && cp src/web/index.html dist/index.html && mkdir -p dist/visualizations && cp src/core/visualizations/templates/*.html dist/visualizations/ && node scripts/build-host-runtime.mjs --best-effort",
|
|
13
|
+
"build": "tsup src/index.ts src/cli.ts --format esm --dts --clean && cp src/web/index.html dist/index.html && mkdir -p dist/visualizations dist/export && cp src/core/visualizations/templates/*.html dist/visualizations/ && cp src/core/export/infographic-template.html dist/export/ && node scripts/build-host-runtime.mjs --best-effort",
|
|
14
14
|
"build:host-runtime": "node scripts/build-host-runtime.mjs",
|
|
15
15
|
"pack:local": "npm run build && npm run build:host-runtime && node scripts/verify-host-runtime-bundle.mjs && npm pack",
|
|
16
16
|
"prepack": "npm run build && npm run build:host-runtime && node scripts/verify-host-runtime-bundle.mjs",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"claude-plugin": {
|
|
69
69
|
"name": "DiscoveryLab",
|
|
70
70
|
"description": "AI-powered app testing & evidence generator",
|
|
71
|
-
"version": "1.
|
|
71
|
+
"version": "1.6.3",
|
|
72
72
|
"tools": [
|
|
73
73
|
"dlab.capture.screen",
|
|
74
74
|
"dlab.capture.emulator",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: knowledge-brain
|
|
3
|
-
description: Search app knowledge from captured flows
|
|
3
|
+
description: Search and visualize app knowledge from captured flows
|
|
4
4
|
context: fork
|
|
5
5
|
agent: general-purpose
|
|
6
6
|
always: true
|
|
@@ -11,71 +11,72 @@ tags:
|
|
|
11
11
|
- app-flow
|
|
12
12
|
- ui
|
|
13
13
|
- screens
|
|
14
|
+
- brain
|
|
14
15
|
---
|
|
15
16
|
|
|
16
17
|
# DiscoveryLab Knowledge Brain
|
|
17
18
|
|
|
18
|
-
DiscoveryLab captures app recordings and analyzes them with
|
|
19
|
+
DiscoveryLab captures app recordings and analyzes them with AI, building a knowledge base of every flow, screen, and UI element. This skill lets you query and visualize that knowledge.
|
|
19
20
|
|
|
20
|
-
##
|
|
21
|
+
## Default behavior: Visual first
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
- How a specific screen or flow works in their app
|
|
24
|
-
- What UI elements exist on a page (buttons, inputs, labels)
|
|
25
|
-
- The user journey through a feature (onboarding, checkout, settings)
|
|
26
|
-
- Comparing different captures of the same flow
|
|
27
|
-
- Any reference to app screens, recordings, or captured content
|
|
28
|
-
- Context about what was tested or recorded
|
|
29
|
-
|
|
30
|
-
Also use proactively when:
|
|
31
|
-
- The user is working on code related to a feature that was captured
|
|
32
|
-
- You need visual context about the app to give better answers
|
|
33
|
-
- The user mentions a Jira ticket that might be linked to a project
|
|
23
|
+
When the user asks about an app flow, **show it visually by default** using `dlab.knowledge.open`. The returned HTML renders as an interactive canvas with animated frame player, annotations, and navigation. Only fall back to text if the user explicitly asks for text or if no frames are available.
|
|
34
24
|
|
|
35
25
|
## Tools
|
|
36
26
|
|
|
37
|
-
### `dlab.knowledge.
|
|
38
|
-
|
|
27
|
+
### `dlab.knowledge.open` (primary - visual)
|
|
28
|
+
Opens an interactive infographic of an app flow. Returns self-contained HTML.
|
|
39
29
|
|
|
40
30
|
```
|
|
41
|
-
dlab.knowledge.
|
|
42
|
-
dlab.knowledge.
|
|
43
|
-
dlab.knowledge.
|
|
31
|
+
dlab.knowledge.open { query: "login" }
|
|
32
|
+
dlab.knowledge.open { query: "onboarding flow" }
|
|
33
|
+
dlab.knowledge.open { projectId: "abc123" }
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Use when:
|
|
37
|
+
- User asks "how does the login work?"
|
|
38
|
+
- User asks "show me the onboarding"
|
|
39
|
+
- User says "what does the settings screen look like?"
|
|
40
|
+
- Any question about a captured flow where visual context helps
|
|
41
|
+
|
|
42
|
+
### `dlab.knowledge.search` (text answers)
|
|
43
|
+
Search across all projects. Returns text with overview, flow steps, UI elements.
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
dlab.knowledge.search { query: "checkout" }
|
|
44
47
|
dlab.knowledge.search { query: "PROJ-123" }
|
|
45
|
-
dlab.knowledge.search { query: "settings profile" }
|
|
46
48
|
```
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
-
|
|
50
|
-
- User
|
|
51
|
-
-
|
|
52
|
-
- OCR text sample (actual text visible on screens)
|
|
53
|
-
- Project ID for deeper lookup
|
|
50
|
+
Use when:
|
|
51
|
+
- User asks a specific factual question ("what buttons are on the paywall?")
|
|
52
|
+
- User references a Jira ticket
|
|
53
|
+
- You need quick context without opening a visual
|
|
54
54
|
|
|
55
|
-
### `dlab.knowledge.summary`
|
|
56
|
-
|
|
55
|
+
### `dlab.knowledge.summary` (overview)
|
|
56
|
+
Lists all captured knowledge grouped by app.
|
|
57
57
|
|
|
58
58
|
```
|
|
59
59
|
dlab.knowledge.summary {}
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
Use
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
-
|
|
62
|
+
Use when:
|
|
63
|
+
- User asks "what do we have captured?"
|
|
64
|
+
- Starting a new conversation
|
|
65
|
+
- Need to orient on available projects
|
|
66
66
|
|
|
67
|
-
### `dlab.project.
|
|
68
|
-
|
|
67
|
+
### `dlab.project.import` (sharing)
|
|
68
|
+
Import a shared .applab project file.
|
|
69
69
|
|
|
70
70
|
```
|
|
71
|
-
dlab.project.
|
|
71
|
+
dlab.project.import { filePath: "/path/to/project.applab" }
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
Use when someone shares a .applab file.
|
|
75
|
+
|
|
76
|
+
## How to respond
|
|
75
77
|
|
|
76
|
-
1. **
|
|
77
|
-
2. **
|
|
78
|
-
3. **
|
|
79
|
-
4. **Cite the source** - mention which project
|
|
80
|
-
5. **Suggest captures** - if
|
|
81
|
-
6. **Be specific** - use the OCR text and UI elements from results to give precise answers, not generic ones
|
|
78
|
+
1. **Visual first** - use `dlab.knowledge.open` by default for flow questions
|
|
79
|
+
2. **Search first** - never say "I don't know" without searching
|
|
80
|
+
3. **No match?** - run `dlab.knowledge.summary` to show what's available
|
|
81
|
+
4. **Cite the source** - mention which project the information comes from
|
|
82
|
+
5. **Suggest captures** - if a flow doesn't exist, suggest capturing it
|