#!/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)