🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@open-pencil/cli

Package Overview
Dependencies
Maintainers
1
Versions
30
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@open-pencil/cli - npm Package Compare versions

Comparing version
0.11.8
to
0.12.0
+14
src/rpc-data.ts
import { executeRpcCommand } from '@open-pencil/core/rpc'
import { isAppMode, requireFile, rpc } from '#cli/app-client'
import { loadDocument } from '#cli/headless'
export async function loadRpcData<Result>(
file: string | undefined,
command: string,
args?: unknown
): Promise<Result> {
if (isAppMode(file)) return rpc<Result>(command, args)
const graph = await loadDocument(requireFile(file))
return executeRpcCommand(graph, command, args) as Result
}
+5
-2
{
"name": "@open-pencil/cli",
"version": "0.11.8",
"version": "0.12.0",
"license": "MIT",
"type": "module",
"imports": {
"#cli/*": "./src/*"
},
"bin": {

@@ -18,3 +21,3 @@ "openpencil": "./src/index.ts"

"dependencies": {
"@open-pencil/core": "^0.11.8",
"@open-pencil/core": "^0.12.0",
"agentfmt": "^0.1.3",

@@ -21,0 +24,0 @@ "canvaskit-wasm": "^0.40.0",

@@ -1,2 +0,2 @@

import { AUTOMATION_HTTP_PORT } from '@open-pencil/core'
import { AUTOMATION_HTTP_PORT } from '@open-pencil/core/constants'

@@ -3,0 +3,0 @@ const HEALTH_URL = `http://127.0.0.1:${AUTOMATION_HTTP_PORT}/health`

import { defineCommand } from 'citty'
import { executeRpcCommand, calcClusterConfidence } from '@open-pencil/core'
import type { AnalyzeClustersResult } from '@open-pencil/core/rpc'
import { calcClusterConfidence } from '@open-pencil/core/tools'
import { isAppMode, requireFile, rpc } from '../../app-client'
import { bold, fmtList, fmtSummary } from '../../format'
import { loadDocument } from '../../headless'
import { bold, fmtList, fmtSummary } from '#cli/format'
import { loadRpcData } from '#cli/rpc-data'
import type { AnalyzeClustersResult } from '@open-pencil/core'
function formatSignature(sig: string): string {

@@ -28,16 +26,2 @@ const [typeSize, children] = sig.split('|')

async function getData(
file: string | undefined,
args: { limit?: string; 'min-size'?: string; 'min-count'?: string }
): Promise<AnalyzeClustersResult> {
const rpcArgs = {
limit: Number(args.limit ?? 20),
minSize: Number(args['min-size'] ?? 30),
minCount: Number(args['min-count'] ?? 2)
}
if (isAppMode(file)) return rpc<AnalyzeClustersResult>('analyze_clusters', rpcArgs)
const graph = await loadDocument(requireFile(file))
return executeRpcCommand(graph, 'analyze_clusters', rpcArgs) as AnalyzeClustersResult
}
export default defineCommand({

@@ -57,3 +41,7 @@ meta: { description: 'Find repeated design patterns (potential components)' },

async run({ args }) {
const data = await getData(args.file, args)
const data = await loadRpcData<AnalyzeClustersResult>(args.file, 'analyze_clusters', {
limit: Number(args.limit),
minSize: Number(args['min-size']),
minCount: Number(args['min-count'])
})

@@ -60,0 +48,0 @@ if (args.json) {

import { defineCommand } from 'citty'
import { executeRpcCommand } from '@open-pencil/core'
import type { AnalyzeColorsResult } from '@open-pencil/core/rpc'
import { isAppMode, requireFile, rpc } from '../../app-client'
import { bold, fmtHistogram, fmtList, fmtSummary } from '../../format'
import { loadDocument } from '../../headless'
import { bold, fmtHistogram, fmtList, fmtSummary } from '#cli/format'
import { loadRpcData } from '#cli/rpc-data'
import type { AnalyzeColorsResult } from '@open-pencil/core'
async function getData(
file: string | undefined,
args: { threshold?: string; similar?: boolean }
): Promise<AnalyzeColorsResult> {
const rpcArgs = { threshold: Number(args.threshold ?? 15), similar: args.similar }
if (isAppMode(file)) return rpc<AnalyzeColorsResult>('analyze_colors', rpcArgs)
const graph = await loadDocument(requireFile(file))
return executeRpcCommand(graph, 'analyze_colors', rpcArgs) as AnalyzeColorsResult
}
export default defineCommand({

@@ -39,3 +26,6 @@ meta: { description: 'Analyze color palette usage' },

async run({ args }) {
const data = await getData(args.file, args)
const data = await loadRpcData<AnalyzeColorsResult>(args.file, 'analyze_colors', {
threshold: Number(args.threshold),
similar: args.similar
})
const limit = Number(args.limit)

@@ -42,0 +32,0 @@

import { defineCommand } from 'citty'
import { executeRpcCommand } from '@open-pencil/core'
import type { AnalyzeSpacingResult } from '@open-pencil/core/rpc'
import { isAppMode, requireFile, rpc } from '../../app-client'
import { bold, kv, fmtHistogram, fmtSummary } from '../../format'
import { loadDocument } from '../../headless'
import { bold, kv, fmtHistogram, fmtSummary } from '#cli/format'
import { loadRpcData } from '#cli/rpc-data'
import type { AnalyzeSpacingResult } from '@open-pencil/core'
async function getData(file?: string): Promise<AnalyzeSpacingResult> {
if (isAppMode(file)) return rpc<AnalyzeSpacingResult>('analyze_spacing')
const graph = await loadDocument(requireFile(file))
return executeRpcCommand(graph, 'analyze_spacing', undefined) as AnalyzeSpacingResult
}
export default defineCommand({

@@ -29,3 +20,3 @@ meta: { description: 'Analyze spacing values (gap, padding)' },

async run({ args }) {
const data = await getData(args.file)
const data = await loadRpcData<AnalyzeSpacingResult>(args.file, 'analyze_spacing')
const gridSize = Number(args.grid)

@@ -32,0 +23,0 @@

import { defineCommand } from 'citty'
import { executeRpcCommand } from '@open-pencil/core'
import type { AnalyzeTypographyResult } from '@open-pencil/core/rpc'
import { isAppMode, requireFile, rpc } from '../../app-client'
import { bold, fmtHistogram, fmtSummary } from '../../format'
import { loadDocument } from '../../headless'
import { bold, fmtHistogram, fmtSummary } from '#cli/format'
import { loadRpcData } from '#cli/rpc-data'
import type { AnalyzeTypographyResult } from '@open-pencil/core'
function weightName(w: number): string {

@@ -23,8 +20,2 @@ if (w <= 100) return 'Thin'

async function getData(file?: string): Promise<AnalyzeTypographyResult> {
if (isAppMode(file)) return rpc<AnalyzeTypographyResult>('analyze_typography')
const graph = await loadDocument(requireFile(file))
return executeRpcCommand(graph, 'analyze_typography', {}) as AnalyzeTypographyResult
}
export default defineCommand({

@@ -46,3 +37,3 @@ meta: { description: 'Analyze typography usage' },

async run({ args }) {
const data = await getData(args.file)
const data = await loadRpcData<AnalyzeTypographyResult>(args.file, 'analyze_typography', {})
const limit = Number(args.limit)

@@ -49,0 +40,0 @@ const groupBy = args['group-by']

@@ -5,14 +5,15 @@ import { basename, extname, resolve } from 'node:path'

import { BUILTIN_IO_FORMATS, IORegistry } from '@open-pencil/core'
import { BUILTIN_IO_FORMATS, IORegistry } from '@open-pencil/core/io'
import { requireFile } from '../app-client'
import { ok, printError } from '../format'
import { loadDocument } from '../headless'
import { requireFile } from '#cli/app-client'
import { ok, printError } from '#cli/format'
import { loadDocument } from '#cli/headless'
const io = new IORegistry(BUILTIN_IO_FORMATS)
const WRITABLE_FORMATS = ['FIG'] as const
type WritableFormat = (typeof WRITABLE_FORMATS)[number]
function writableFormatIds(): string[] {
return io.listWritableFormats().map((format) => format.id)
}
function defaultOutput(file: string, format: WritableFormat): string {
function defaultOutput(file: string, format: string): string {
const base = basename(file, extname(file))

@@ -44,5 +45,6 @@ return resolve(`${base}.${format.toLowerCase()}`)

async run({ args }) {
const format = args.format.toUpperCase() as WritableFormat
if (!WRITABLE_FORMATS.includes(format)) {
printError(`Invalid format "${args.format}". Use fig.`)
const format = args.format.toLowerCase()
const writableFormats = writableFormatIds()
if (!writableFormats.includes(format)) {
printError(`Invalid format "${args.format}". Use ${writableFormats.join(', ')}.`)
process.exit(1)

@@ -53,3 +55,3 @@ }

const graph = await loadDocument(file)
const result = await io.writeDocument(format.toLowerCase(), graph)
const result = await io.writeDocument(format, graph)
const output = args.output ? resolve(args.output) : defaultOutput(file, format)

@@ -56,0 +58,0 @@ await Bun.write(output, result.data as Uint8Array)

import { defineCommand } from 'citty'
import { FigmaAPI } from '@open-pencil/core'
import { FigmaAPI } from '@open-pencil/core/figma-api'
import { isAppMode, requireFile, rpc } from '../app-client'
import { printError } from '../format'
import { loadDocument } from '../headless'
import { isAppMode, requireFile, rpc } from '#cli/app-client'
import { printError } from '#cli/format'
import { loadDocument } from '#cli/headless'

@@ -72,4 +72,7 @@ function printResult(value: unknown, json: boolean) {

// eslint-disable-next-line no-empty-function -- needed to get AsyncFunction constructor
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor
type AsyncFunctionConstructor = new (
...args: string[]
) => (...args: unknown[]) => Promise<unknown>
const AsyncFunction = Object.getPrototypeOf(async () => undefined)
.constructor as AsyncFunctionConstructor
const wrappedCode = code.trim().startsWith('return')

@@ -93,3 +96,3 @@ ? code

if (args.write || args.output) {
const { BUILTIN_IO_FORMATS, IORegistry } = await import('@open-pencil/core')
const { BUILTIN_IO_FORMATS, IORegistry } = await import('@open-pencil/core/io')
const io = new IORegistry(BUILTIN_IO_FORMATS)

@@ -96,0 +99,0 @@ const outPath = args.output ? args.output : file

@@ -5,14 +5,13 @@ import { basename, extname, resolve } from 'node:path'

import { BUILTIN_IO_FORMATS, IORegistry } from '@open-pencil/core'
import { BUILTIN_IO_FORMATS, IORegistry } from '@open-pencil/core/io'
import type { RasterExportFormat } from '@open-pencil/core/io'
import { isAppMode, requireFile, rpc } from '../app-client'
import { ok, printError } from '../format'
import { loadDocument } from '../headless'
import { isAppMode, requireFile, rpc } from '#cli/app-client'
import { ok, printError } from '#cli/format'
import { loadDocument } from '#cli/headless'
import type { RasterExportFormat } from '@open-pencil/core'
const io = new IORegistry(BUILTIN_IO_FORMATS)
const RASTER_FORMATS = ['PNG', 'JPG', 'WEBP']
const ALL_FORMATS = [...RASTER_FORMATS, 'SVG', 'JSX', 'FIG']
const JSX_STYLES = ['openpencil', 'tailwind']
const ALL_FORMATS = new Set([...RASTER_FORMATS, 'SVG', 'PDF', 'JSX', 'FIG'])
const JSX_STYLES = new Set(['openpencil', 'tailwind'])

@@ -53,2 +52,16 @@ interface ExportArgs {

if (format === 'PDF') {
const result = await rpc<{ base64: string }>('tool', {
name: 'export_pdf',
args: { ids: args.node ? [args.node] : undefined }
})
if (!result.base64) {
printError('Nothing to export.')
process.exit(1)
}
const data = Uint8Array.from(atob(result.base64), (c) => c.charCodeAt(0))
await writeAndLog(resolve(args.output ?? 'export.pdf'), data)
return
}
if (format === 'JSX' || format === 'FIG') {

@@ -136,3 +149,3 @@ printError(`${format} export is only available in file mode right now.`)

export default defineCommand({
meta: { description: 'Export a document to PNG, JPG, WEBP, SVG, JSX, or .fig' },
meta: { description: 'Export a document to PNG, JPG, WEBP, SVG, PDF, JSX, or .fig' },
args: {

@@ -153,3 +166,3 @@ file: {

alias: 'f',
description: 'Export format: png, jpg, webp, svg, jsx, fig (default: png)',
description: 'Export format: png, jpg, webp, svg, pdf, jsx, fig (default: png)',
default: 'png'

@@ -185,8 +198,8 @@ },

const format = args.format.toUpperCase() as RasterExportFormat | 'SVG' | 'JSX' | 'FIG'
if (!ALL_FORMATS.includes(format)) {
printError(`Invalid format "${args.format}". Use png, jpg, webp, svg, jsx, or fig.`)
if (!ALL_FORMATS.has(format)) {
printError(`Invalid format "${args.format}". Use png, jpg, webp, svg, pdf, jsx, or fig.`)
process.exit(1)
}
if (format === 'JSX' && !JSX_STYLES.includes(args.style)) {
if (format === 'JSX' && !JSX_STYLES.has(args.style)) {
printError(`Invalid JSX style "${args.style}". Use openpencil or tailwind.`)

@@ -193,0 +206,0 @@ process.exit(1)

import { defineCommand } from 'citty'
import { executeRpcCommand } from '@open-pencil/core'
import type { FindNodeResult } from '@open-pencil/core/rpc'
import { isAppMode, requireFile, rpc } from '../app-client'
import { printNodeResults } from '../format'
import { loadDocument } from '../headless'
import { printNodeResults } from '#cli/format'
import { loadRpcData } from '#cli/rpc-data'
import type { FindNodeResult } from '@open-pencil/core'
async function getData(
file: string | undefined,
args: { name?: string; type?: string; page?: string; limit?: string }
): Promise<FindNodeResult[]> {
const rpcArgs = {
name: args.name,
type: args.type,
page: args.page,
limit: args.limit ? Number(args.limit) : undefined
}
if (isAppMode(file)) return rpc<FindNodeResult[]>('find', rpcArgs)
const graph = await loadDocument(requireFile(file))
return executeRpcCommand(graph, 'find', rpcArgs) as FindNodeResult[]
}
export default defineCommand({

@@ -41,3 +23,8 @@ meta: { description: 'Find nodes by name or type' },

async run({ args }) {
const results = await getData(args.file, args)
const results = await loadRpcData<FindNodeResult[]>(args.file, 'find', {
name: args.name,
type: args.type,
page: args.page,
limit: args.limit ? Number(args.limit) : undefined
})

@@ -44,0 +31,0 @@ if (args.json) {

import { defineCommand } from 'citty'
import { BUILTIN_IO_FORMATS, IORegistry } from '@open-pencil/core'
import { BUILTIN_IO_FORMATS, IORegistry } from '@open-pencil/core/io'
import { bold, fmtList, kv } from '../format'
import { bold, fmtList, kv } from '#cli/format'

@@ -7,0 +7,0 @@ const io = new IORegistry(BUILTIN_IO_FORMATS)

import { defineCommand } from 'citty'
import { executeRpcCommand } from '@open-pencil/core'
import type { InfoResult } from '@open-pencil/core/rpc'
import { isAppMode, requireFile, rpc } from '../app-client'
import { bold, fmtHistogram, fmtSummary, kv } from '../format'
import { loadDocument } from '../headless'
import { bold, fmtHistogram, fmtSummary, kv } from '#cli/format'
import { loadRpcData } from '#cli/rpc-data'
import type { InfoResult } from '@open-pencil/core'
async function getData(file?: string): Promise<InfoResult> {
if (isAppMode(file)) return rpc<InfoResult>('info')
const graph = await loadDocument(requireFile(file))
return executeRpcCommand(graph, 'info', undefined) as InfoResult
}
export default defineCommand({

@@ -28,3 +19,3 @@ meta: { description: 'Show document info (pages, node counts, fonts)' },

async run({ args }) {
const data = await getData(args.file)
const data = await loadRpcData<InfoResult>(args.file, 'info')

@@ -31,0 +22,0 @@ if (args.json) {

import { defineCommand } from 'citty'
import { allRules, createLinter, presets, type LintMessage } from '@open-pencil/core'
import { allRules, createLinter, presets, type LintMessage } from '@open-pencil/core/lint'
import { bold, dim, fail, fmtList, ok } from '../format'
import { loadDocument } from '../headless'
import { bold, dim, fail, fmtList, ok } from '#cli/format'
import { loadDocument } from '#cli/headless'

@@ -8,0 +8,0 @@ function formatSeverity(severity: LintMessage['severity']) {

import { defineCommand } from 'citty'
import { executeRpcCommand, colorToHex } from '@open-pencil/core'
import { colorToHex } from '@open-pencil/core/color'
import type { NodeResult } from '@open-pencil/core/rpc'
import type { Color } from '@open-pencil/core/types'
import { isAppMode, requireFile, rpc } from '../app-client'
import { fmtNode, printError, formatType } from '../format'
import { loadDocument } from '../headless'
import { fmtNode, printError, formatType } from '#cli/format'
import { loadRpcData } from '#cli/rpc-data'
import type { Color, NodeResult } from '@open-pencil/core'
async function getData(
file: string | undefined,
id: string
): Promise<NodeResult | { error: string }> {
if (isAppMode(file)) return rpc<NodeResult>('node', { id })
const graph = await loadDocument(requireFile(file))
return executeRpcCommand(graph, 'node', { id }) as NodeResult | { error: string }
}
export default defineCommand({

@@ -32,3 +22,5 @@ meta: { description: 'Show detailed node properties by ID' },

async run({ args }) {
const data = await getData(args.file, args.id)
const data = await loadRpcData<NodeResult | { error: string }>(args.file, 'node', {
id: args.id
})

@@ -35,0 +27,0 @@ if ('error' in data) {

import { defineCommand } from 'citty'
import { executeRpcCommand } from '@open-pencil/core'
import type { PageItem } from '@open-pencil/core/rpc'
import { isAppMode, requireFile, rpc } from '../app-client'
import { bold, fmtList, entity } from '../format'
import { loadDocument } from '../headless'
import { bold, fmtList, entity } from '#cli/format'
import { loadRpcData } from '#cli/rpc-data'
import type { PageItem } from '@open-pencil/core'
async function getData(file?: string): Promise<PageItem[]> {
if (isAppMode(file)) return rpc<PageItem[]>('pages')
const graph = await loadDocument(requireFile(file))
return executeRpcCommand(graph, 'pages', undefined) as PageItem[]
}
export default defineCommand({

@@ -28,3 +19,3 @@ meta: { description: 'List pages in a document' },

async run({ args }) {
const pages = await getData(args.file)
const pages = await loadRpcData<PageItem[]>(args.file, 'pages')

@@ -31,0 +22,0 @@ if (args.json) {

import { defineCommand } from 'citty'
import { executeRpcCommand } from '@open-pencil/core'
import type { QueryNodeResult } from '@open-pencil/core/rpc'
import { isAppMode, requireFile, rpc } from '../app-client'
import { printNodeResults, printError } from '../format'
import { loadDocument } from '../headless'
import { printNodeResults, printError } from '#cli/format'
import { loadRpcData } from '#cli/rpc-data'
import type { QueryNodeResult } from '@open-pencil/core'
async function getData(
file: string | undefined,
args: { selector: string; page?: string; limit?: string }
): Promise<QueryNodeResult[] | { error: string }> {
const rpcArgs = {
selector: args.selector,
page: args.page,
limit: args.limit ? Number(args.limit) : undefined
}
if (isAppMode(file)) return rpc<QueryNodeResult[]>('query', rpcArgs)
const graph = await loadDocument(requireFile(file))
return (await executeRpcCommand(graph, 'query', rpcArgs)) as QueryNodeResult[] | { error: string }
}
export default defineCommand({

@@ -45,6 +28,6 @@ meta: {

async run({ args }) {
const results = await getData(args.file, {
const results = await loadRpcData<QueryNodeResult[] | { error: string }>(args.file, 'query', {
selector: args.selector,
page: args.page,
limit: args.limit
limit: args.limit ? Number(args.limit) : undefined
})

@@ -51,0 +34,0 @@

import { defineCommand } from 'citty'
import { rpc } from '../app-client'
import { bold, entity, fmtList, formatType, printError } from '../format'
import { rpc } from '#cli/app-client'
import { bold, entity, fmtList, formatType, printError } from '#cli/format'

@@ -6,0 +6,0 @@ interface SelectionNode {

@@ -0,12 +1,9 @@

import type { TreeNode } from 'agentfmt'
import { defineCommand } from 'citty'
import { executeRpcCommand } from '@open-pencil/core'
import type { TreeNodeResult, TreeResult } from '@open-pencil/core/rpc'
import { isAppMode, requireFile, rpc } from '../app-client'
import { fmtTree, printError, entity, formatType } from '../format'
import { loadDocument } from '../headless'
import { fmtTree, printError, entity, formatType } from '#cli/format'
import { loadRpcData } from '#cli/rpc-data'
import type { TreeResult, TreeNodeResult } from '@open-pencil/core'
import type { TreeNode } from 'agentfmt'
function toAgentfmtTree(node: TreeNodeResult, maxDepth: number, depth = 0): TreeNode {

@@ -22,12 +19,2 @@ const treeNode: TreeNode = {

async function getData(
file: string | undefined,
args: { page?: string; depth?: string }
): Promise<TreeResult | { error: string }> {
const rpcArgs = { page: args.page, depth: args.depth ? Number(args.depth) : undefined }
if (isAppMode(file)) return rpc<TreeResult>('tree', rpcArgs)
const graph = await loadDocument(requireFile(file))
return executeRpcCommand(graph, 'tree', rpcArgs) as TreeResult | { error: string }
}
export default defineCommand({

@@ -46,3 +33,6 @@ meta: { description: 'Print the node tree' },

async run({ args }) {
const data = await getData(args.file, args)
const data = await loadRpcData<TreeResult | { error: string }>(args.file, 'tree', {
page: args.page,
depth: args.depth ? Number(args.depth) : undefined
})
const maxDepth = args.depth ? Number(args.depth) : Infinity

@@ -49,0 +39,0 @@

import { defineCommand } from 'citty'
import { executeRpcCommand } from '@open-pencil/core'
import type { VariablesResult } from '@open-pencil/core/rpc'
import { isAppMode, requireFile, rpc } from '../app-client'
import { bold, entity, fmtList, fmtSummary } from '../format'
import { loadDocument } from '../headless'
import { bold, entity, fmtList, fmtSummary } from '#cli/format'
import { loadRpcData } from '#cli/rpc-data'
import type { VariablesResult } from '@open-pencil/core'
async function getData(
file: string | undefined,
args: { collection?: string; type?: string }
): Promise<VariablesResult> {
const rpcArgs = { collection: args.collection, type: args.type }
if (isAppMode(file)) return rpc<VariablesResult>('variables', rpcArgs)
const graph = await loadDocument(requireFile(file))
return executeRpcCommand(graph, 'variables', rpcArgs) as VariablesResult
}
export default defineCommand({

@@ -34,3 +21,6 @@ meta: { description: 'List design variables and collections' },

async run({ args }) {
const data = await getData(args.file, args)
const data = await loadRpcData<VariablesResult>(args.file, 'variables', {
collection: args.collection,
type: args.type
})

@@ -37,0 +27,0 @@ if (data.totalVariables === 0) {

@@ -16,6 +16,6 @@ import {

} from 'agentfmt'
import type { SceneNode, SceneGraph } from '@open-pencil/core'
import type { TreeNode, ListItem, NodeData } from 'agentfmt'
import type { SceneGraph, SceneNode } from '@open-pencil/core/scene-graph'
export {

@@ -22,0 +22,0 @@ ok,

@@ -1,8 +0,4 @@

import {
BUILTIN_IO_FORMATS,
IORegistry,
computeAllLayouts,
initCanvasKit,
type SceneGraph
} from '@open-pencil/core'
import { BUILTIN_IO_FORMATS, IORegistry, initCanvasKit } from '@open-pencil/core/io'
import { computeAllLayouts } from '@open-pencil/core/layout'
import type { SceneGraph } from '@open-pencil/core/scene-graph'

@@ -9,0 +5,0 @@ export { initCanvasKit }