screenhand 0.4.0 → 0.4.2
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/mcp-desktop.js +10 -0
- package/dist/src/ingestion/coverage-auditor.js +12 -3
- package/dist/src/ingestion/feature-extractor.js +29 -29
- package/dist/src/memory/playbook-seeds.js +1 -1
- package/dist/src/playbook/engine.js +15 -0
- package/dist/src/playbook/mcp-recorder.js +21 -1
- package/dist/src/state/app-map.js +56 -51
- package/dist/src/state/ladder-generator.js +28 -28
- package/dist-app-maps/com.apple.Music.json +6587 -0
- package/dist-app-maps/com.apple.Notes.json +6098 -0
- package/dist-app-maps/com.apple.Photos.json +406 -0
- package/dist-app-maps/com.apple.Terminal.json +3968 -0
- package/dist-app-maps/com.apple.finder.json +420 -0
- package/dist-app-maps/com.apple.iCal.json +2375 -0
- package/dist-app-maps/com.apple.iWork.Keynote.json +2374 -0
- package/dist-app-maps/com.apple.iWork.Pages.json +7564 -0
- package/dist-app-maps/com.apple.mail.json +8353 -0
- package/dist-app-maps/com.apple.reminders.json +3322 -0
- package/dist-app-maps/net.whatsapp.WhatsApp.json +5151 -0
- package/dist-playbooks/calendar-create-event.json +20 -0
- package/dist-playbooks/calendar-list-events.json +20 -0
- package/dist-playbooks/calendar-navigate-views.json +47 -0
- package/dist-playbooks/calendar-open-settings.json +20 -0
- package/dist-playbooks/google-ads-transparency-competitor-research.json +89 -0
- package/dist-playbooks/google-search-competitor-research.json +76 -0
- package/dist-playbooks/keynote-add-slide.json +20 -0
- package/dist-playbooks/keynote-create-presentation.json +20 -0
- package/dist-playbooks/keynote-export-pdf.json +20 -0
- package/dist-playbooks/keynote-play-slideshow.json +20 -0
- package/dist-playbooks/meta-ad-library-competitor-research.json +100 -0
- package/dist-playbooks/notes-mastery-workflows.json +468 -0
- package/dist-playbooks/pages-export-pdf.json +20 -0
- package/dist-playbooks/pages-new-document.json +20 -0
- package/dist-playbooks/pages-open-document.json +20 -0
- package/dist-playbooks/reminders-complete.json +21 -0
- package/dist-playbooks/reminders-create.json +21 -0
- package/dist-playbooks/reminders-list.json +22 -0
- package/dist-playbooks/reminders-open.json +35 -0
- package/dist-playbooks/whatsapp-contact-info.json +32 -0
- package/dist-playbooks/whatsapp-navigate.json +71 -0
- package/dist-playbooks/whatsapp-new-call.json +32 -0
- package/dist-playbooks/whatsapp-new-group.json +32 -0
- package/dist-playbooks/whatsapp-search.json +28 -0
- package/dist-playbooks/whatsapp-settings.json +23 -0
- package/dist-playbooks/x_change_avatar.json +52 -0
- package/dist-references/apple-music.json +822 -0
- package/dist-references/calendar.json +1020 -0
- package/dist-references/finder.json +735 -7
- package/dist-references/google-search-competitor-research.json +73 -0
- package/dist-references/keynote.json +134 -0
- package/dist-references/mail.json +431 -0
- package/dist-references/pages.json +1203 -0
- package/dist-references/photos.json +642 -0
- package/dist-references/reminders.json +835 -0
- package/dist-references/terminal.json +640 -0
- package/dist-references/whatsapp.json +324 -0
- package/package.json +1 -1
package/dist/mcp-desktop.js
CHANGED
|
@@ -5289,6 +5289,16 @@ function getJobRunner() {
|
|
|
5289
5289
|
const client = await CDPClient({ port });
|
|
5290
5290
|
return { Runtime: client.Runtime, Input: client.Input, close: () => client.close() };
|
|
5291
5291
|
});
|
|
5292
|
+
// Wire AppleScript runner into playbook engine for applescript steps
|
|
5293
|
+
playbookEngine.setAppleScriptRunner(async (script) => {
|
|
5294
|
+
if (process.platform === "win32")
|
|
5295
|
+
throw new Error("AppleScript is not supported on Windows");
|
|
5296
|
+
const { execSync } = await import("node:child_process");
|
|
5297
|
+
return execSync(`osascript -e '${script.replace(/'/g, "'\\''")}'`, {
|
|
5298
|
+
encoding: "utf-8",
|
|
5299
|
+
timeout: 15000,
|
|
5300
|
+
}).trim();
|
|
5301
|
+
});
|
|
5292
5302
|
activeJobRunner = new JobRunner(bridge, jobManager, leaseManager, supervisor, (() => {
|
|
5293
5303
|
const cfg = {
|
|
5294
5304
|
hasCDP: cdpPort !== null,
|
|
@@ -39,7 +39,7 @@ export class CoverageAuditor {
|
|
|
39
39
|
*/
|
|
40
40
|
audit(bundleId, appName, menuScan) {
|
|
41
41
|
const refs = this.loadReferences(bundleId);
|
|
42
|
-
const playbooks = this.loadPlaybooks(bundleId);
|
|
42
|
+
const playbooks = this.loadPlaybooks(bundleId, appName);
|
|
43
43
|
// Count what we know
|
|
44
44
|
let shortcutsKnown = 0;
|
|
45
45
|
let selectorsKnown = 0;
|
|
@@ -208,8 +208,12 @@ export class CoverageAuditor {
|
|
|
208
208
|
catch { /* dir not found */ }
|
|
209
209
|
return refs;
|
|
210
210
|
}
|
|
211
|
-
loadPlaybooks(bundleId) {
|
|
211
|
+
loadPlaybooks(bundleId, appName) {
|
|
212
212
|
const playbooks = [];
|
|
213
|
+
// Derive short platform name from bundleId: "com.apple.Notes" → "notes"
|
|
214
|
+
const bundleParts = bundleId.split(".");
|
|
215
|
+
const shortName = (bundleParts[bundleParts.length - 1] ?? "").toLowerCase();
|
|
216
|
+
const appNameLower = appName.toLowerCase();
|
|
213
217
|
try {
|
|
214
218
|
const files = fs.readdirSync(this.playbooksDir);
|
|
215
219
|
for (const file of files) {
|
|
@@ -218,7 +222,12 @@ export class CoverageAuditor {
|
|
|
218
222
|
try {
|
|
219
223
|
const raw = fs.readFileSync(path.join(this.playbooksDir, file), "utf-8");
|
|
220
224
|
const pb = JSON.parse(raw);
|
|
221
|
-
|
|
225
|
+
// Match by bundleId (exact), platform name (case-insensitive), or app name
|
|
226
|
+
const platformLower = (pb.platform ?? "").toLowerCase();
|
|
227
|
+
if (pb.bundleId === bundleId ||
|
|
228
|
+
platformLower === bundleId ||
|
|
229
|
+
platformLower === shortName ||
|
|
230
|
+
platformLower === appNameLower) {
|
|
222
231
|
playbooks.push(pb);
|
|
223
232
|
}
|
|
224
233
|
}
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
// Copyright (C) 2025 Clazro Technology Private Limited
|
|
2
2
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
3
|
-
// ──
|
|
3
|
+
// ── Tier assignment keywords (F=entry, B=proficient, S=expert, SSS=grandmaster) ─
|
|
4
4
|
/** Single-word keywords checked individually */
|
|
5
|
-
const
|
|
5
|
+
const F_WORDS = new Set([
|
|
6
6
|
"basic", "create", "view", "share", "read",
|
|
7
7
|
"browse", "search", "home", "start", "launch", "write",
|
|
8
8
|
]);
|
|
9
|
-
const
|
|
9
|
+
const B_WORDS = new Set([
|
|
10
10
|
"organize", "format", "customize", "template", "tag", "folder",
|
|
11
11
|
"sort", "filter", "pin", "archive", "move", "rename", "duplicate",
|
|
12
12
|
"favorites", "bookmark", "list", "table", "style", "font",
|
|
13
13
|
]);
|
|
14
|
-
const
|
|
14
|
+
const S_WORDS = new Set([
|
|
15
15
|
"automate", "shortcut", "export", "import", "collaborate", "sync",
|
|
16
16
|
"scan", "link", "mention", "embed", "attachment", "password",
|
|
17
17
|
"encrypt", "lock", "version", "history", "recover", "backup",
|
|
18
18
|
]);
|
|
19
|
-
const
|
|
19
|
+
const SSS_WORDS = new Set([
|
|
20
20
|
"api", "integrate", "plugin", "advanced", "workflow", "script",
|
|
21
21
|
"extension", "developer", "sdk", "automation", "pipeline", "webhook",
|
|
22
22
|
]);
|
|
23
23
|
/** Multi-word phrases checked via substring match */
|
|
24
|
-
const
|
|
24
|
+
const SSS_PHRASES = [
|
|
25
25
|
"custom action", "get started",
|
|
26
26
|
];
|
|
27
27
|
// ── HTML entity decoding ──────────────────────────────────────────
|
|
@@ -97,32 +97,32 @@ function nameToId(name) {
|
|
|
97
97
|
function assignLevel(name, description) {
|
|
98
98
|
const text = `${name} ${description}`.toLowerCase();
|
|
99
99
|
const words = text.split(/\s+/);
|
|
100
|
-
// Check multi-word
|
|
101
|
-
for (const phrase of
|
|
100
|
+
// Check multi-word SSS phrases first
|
|
101
|
+
for (const phrase of SSS_PHRASES) {
|
|
102
102
|
if (text.includes(phrase))
|
|
103
|
-
return "
|
|
103
|
+
return "SSS";
|
|
104
104
|
}
|
|
105
105
|
// Check single-word keywords
|
|
106
106
|
for (const w of words) {
|
|
107
|
-
if (
|
|
108
|
-
return "
|
|
107
|
+
if (SSS_WORDS.has(w))
|
|
108
|
+
return "SSS";
|
|
109
109
|
}
|
|
110
110
|
for (const w of words) {
|
|
111
|
-
if (
|
|
112
|
-
return "
|
|
111
|
+
if (S_WORDS.has(w))
|
|
112
|
+
return "S";
|
|
113
113
|
}
|
|
114
114
|
for (const w of words) {
|
|
115
|
-
if (
|
|
116
|
-
return "
|
|
115
|
+
if (B_WORDS.has(w))
|
|
116
|
+
return "B";
|
|
117
117
|
}
|
|
118
118
|
for (const w of words) {
|
|
119
|
-
if (
|
|
120
|
-
return "
|
|
119
|
+
if (F_WORDS.has(w))
|
|
120
|
+
return "F";
|
|
121
121
|
}
|
|
122
122
|
// Fallback: longer descriptions suggest complexity
|
|
123
123
|
if (description.length > 80)
|
|
124
|
-
return "
|
|
125
|
-
return "
|
|
124
|
+
return "B";
|
|
125
|
+
return "F";
|
|
126
126
|
}
|
|
127
127
|
// ── Main: Extract features from HTML ──────────────────────────────
|
|
128
128
|
export function extractFeaturesFromHTML(html, appName, url) {
|
|
@@ -252,7 +252,7 @@ const VALUE_ADD_RULES = [
|
|
|
252
252
|
name: "Bulk Create",
|
|
253
253
|
description: `Create multiple ${app} items from a list or template`,
|
|
254
254
|
category: "bulk",
|
|
255
|
-
level: "
|
|
255
|
+
level: "B",
|
|
256
256
|
}),
|
|
257
257
|
},
|
|
258
258
|
{
|
|
@@ -263,7 +263,7 @@ const VALUE_ADD_RULES = [
|
|
|
263
263
|
name: "Bulk Delete",
|
|
264
264
|
description: `Delete multiple ${app} items matching criteria`,
|
|
265
265
|
category: "bulk",
|
|
266
|
-
level: "
|
|
266
|
+
level: "B",
|
|
267
267
|
}),
|
|
268
268
|
},
|
|
269
269
|
{
|
|
@@ -274,7 +274,7 @@ const VALUE_ADD_RULES = [
|
|
|
274
274
|
name: "Bulk Export",
|
|
275
275
|
description: `Export all ${app} items at once`,
|
|
276
276
|
category: "bulk",
|
|
277
|
-
level: "
|
|
277
|
+
level: "B",
|
|
278
278
|
}),
|
|
279
279
|
},
|
|
280
280
|
{
|
|
@@ -285,7 +285,7 @@ const VALUE_ADD_RULES = [
|
|
|
285
285
|
name: "Auto-Organize",
|
|
286
286
|
description: `Sort and organize ${app} items by content, date, or type`,
|
|
287
287
|
category: "organization",
|
|
288
|
-
level: "
|
|
288
|
+
level: "S",
|
|
289
289
|
}),
|
|
290
290
|
},
|
|
291
291
|
{
|
|
@@ -296,7 +296,7 @@ const VALUE_ADD_RULES = [
|
|
|
296
296
|
name: "Smart Search",
|
|
297
297
|
description: `Search across all ${app} content with pattern matching`,
|
|
298
298
|
category: "organization",
|
|
299
|
-
level: "
|
|
299
|
+
level: "B",
|
|
300
300
|
}),
|
|
301
301
|
},
|
|
302
302
|
{
|
|
@@ -307,7 +307,7 @@ const VALUE_ADD_RULES = [
|
|
|
307
307
|
name: "Summarize",
|
|
308
308
|
description: `Read and summarize all ${app} content`,
|
|
309
309
|
category: "intelligence",
|
|
310
|
-
level: "
|
|
310
|
+
level: "S",
|
|
311
311
|
}),
|
|
312
312
|
},
|
|
313
313
|
{
|
|
@@ -318,7 +318,7 @@ const VALUE_ADD_RULES = [
|
|
|
318
318
|
name: "Find Duplicates",
|
|
319
319
|
description: `Identify duplicate or near-duplicate ${app} items`,
|
|
320
320
|
category: "intelligence",
|
|
321
|
-
level: "
|
|
321
|
+
level: "S",
|
|
322
322
|
}),
|
|
323
323
|
},
|
|
324
324
|
{
|
|
@@ -329,7 +329,7 @@ const VALUE_ADD_RULES = [
|
|
|
329
329
|
name: "Cross-App Export",
|
|
330
330
|
description: `Export ${app} content to other apps automatically`,
|
|
331
331
|
category: "cross_app",
|
|
332
|
-
level: "
|
|
332
|
+
level: "S",
|
|
333
333
|
}),
|
|
334
334
|
},
|
|
335
335
|
{
|
|
@@ -340,7 +340,7 @@ const VALUE_ADD_RULES = [
|
|
|
340
340
|
name: "Cross-App Import",
|
|
341
341
|
description: `Import content from other apps into ${app}`,
|
|
342
342
|
category: "cross_app",
|
|
343
|
-
level: "
|
|
343
|
+
level: "S",
|
|
344
344
|
}),
|
|
345
345
|
},
|
|
346
346
|
{
|
|
@@ -351,7 +351,7 @@ const VALUE_ADD_RULES = [
|
|
|
351
351
|
name: "Change Monitor",
|
|
352
352
|
description: `Monitor ${app} for changes and notify`,
|
|
353
353
|
category: "monitoring",
|
|
354
|
-
level: "
|
|
354
|
+
level: "SSS",
|
|
355
355
|
}),
|
|
356
356
|
},
|
|
357
357
|
];
|
|
@@ -50,7 +50,7 @@ export function seedErrorsFromPlaybooks(playbooksDir) {
|
|
|
50
50
|
for (const pb of playbooks) {
|
|
51
51
|
const platform = pb.platform ?? pb.id ?? "unknown";
|
|
52
52
|
// Extract from errors[]
|
|
53
|
-
if (pb.errors) {
|
|
53
|
+
if (pb.errors && Array.isArray(pb.errors)) {
|
|
54
54
|
for (const err of pb.errors) {
|
|
55
55
|
const key = `${platform}::${err.error}`;
|
|
56
56
|
if (seen.has(key))
|
|
@@ -20,6 +20,7 @@ const STEP_DELAY_MS = 300;
|
|
|
20
20
|
export class PlaybookEngine {
|
|
21
21
|
runtime;
|
|
22
22
|
cdpConnect;
|
|
23
|
+
appleScriptRunner;
|
|
23
24
|
/** Enable observer-based popup checks before each step */
|
|
24
25
|
popupCheckEnabled = false;
|
|
25
26
|
constructor(runtime) {
|
|
@@ -33,6 +34,10 @@ export class PlaybookEngine {
|
|
|
33
34
|
setCDPConnect(factory) {
|
|
34
35
|
this.cdpConnect = factory;
|
|
35
36
|
}
|
|
37
|
+
/** Set AppleScript runner for applescript steps. Runner should execute the script and return stdout. */
|
|
38
|
+
setAppleScriptRunner(runner) {
|
|
39
|
+
this.appleScriptRunner = runner;
|
|
40
|
+
}
|
|
36
41
|
/**
|
|
37
42
|
* Execute a playbook against a live session.
|
|
38
43
|
* Returns result with success/failure and which step broke.
|
|
@@ -284,6 +289,14 @@ export class PlaybookEngine {
|
|
|
284
289
|
await client.close();
|
|
285
290
|
}
|
|
286
291
|
}
|
|
292
|
+
case "applescript": {
|
|
293
|
+
if (!step.script)
|
|
294
|
+
throw new Error("applescript step missing script");
|
|
295
|
+
if (!this.appleScriptRunner)
|
|
296
|
+
throw new Error("applescript requires runner — call setAppleScriptRunner() first");
|
|
297
|
+
const result = await this.appleScriptRunner(step.script);
|
|
298
|
+
return `applescript: ${result.substring(0, 200)}`;
|
|
299
|
+
}
|
|
287
300
|
default:
|
|
288
301
|
throw new Error(`Unknown action: ${step.action}`);
|
|
289
302
|
}
|
|
@@ -312,6 +325,8 @@ export class PlaybookEngine {
|
|
|
312
325
|
result.verify = sub(result.verify);
|
|
313
326
|
if (result.menuPath)
|
|
314
327
|
result.menuPath = result.menuPath.map(sub);
|
|
328
|
+
if (result.script)
|
|
329
|
+
result.script = sub(result.script);
|
|
315
330
|
return result;
|
|
316
331
|
}
|
|
317
332
|
/**
|
|
@@ -28,18 +28,30 @@ const SKIP_TOOLS = new Set([
|
|
|
28
28
|
"codex_monitor_start", "codex_monitor_status", "codex_monitor_add_task",
|
|
29
29
|
"codex_monitor_tasks", "codex_monitor_assign_now", "codex_monitor_stop",
|
|
30
30
|
"platform_learn", "platform_explore",
|
|
31
|
+
// Observation tools — not steps
|
|
32
|
+
"read_with_fallback", "locate_with_fallback", "execution_plan",
|
|
33
|
+
"browser_stealth",
|
|
34
|
+
// Observer/orchestrator lifecycle — not steps
|
|
35
|
+
"observer_start", "observer_stop", "observer_status", "observer_ocr_roi",
|
|
36
|
+
"orchestrator_start", "orchestrator_stop", "orchestrator_submit", "orchestrator_status",
|
|
37
|
+
// Ingestion/discovery — learning, not steps
|
|
38
|
+
"scan_menu_bar", "ingest_documentation", "ingest_tutorial",
|
|
39
|
+
"discover_features", "coverage_report",
|
|
31
40
|
]);
|
|
32
41
|
/** Map MCP tool names to PlaybookStep actions */
|
|
33
42
|
function mapToolToAction(toolName) {
|
|
34
43
|
switch (toolName) {
|
|
35
|
-
case "browser_navigate":
|
|
44
|
+
case "browser_navigate":
|
|
45
|
+
case "browser_open": return "navigate";
|
|
36
46
|
case "click":
|
|
37
47
|
case "click_text":
|
|
38
48
|
case "browser_click":
|
|
49
|
+
case "browser_human_click":
|
|
39
50
|
case "click_with_fallback":
|
|
40
51
|
case "ui_press": return "press";
|
|
41
52
|
case "type_text":
|
|
42
53
|
case "browser_type":
|
|
54
|
+
case "browser_fill_form":
|
|
43
55
|
case "type_with_fallback": return "type_into";
|
|
44
56
|
case "key": return "key";
|
|
45
57
|
case "menu_click": return "menu_click";
|
|
@@ -50,6 +62,9 @@ function mapToolToAction(toolName) {
|
|
|
50
62
|
case "screenshot_file": return "screenshot";
|
|
51
63
|
case "browser_wait":
|
|
52
64
|
case "wait_for_state": return "wait";
|
|
65
|
+
case "applescript": return "applescript";
|
|
66
|
+
case "ui_set_value": return "type_into";
|
|
67
|
+
case "select_with_fallback": return "press";
|
|
53
68
|
case "focus":
|
|
54
69
|
case "launch": return null; // useful context but not a step
|
|
55
70
|
case "drag": return null; // drag is complex, skip for now
|
|
@@ -108,6 +123,10 @@ function buildStep(toolName, params, success) {
|
|
|
108
123
|
step.ms = Number(params.timeout ?? params.ms ?? params.timeoutMs ?? 1000);
|
|
109
124
|
step.description = `Wait ${step.ms}ms`;
|
|
110
125
|
break;
|
|
126
|
+
case "applescript":
|
|
127
|
+
step.script = String(params.script ?? "");
|
|
128
|
+
step.description = `AppleScript: ${step.script.substring(0, 80)}${step.script.length > 80 ? "..." : ""}`;
|
|
129
|
+
break;
|
|
111
130
|
}
|
|
112
131
|
if (!success) {
|
|
113
132
|
step.optional = true;
|
|
@@ -168,6 +187,7 @@ export class McpPlaybookRecorder {
|
|
|
168
187
|
...(typeof s.target === "string" ? { target: redactPII(s.target) } : {}),
|
|
169
188
|
...(s.url ? { url: redactPII(s.url) } : {}),
|
|
170
189
|
...(s.code ? { code: redactPII(s.code) } : {}),
|
|
190
|
+
...(s.script ? { script: redactPII(s.script) } : {}),
|
|
171
191
|
...(s.description ? { description: redactPII(s.description) } : {}),
|
|
172
192
|
}));
|
|
173
193
|
const playbook = {
|
|
@@ -11,59 +11,59 @@ import { generateLadderFromReference } from "./ladder-generator.js";
|
|
|
11
11
|
// Define what real users do at each level. Used to measure honest mastery.
|
|
12
12
|
const BUILTIN_LADDERS = {
|
|
13
13
|
"com.hnc.Discord": [
|
|
14
|
-
// ──
|
|
15
|
-
{ id: "browse_channels", description: "Join servers and browse channels", level: "
|
|
16
|
-
{ id: "send_message", description: "Send messages, replies, emojis, and reactions", level: "
|
|
17
|
-
{ id: "direct_messages", description: "Direct messages and group chats", level: "
|
|
18
|
-
{ id: "voice_video", description: "Voice channels, video calls, and screen share", level: "
|
|
19
|
-
// ──
|
|
20
|
-
{ id: "threads_forums", description: "Create and manage threads and forum channels", level: "
|
|
21
|
-
{ id: "roles_permissions", description: "Configure roles, overrides, inheritance, hidden channels", level: "
|
|
22
|
-
{ id: "events_stage", description: "Schedule events, run Stage channels, manage speakers", level: "
|
|
23
|
-
{ id: "onboarding_funnel", description: "Build join flows: rules screening, role assignment, starter channels", level: "
|
|
24
|
-
{ id: "notification_control", description: "Channel overrides, mention control, suppression settings", level: "
|
|
25
|
-
// ──
|
|
26
|
-
{ id: "moderation_system", description: "Configure AutoMod, mod bots, alert flows, ban appeals, raid defense", level: "
|
|
27
|
-
{ id: "bot_ecosystem", description: "Combine bots, slash commands, webhooks into coherent server OS", level: "
|
|
28
|
-
{ id: "server_architecture", description: "Design categories, channel taxonomy, permissions, escalation paths", level: "
|
|
29
|
-
{ id: "community_growth", description: "Events, role rewards, content loops, announcements, retention mechanics", level: "
|
|
30
|
-
{ id: "analytics_health", description: "Track activity patterns, onboarding drop-off, channel usage, retention", level: "
|
|
31
|
-
// ──
|
|
32
|
-
{ id: "monetization_membership", description: "Premium roles, gated channels, supporter tiers, creator monetization", level: "
|
|
33
|
-
{ id: "crisis_handling", description: "Handle raids, harassment, spam, leaks, impersonation, conflicts", level: "
|
|
34
|
-
{ id: "cross_platform", description: "Connect Discord with GitHub, Notion, Twitch, Stripe, Zapier, tools", level: "
|
|
35
|
-
{ id: "staff_system", description: "Structure mod roles, escalation, internal channels, review processes", level: "
|
|
36
|
-
{ id: "brand_culture", description: "Shape tone, rituals, norms, recognition systems, community identity", level: "
|
|
37
|
-
{ id: "governance_policy", description: "Define rules, enforcement, appeals, social boundaries that hold up", level: "
|
|
14
|
+
// ── F tier: basic consumer actions (weight 1) ──
|
|
15
|
+
{ id: "browse_channels", description: "Join servers and browse channels", level: "F", weight: 1, critical: false },
|
|
16
|
+
{ id: "send_message", description: "Send messages, replies, emojis, and reactions", level: "F", weight: 1, critical: false },
|
|
17
|
+
{ id: "direct_messages", description: "Direct messages and group chats", level: "F", weight: 1, critical: false },
|
|
18
|
+
{ id: "voice_video", description: "Voice channels, video calls, and screen share", level: "F", weight: 1, critical: false },
|
|
19
|
+
// ── B tier: operational features (weight 2) ──
|
|
20
|
+
{ id: "threads_forums", description: "Create and manage threads and forum channels", level: "B", weight: 2, critical: false },
|
|
21
|
+
{ id: "roles_permissions", description: "Configure roles, overrides, inheritance, hidden channels", level: "B", weight: 2, critical: true },
|
|
22
|
+
{ id: "events_stage", description: "Schedule events, run Stage channels, manage speakers", level: "B", weight: 2, critical: false },
|
|
23
|
+
{ id: "onboarding_funnel", description: "Build join flows: rules screening, role assignment, starter channels", level: "B", weight: 2, critical: true },
|
|
24
|
+
{ id: "notification_control", description: "Channel overrides, mention control, suppression settings", level: "B", weight: 1, critical: false },
|
|
25
|
+
// ── S tier: system-level features (weight 2-3) ──
|
|
26
|
+
{ id: "moderation_system", description: "Configure AutoMod, mod bots, alert flows, ban appeals, raid defense", level: "S", weight: 3, critical: true },
|
|
27
|
+
{ id: "bot_ecosystem", description: "Combine bots, slash commands, webhooks into coherent server OS", level: "S", weight: 3, critical: true },
|
|
28
|
+
{ id: "server_architecture", description: "Design categories, channel taxonomy, permissions, escalation paths", level: "S", weight: 3, critical: true },
|
|
29
|
+
{ id: "community_growth", description: "Events, role rewards, content loops, announcements, retention mechanics", level: "S", weight: 2, critical: false },
|
|
30
|
+
{ id: "analytics_health", description: "Track activity patterns, onboarding drop-off, channel usage, retention", level: "S", weight: 2, critical: true },
|
|
31
|
+
// ── SSS tier: grandmaster operations (weight 3) ──
|
|
32
|
+
{ id: "monetization_membership", description: "Premium roles, gated channels, supporter tiers, creator monetization", level: "SSS", weight: 2, critical: false },
|
|
33
|
+
{ id: "crisis_handling", description: "Handle raids, harassment, spam, leaks, impersonation, conflicts", level: "SSS", weight: 3, critical: true },
|
|
34
|
+
{ id: "cross_platform", description: "Connect Discord with GitHub, Notion, Twitch, Stripe, Zapier, tools", level: "SSS", weight: 2, critical: false },
|
|
35
|
+
{ id: "staff_system", description: "Structure mod roles, escalation, internal channels, review processes", level: "SSS", weight: 3, critical: true },
|
|
36
|
+
{ id: "brand_culture", description: "Shape tone, rituals, norms, recognition systems, community identity", level: "SSS", weight: 2, critical: false },
|
|
37
|
+
{ id: "governance_policy", description: "Define rules, enforcement, appeals, social boundaries that hold up", level: "SSS", weight: 3, critical: true },
|
|
38
38
|
],
|
|
39
39
|
"com.apple.Safari": [
|
|
40
|
-
{ id: "browse_navigate", description: "Open URLs and navigate pages", level: "
|
|
41
|
-
{ id: "tabs_windows", description: "Manage tabs and windows", level: "
|
|
42
|
-
{ id: "bookmarks", description: "Bookmarks and reading list", level: "
|
|
43
|
-
{ id: "history_search", description: "History and search", level: "
|
|
44
|
-
{ id: "tab_groups", description: "Tab groups and profiles", level: "
|
|
45
|
-
{ id: "extensions", description: "Install and use extensions", level: "
|
|
46
|
-
{ id: "dev_tools", description: "Web Inspector and developer tools", level: "
|
|
47
|
-
{ id: "privacy_settings", description: "Privacy, cookies, and content blockers", level: "
|
|
48
|
-
{ id: "web_apps", description: "Add to Dock, web apps, notifications", level: "
|
|
40
|
+
{ id: "browse_navigate", description: "Open URLs and navigate pages", level: "F", weight: 1, critical: false },
|
|
41
|
+
{ id: "tabs_windows", description: "Manage tabs and windows", level: "F", weight: 1, critical: false },
|
|
42
|
+
{ id: "bookmarks", description: "Bookmarks and reading list", level: "F", weight: 1, critical: false },
|
|
43
|
+
{ id: "history_search", description: "History and search", level: "F", weight: 1, critical: false },
|
|
44
|
+
{ id: "tab_groups", description: "Tab groups and profiles", level: "B", weight: 2, critical: false },
|
|
45
|
+
{ id: "extensions", description: "Install and use extensions", level: "B", weight: 2, critical: false },
|
|
46
|
+
{ id: "dev_tools", description: "Web Inspector and developer tools", level: "S", weight: 2, critical: true },
|
|
47
|
+
{ id: "privacy_settings", description: "Privacy, cookies, and content blockers", level: "S", weight: 2, critical: false },
|
|
48
|
+
{ id: "web_apps", description: "Add to Dock, web apps, notifications", level: "SSS", weight: 2, critical: false },
|
|
49
49
|
],
|
|
50
50
|
"com.apple.finder": [
|
|
51
|
-
{ id: "browse_files", description: "Browse and open files/folders", level: "
|
|
52
|
-
{ id: "copy_move", description: "Copy, move, rename, delete files", level: "
|
|
53
|
-
{ id: "search", description: "Spotlight and Finder search", level: "
|
|
54
|
-
{ id: "views_sort", description: "Change views, sort, and organize", level: "
|
|
55
|
-
{ id: "tags_favorites", description: "Tags, favorites, and sidebar", level: "
|
|
56
|
-
{ id: "quick_actions", description: "Quick Look, Quick Actions, and Services", level: "
|
|
57
|
-
{ id: "automator_scripts", description: "Automator, terminal, and scripting", level: "
|
|
51
|
+
{ id: "browse_files", description: "Browse and open files/folders", level: "F", weight: 1, critical: false },
|
|
52
|
+
{ id: "copy_move", description: "Copy, move, rename, delete files", level: "F", weight: 1, critical: false },
|
|
53
|
+
{ id: "search", description: "Spotlight and Finder search", level: "F", weight: 1, critical: false },
|
|
54
|
+
{ id: "views_sort", description: "Change views, sort, and organize", level: "B", weight: 2, critical: false },
|
|
55
|
+
{ id: "tags_favorites", description: "Tags, favorites, and sidebar", level: "B", weight: 2, critical: false },
|
|
56
|
+
{ id: "quick_actions", description: "Quick Look, Quick Actions, and Services", level: "S", weight: 2, critical: true },
|
|
57
|
+
{ id: "automator_scripts", description: "Automator, terminal, and scripting", level: "SSS", weight: 2, critical: false },
|
|
58
58
|
],
|
|
59
59
|
};
|
|
60
60
|
/** Generic fallback ladder — used when no builtin AND no reference-generated ladder exists. */
|
|
61
61
|
const GENERIC_LADDER = [
|
|
62
|
-
{ id: "basic_navigation", description: "Open, navigate, and browse the app", level: "
|
|
63
|
-
{ id: "core_action", description: "Perform the app's primary action", level: "
|
|
64
|
-
{ id: "settings", description: "Configure settings and preferences", level: "
|
|
65
|
-
{ id: "advanced_features", description: "Use advanced/power-user features", level: "
|
|
66
|
-
{ id: "automation", description: "Automate or customize workflows", level: "
|
|
62
|
+
{ id: "basic_navigation", description: "Open, navigate, and browse the app", level: "F", weight: 1, critical: false },
|
|
63
|
+
{ id: "core_action", description: "Perform the app's primary action", level: "F", weight: 1, critical: false },
|
|
64
|
+
{ id: "settings", description: "Configure settings and preferences", level: "B", weight: 2, critical: false },
|
|
65
|
+
{ id: "advanced_features", description: "Use advanced/power-user features", level: "S", weight: 2, critical: true },
|
|
66
|
+
{ id: "automation", description: "Automate or customize workflows", level: "SSS", weight: 3, critical: true },
|
|
67
67
|
];
|
|
68
68
|
/** Redact an array of user-facing strings in place, returning a new array. */
|
|
69
69
|
function redactStrings(strings) {
|
|
@@ -289,11 +289,11 @@ export class AppMap {
|
|
|
289
289
|
if (discoveredCount >= 30)
|
|
290
290
|
return null;
|
|
291
291
|
// Assign level based on tool complexity
|
|
292
|
-
let level = "
|
|
292
|
+
let level = "F";
|
|
293
293
|
if (toolName === "menu_click" || toolName === "key")
|
|
294
|
-
level = "
|
|
294
|
+
level = "B";
|
|
295
295
|
if (toolName === "applescript" || toolName === "browser_js")
|
|
296
|
-
level = "
|
|
296
|
+
level = "S";
|
|
297
297
|
const feature = {
|
|
298
298
|
id: featureId,
|
|
299
299
|
description: `[auto] ${target}`,
|
|
@@ -1435,9 +1435,14 @@ export class AppMap {
|
|
|
1435
1435
|
let totalFailures = 0;
|
|
1436
1436
|
let weightedScore = 0;
|
|
1437
1437
|
// Tier-scoped critical floor: only check critical features at or below the target tier
|
|
1438
|
-
|
|
1439
|
-
const
|
|
1440
|
-
|
|
1438
|
+
// MasteryLevel (deprecated) maps to FeatureTier: beginner→F, pro→B, expert→S, grandmaster→SSS
|
|
1439
|
+
const masteryToFeatureTier = {
|
|
1440
|
+
beginner: "F", pro: "B", expert: "S", grandmaster: "SSS",
|
|
1441
|
+
};
|
|
1442
|
+
const featureTierOrder = ["F", "B", "S", "SSS"];
|
|
1443
|
+
const scopedFeatureTier = tierScope ? masteryToFeatureTier[tierScope] : "SSS";
|
|
1444
|
+
const scopeIdx = featureTierOrder.indexOf(scopedFeatureTier);
|
|
1445
|
+
const scopedLevels = new Set(featureTierOrder.slice(0, scopeIdx + 1));
|
|
1441
1446
|
let criticalMinDepth = 999;
|
|
1442
1447
|
let hasScopedCritical = false;
|
|
1443
1448
|
for (const feature of ladder) {
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
// Copyright (C) 2025 Clazro Technology Private Limited
|
|
2
2
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
3
|
-
// ── Keyword sets for
|
|
4
|
-
const
|
|
3
|
+
// ── Keyword sets for tier assignment (F=entry, B=proficient, S=expert, SSS=grandmaster) ──
|
|
4
|
+
const F_KEYWORDS = new Set([
|
|
5
5
|
"navigation", "browse", "basic", "search", "header", "page", "nav", "home",
|
|
6
6
|
"page_header", "quick_find",
|
|
7
7
|
]);
|
|
8
|
-
const
|
|
8
|
+
const B_KEYWORDS = new Set([
|
|
9
9
|
"settings", "create", "views", "sort", "filter", "template", "new", "sidebar",
|
|
10
10
|
"create_new", "slash_commands", "notification", "preferences",
|
|
11
11
|
]);
|
|
12
|
-
const
|
|
12
|
+
const S_KEYWORDS = new Set([
|
|
13
13
|
"database", "admin", "moderation", "permissions", "automation", "advanced",
|
|
14
14
|
"server", "import", "export", "workflow", "security", "form",
|
|
15
15
|
]);
|
|
16
|
-
const
|
|
16
|
+
const SSS_KEYWORDS = new Set([
|
|
17
17
|
"ai", "api", "integration", "custom", "governance", "crisis", "identity",
|
|
18
18
|
"billing", "orchestrat", "pipeline",
|
|
19
19
|
]);
|
|
@@ -89,9 +89,9 @@ export function generateLadderFromReference(ref) {
|
|
|
89
89
|
// where a short website feature id like "export" matches "export_pdf_flow")
|
|
90
90
|
if (selectorGroups[wf.id] !== undefined || flows[wf.id] !== undefined)
|
|
91
91
|
continue;
|
|
92
|
-
const level = (["
|
|
92
|
+
const level = (["F", "B", "S", "SSS"].includes(wf.level)
|
|
93
93
|
? wf.level
|
|
94
|
-
: "
|
|
94
|
+
: "F");
|
|
95
95
|
const weight = assignWeight(wf.id, 0, level);
|
|
96
96
|
features.push({
|
|
97
97
|
id: featureId,
|
|
@@ -109,9 +109,9 @@ export function generateLadderFromReference(ref) {
|
|
|
109
109
|
const featureId = `va_${va.id}`;
|
|
110
110
|
if (features.some((f) => f.id === featureId))
|
|
111
111
|
continue;
|
|
112
|
-
const level = (["
|
|
112
|
+
const level = (["B", "S", "SSS"].includes(va.level)
|
|
113
113
|
? va.level
|
|
114
|
-
: "
|
|
114
|
+
: "S");
|
|
115
115
|
features.push({
|
|
116
116
|
id: featureId,
|
|
117
117
|
description: va.description,
|
|
@@ -124,7 +124,7 @@ export function generateLadderFromReference(ref) {
|
|
|
124
124
|
}
|
|
125
125
|
// ── Step 3: Sort by level progression ──────────────────────────
|
|
126
126
|
const levelOrder = {
|
|
127
|
-
|
|
127
|
+
F: 0, B: 1, S: 2, SSS: 3,
|
|
128
128
|
};
|
|
129
129
|
features.sort((a, b) => levelOrder[a.level] - levelOrder[b.level]);
|
|
130
130
|
return { ladder: features, signals, hash: computeHash(ref) };
|
|
@@ -133,37 +133,37 @@ export function generateLadderFromReference(ref) {
|
|
|
133
133
|
function assignLevel(name, selectorCount, isFlow, complexity) {
|
|
134
134
|
const nameLower = name.toLowerCase();
|
|
135
135
|
const parts = nameLower.split(/[_\s-]+/);
|
|
136
|
-
// Check
|
|
137
|
-
if (parts.some(p =>
|
|
138
|
-
return "
|
|
139
|
-
if (parts.some(p =>
|
|
140
|
-
return "
|
|
141
|
-
if (parts.some(p =>
|
|
142
|
-
return "
|
|
143
|
-
if (parts.some(p =>
|
|
144
|
-
return "
|
|
136
|
+
// Check SSS first (most specific)
|
|
137
|
+
if (parts.some(p => SSS_KEYWORDS.has(p)))
|
|
138
|
+
return "SSS";
|
|
139
|
+
if (parts.some(p => S_KEYWORDS.has(p)))
|
|
140
|
+
return "S";
|
|
141
|
+
if (parts.some(p => B_KEYWORDS.has(p)))
|
|
142
|
+
return "B";
|
|
143
|
+
if (parts.some(p => F_KEYWORDS.has(p)))
|
|
144
|
+
return "F";
|
|
145
145
|
// Fallback based on complexity
|
|
146
146
|
if (isFlow) {
|
|
147
147
|
if (complexity >= 8)
|
|
148
|
-
return "
|
|
148
|
+
return "S";
|
|
149
149
|
if (complexity >= 5)
|
|
150
|
-
return "
|
|
151
|
-
return "
|
|
150
|
+
return "B";
|
|
151
|
+
return "F";
|
|
152
152
|
}
|
|
153
153
|
// Selector group: more selectors = more complex
|
|
154
154
|
if (selectorCount >= 8)
|
|
155
|
-
return "
|
|
155
|
+
return "S";
|
|
156
156
|
if (selectorCount >= 4)
|
|
157
|
-
return "
|
|
158
|
-
return "
|
|
157
|
+
return "B";
|
|
158
|
+
return "F";
|
|
159
159
|
}
|
|
160
160
|
// ── Weight assignment ────────────────────────────────────────────
|
|
161
161
|
function assignWeight(name, complexity, level) {
|
|
162
|
-
if (level === "
|
|
162
|
+
if (level === "SSS")
|
|
163
163
|
return 3;
|
|
164
|
-
if (level === "
|
|
164
|
+
if (level === "S" && complexity >= 6)
|
|
165
165
|
return 3;
|
|
166
|
-
if (level === "
|
|
166
|
+
if (level === "S" || level === "B")
|
|
167
167
|
return 2;
|
|
168
168
|
return 1;
|
|
169
169
|
}
|