Files
ycode/scripts/rea/recover-subagent-icons.cjs
T

71 lines
4.8 KiB
JavaScript

// SVG recovery from the fixed REA script export only; no original installation/runtime access.
const fs = require('node:fs'), crypto = require('node:crypto')
const { parse } = require('@babel/parser')
const traverse = require('@babel/traverse').default
const archive = JSON.parse(fs.readFileSync('.tmp/rea-runtime-scripts.json', 'utf8'))
const cache = new Map()
const literal = node => !node ? undefined : node.type === 'TemplateLiteral' && !node.expressions.length ? node.quasis[0].value.cooked : ['StringLiteral', 'NumericLiteral', 'BooleanLiteral'].includes(node.type) ? node.value : undefined
const object = node => Object.fromEntries((node?.properties || []).filter(prop => prop.type === 'ObjectProperty').flatMap(prop => {
const value = literal(prop.value)
return value !== undefined ? [[prop.key.name ?? prop.key.value, value]] : []
}))
function get(file) {
if (cache.has(file)) return cache.get(file)
const source = archive.items.find(item => item.file_path.endsWith(file))?.source?.artifact
if (!source) return
if (crypto.createHash('sha256').update(source.text).digest('hex') !== source.sha256) throw Error('REA digest mismatch')
if (file === 'contexts-BMeWSUHk.js' && source.sha256 !== '5f35b7489b107e21ef452c8ee88a95265756f6fc444841eef869228bbc44768b') throw Error('REA context identity changed')
if (file === 'src-DPDj7_qD.js' && source.sha256 !== '747e41d215375e1214c366213aea7e077d85421923029c61cbb6e9562be58de0') throw Error('REA icon identity changed')
const ast = parse(source.text, { sourceType: 'module' })
const result = { source, ast }
cache.set(file, result)
return result
}
function resolve(file, name, depth = 0) {
const source = get(file)
if (!source || depth > 8) return
let imported, declaration
traverse(source.ast, {
ImportDeclaration(p) { for (const spec of p.node.specifiers) if (spec.local.name === name && spec.type === 'ImportSpecifier') imported = { file: p.node.source.value.replace('./', ''), member: spec.imported.name } },
FunctionDeclaration(p) { if (p.node.id?.name === name) declaration = p },
AssignmentExpression(p) { if (p.node.left.name === name) declaration = p },
VariableDeclarator(p) { if (p.node.id?.name === name && p.node.init) declaration = p }
})
if (imported && get(imported.file)) {
let local
traverse(get(imported.file).ast, { ExportNamedDeclaration(p) { for (const spec of p.node.specifiers) if (spec.exported.name === imported.member) local = spec.local.name } })
if (local) return resolve(imported.file, local, depth + 1)
}
if (!declaration) return
const right = declaration.node.right ?? declaration.node.init
if (right?.type === 'CallExpression' && typeof literal(right.arguments[0]) === 'string' && right.arguments[1]?.type === 'Identifier') {
let nodes
traverse(source.ast, { AssignmentExpression(p) { if (p.node.left.name === right.arguments[1].name && p.node.right.type === 'ArrayExpression') nodes = p.node.right.elements } })
if (nodes) return { file, sha256: source.source.sha256, props: { viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round' }, children: nodes.map(entry => ({ tag: literal(entry.elements[0]), props: object(entry.elements[1]) })) }
}
if (right?.type === 'CallExpression' && right.arguments[0]?.type === 'Identifier') return resolve(file, right.arguments[0].name, depth + 1)
let svg, paths = [], next
declaration.traverse({ CallExpression(p) {
const tag = p.node.arguments[0]
if (tag?.type === 'Identifier') { next = tag.name; return }
const kind = literal(tag)
if (!['svg', 'path', 'circle', 'rect', 'line', 'polyline', 'polygon', 'ellipse'].includes(kind)) return
const props = object(p.node.arguments[1])
if (kind === 'svg') svg = props
else paths.push({ tag: kind, props })
} })
if (svg && paths.length) return { file, sha256: source.source.sha256, props: svg, children: paths }
return next ? resolve(file, next, depth + 1) : undefined
}
const icons = {}
for (const [key, binding] of Object.entries({ chevronDown: 'Oo', chevronRight: 'yo', loading: 'xo', check: 'Ea', failure: 'Ba', close: 'Da', assignment: 'Pte' })) {
icons[key] = resolve('contexts-BMeWSUHk.js', binding) || null
}
if (Object.values(icons).some(icon => !icon)) throw Error('A selected source member icon was not recovered')
const output = 'src/renderer/src/workbuddy/subagents/icons.json'
const content = JSON.stringify({ version: '5.8.2', evidenceId: archive.evidence_id, icons }, null, 2) + '\n'
if (process.argv.includes('--check')) {
if (fs.readFileSync(output, 'utf8') !== content) throw Error('Recovered member icon drift')
} else fs.writeFileSync(output, content)
console.log(JSON.stringify({ icons: Object.keys(icons).length, sha256: crypto.createHash('sha256').update(content).digest('hex') }))