aboutsummaryrefslogtreecommitdiff
path: root/scripts/gen-floating-designer-registry.py
blob: 1b150d673f8a8c05b2d35d7a91878a23fa3a0842 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#!/usr/bin/env python3
"""gen-floating-designer-registry.py — Task 4 of the v2->v3 repair plan. (v2)

Rebuilds the floating-ui-designer CMDS registry from ground truth:
  * union of `commands['X']` registrations in modules/core + modules/ext
    (bundle/worker files skipped), and
  * terminal.js dispatch builtins (`cmd === 'x'`).

Emits /tmp/fui_cmds_snippet.txt with one JS line per command:
    ['name','desc','cat',1],
Descriptions are re-escaped safely: apostrophes -> \u2019, backslashes -> \u005C
(no raw backslash pairs ever enter the page). Prints acceptance diagnostics.
"""
import os, re
from collections import Counter

BASE = '/var/www/webx11.duckdns.org/spaces/newos4'
OUT = '/tmp/fui_cmds_snippet.txt'

files = []
for d in ('modules/core', 'modules/ext'):
    dd = os.path.join(BASE, d)
    for f in sorted(os.listdir(dd)):
        if not f.endswith('.js'):
            continue
        if 'bundle' in f or 'worker' in f:
            continue
        files.append(os.path.join(d, f))

texts = {}
for rel in files:
    try:
        texts[rel] = open(os.path.join(BASE, rel), encoding='utf-8', errors='replace').read()
    except Exception as e:
        print('skip', rel, e)

cmd_re = re.compile(r"commands\['([^']+)'\]\s*=")
src_of = {}
for rel, txt in texts.items():
    for name in cmd_re.findall(txt):
        src_of.setdefault(name, set()).add(rel)

disp = set()
t = texts.get('modules/core/terminal.js', '')
for m in re.finditer(r"cmd === '([a-z0-9-]+)'", t):
    disp.add(m.group(1))
for n in disp:
    src_of.setdefault(n, set()).add('modules/core/terminal.js')

ALL = sorted(src_of.keys())

def cat_for(rels):
    joined = ' '.join(rels)
    rules = [
        (('aiassist', 'aiasm', 'smartctx', 'chatstudio', 'ollama'), 'ai'),
        (('agent-registry', 'orchestrator', 'leases', 'memory-graph', 'world-model'), 'agents'),
        (('module-fs', 'busybox', 'shell', 'filemgr', 'editor', 'finder', 'diff', 'neural-vfs', 'cryptogzip'), 'files'),
        (('searxng', 'fetcher', 'webdav'), 'network'),
        (('calc', 'mindmap', 'three', 'media', 'voice', 'wpm', 'tutorial', 'console'), 'tools'),
        (('floating', 'theme', 'kanban'), 'ui'),
        (('system', 'config', 'modmgr', 'debug-supervisor', 'predictor', 'servermon', 'sandbox', 'secret-vault', 'terminal'), 'system'),
    ]
    for keys, cat in rules:
        if any(k in joined for k in keys):
            return cat
    return 'system'

DESC_FALLBACK = 'Terminal command'

def desc_for(name):
    pat = re.compile(r"'" + re.escape(name) + r"'\s*:\s*'((?:[^'\\]|\\.){1,200})'")
    for rel in sorted(texts.keys()):
        m = pat.search(texts[rel])
        if not m:
            continue
        raw = m.group(1)
        # unescape source-level escaping: \\ -> placeholder, \' and \" -> quote
        raw = raw.replace('\\\\', '\x01')
        raw = raw.replace("\\'", "'").replace('\\"', '"')
        raw = raw.replace('\x01', '\\')
        # strip control chars
        raw = ''.join(' ' if ord(c) < 32 else c for c in raw)
        if '\u2014' in raw:
            raw = raw.split('\u2014', 1)[1].strip()
        if not raw or raw.lower() == name:
            continue
        if len(raw) > 90:
            raw = raw[:87].rstrip() + '...'
        return raw
    return DESC_FALLBACK

entries = []
for n in ALL:
    d = desc_for(n)
    d = d.replace('\\', '\\u005C').replace("'", '\\u2019')
    entries.append((cat_for(src_of[n]), n, d))

entries.sort(key=lambda e: (e[0], e[1]))

cnt = Counter(c for c, _, _ in entries)
print('total=%d' % len(entries))
for k in sorted(cnt):
    print('  %-8s %d' % (k, cnt[k]))

want = ['session-new', 'health-check', 'doctor', 'uptime', 'ai-stop', 'kanban', 'webdav-browser', 'vfs-export', 'fetch']
bad = ['new-session', 'clipboard-history', 'web', 'save', 'load', 'import', 'svg']
have = set(e[1] for e in entries)
print('missing:', [w for w in want if w not in have])
print('excluded-present:', [b for b in bad if b in have])
print('no-desc fallback count:', sum(1 for _, _, d in entries if d == DESC_FALLBACK))

assert len(entries) >= 330, 'TOO FEW COMMANDS: %d' % len(entries)

with open(OUT, 'w', encoding='utf-8') as f:
    for cat, n, d in entries:
        f.write("['%s','%s','%s',1],\n" % (n, d, cat))
print('WROTE', OUT)