openpencil/packages/cli/src/commands/tree.ts
Danila Poyarkov 1975fae36e Add @open-pencil/cli with headless .fig file operations
Commands:
- open-pencil info <file> — document stats, node types, fonts
- open-pencil tree <file> — visual node tree with agentfmt
- open-pencil find <file> — search by name/type with rich output
- open-pencil export <file> — render to PNG/JPG/WEBP at any scale

Uses canvaskit-wasm/full for headless CPU rasterization.
All commands support --json for machine-readable output.
agentfmt provides consistent colored terminal formatting.
2026-03-01 01:19:24 +03:00

64 lines
2.1 KiB
TypeScript

import { defineCommand } from 'citty'
import { loadDocument } from '../headless'
import { fmtTree, nodeToTreeNode, printError, entity, formatType } from '../format'
import type { SceneNode } from '@open-pencil/core'
export default defineCommand({
meta: { description: 'Print the node tree' },
args: {
file: { type: 'positional', description: '.fig file path', required: true },
page: { type: 'string', description: 'Page name (default: first page)' },
depth: { type: 'string', description: 'Max depth (default: unlimited)' },
json: { type: 'boolean', description: 'Output as JSON' }
},
async run({ args }) {
const graph = await loadDocument(args.file)
const pages = graph.getPages()
const maxDepth = args.depth ? Number(args.depth) : Infinity
const page = args.page
? pages.find((p) => p.name === args.page)
: pages[0]
if (!page) {
printError(`Page "${args.page}" not found. Available: ${pages.map((p) => p.name).join(', ')}`)
process.exit(1)
}
if (args.json) {
const buildJson = (id: string, depth: number): unknown => {
const node = graph.getNode(id)
if (!node) return null
const result: Record<string, unknown> = {
id: node.id,
name: node.name,
type: node.type,
x: Math.round(node.x),
y: Math.round(node.y),
width: Math.round(node.width),
height: Math.round(node.height)
}
if (node.childIds.length > 0 && depth < maxDepth) {
result.children = node.childIds.map((cid) => buildJson(cid, depth + 1)).filter(Boolean)
}
return result
}
console.log(JSON.stringify(page.childIds.map((id) => buildJson(id, 0)), null, 2))
return
}
const root = {
header: entity(formatType(page.type), page.name, page.id),
children: page.childIds
.map((id) => graph.getNode(id))
.filter((n): n is SceneNode => n !== undefined)
.map((child) => nodeToTreeNode(graph, child, maxDepth))
}
console.log('')
console.log(fmtTree(root, { maxDepth }))
console.log('')
}
})