Token导航 LogoToken导航

Agent Skill · 开发

unlayer-custom-tools(取消分层自定义工具)

unlayer-custom-tools 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要围绕仓库状态、代码变更或协作事项进行整理时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

unlayer-custom-tools 是开发类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为需要联网;许可证信息还不明确,第三方扫描已通过,按安装量在开发类的 21,746 个 Skill 里排第 11,708。

总安装 1,623周安装 69GitHub Stars 5下载 569核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
unknown

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:unlayer-custom-tools(取消分层自定义工具)
来源仓库:https://github.com/unlayer/unlayer-skills
仓库路径:skills/unlayer-custom-tools
安装命令:
npx skills add https://github.com/unlayer/unlayer-skills --skill unlayer-custom-tools
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/unlayer/unlayer-skills --skill unlayer-custom-tools

这个 Skill 是做什么的

开发类 Skill 面向写代码、改代码和排查问题,一般会读写仓库文件,部分还会执行构建或测试命令。

取消分层自定义工具技能用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息。适合在开发环境中定制和集成特定功能。通过 npx skills add 命令从 unlayer-skills 仓库安装。需评估自定义工具的稳定性和兼容性。

unlayer-custom-tools 属于开发类 Skill,可作为该场景下的辅助能力补充。

来源仓库内路径:skills/unlayer-custom-tools

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/unlayer/unlayer-skills --skill unlayer-custom-…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要需要联网,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

unlayer-custom-tools 怎么安装?

在终端执行 npx skills add https://github.com/unlayer/unlayer-skills --skill unlayer-custom-tools。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

unlayer-custom-tools 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

unlayer-custom-tools 需要什么权限?

权限级别是需要联网。该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

unlayer-custom-tools 是免费的吗?

公开资料没有标注明确许可证,收费和商用限制都无法确认。正式使用前请到来源仓库核对授权说明。

unlayer-custom-tools 用的人多吗?

公开数据是累计安装 1,623 次,近一周安装 69 次,下载 569 次,GitHub 5 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

unlayer-custom-tools 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 第三方扫描已通过,常见的高风险行为没有命中。

先想清楚的地方

  • 许可证没有明确标注,能不能商用、能不能再分发都不确定。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理开发类任务、又不想每次都手写提示词的人。

不太适合

  • 需要明确授权才能用到公司项目里的场景。
  • 需要厂商级技术支持和 SLA 的生产系统。

开发类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
skill-vetter

技能审查员 · OpenClaw

8,779,018986操作浏览器MIT-0通过
ontology

本体论 · OpenClaw

6,636,960576只读MIT-0通过
entra-app-registration

内部应用程序注册 · Codex、Claude

6,631,872764接触敏感数据未明确通过
entra-app-registration

内部应用程序注册 · Codex、Claude

3,757,680201接触敏感数据未明确通过
nano-banana-pro

纳米香蕉专业版 · OpenClaw

3,526,898364接触敏感数据MIT-0有提醒
unlayer-custom-tools

取消分层自定义工具 · 本页

1,6235需要联网未明确通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

Build Custom Tools

Overview

Custom tools are drag-and-drop content blocks you create for the Unlayer editor. Each tool needs:

  • A renderer (what users see in the editor)
  • Exporters (HTML output — must be table-based for email)
  • Property editors (the settings panel)

Complete Example: Product Card

This is a fully working custom tool with an image, title, price, and buy button:

unlayer.registerTool({
  name: 'product_card',
  label: 'Product Card',
  icon: 'fa-shopping-cart',
  supportedDisplayModes: ['web', 'email'],

  options: {
    content: {
      title: 'Content',
      position: 1,
      options: {
        productTitle: {
          label: 'Product Title',
          defaultValue: 'Product Name',
          widget: 'text',               // → values.productTitle = 'Product Name'
        },
        productImage: {
          label: 'Image',
          defaultValue: { url: 'https://via.placeholder.com/300x200' },
          widget: 'image',              // → values.productImage.url = 'https://...'
        },
        price: {
          label: 'Price',
          defaultValue: '$99.99',
          widget: 'text',               // → values.price = '$99.99'
        },
        buttonText: {
          label: 'Button Text',
          defaultValue: 'Buy Now',
          widget: 'text',
        },
        buttonLink: {
          label: 'Button Link',
          defaultValue: { name: 'web', values: { href: 'https://example.com', target: '_blank' } },
          widget: 'link',               // → values.buttonLink.values.href = 'https://...'
        },
      },
    },
    colors: {
      title: 'Colors',
      position: 2,
      options: {
        titleColor: {
          label: 'Title Color',
          defaultValue: '#333333',
          widget: 'color_picker',       // → values.titleColor = '#333333'
        },
        buttonBg: {
          label: 'Button Background',
          defaultValue: '#007bff',
          widget: 'color_picker',
        },
      },
    },
  },

  values: {},

  renderer: {
    Viewer: unlayer.createViewer({
      render(values) {
        return `
          <div style="text-align: center; padding: 20px;">
            <img src="${values.productImage.url}" style="max-width: 100%;" />
            <h3 style="color: ${values.titleColor};">${values.productTitle}</h3>
            <p style="font-size: 24px; font-weight: bold;">${values.price}</p>
            <a href="${values.buttonLink.values.href}"
               style="display: inline-block; background: ${values.buttonBg};
                      color: #fff; padding: 12px 24px; text-decoration: none;
                      border-radius: 4px;">
              ${values.buttonText}
            </a>
          </div>
        `;
      },
    }),

    exporters: {
      web(values) {
        return `
          <div style="text-align: center; padding: 20px;">
            <img src="${values.productImage.url}" alt="${values.productTitle}" style="max-width: 100%;" />
            <h3 style="color: ${values.titleColor};">${values.productTitle}</h3>
            <p style="font-size: 24px; font-weight: bold;">${values.price}</p>
            <a href="${values.buttonLink.values.href}" target="${values.buttonLink.values.target}"
               style="display: inline-block; background: ${values.buttonBg};
                      color: #fff; padding: 12px 24px; text-decoration: none;">
              ${values.buttonText}
            </a>
          </div>
        `;
      },
      email(values) {
        // Email MUST use tables — divs break in Outlook/Gmail
        return `
          <table width="100%" cellpadding="0" cellspacing="0" border="0">
            <tr><td align="center" style="padding: 20px;">
              <img src="${values.productImage.url}" alt="${values.productTitle}"
                   style="max-width: 100%; display: block;" />
            </td></tr>
            <tr><td align="center" style="padding: 10px;">
              <h3 style="color: ${values.titleColor}; margin: 0;">${values.productTitle}</h3>
            </td></tr>
            <tr><td align="center">
              <p style="font-size: 24px; font-weight: bold; margin: 5px 0;">${values.price}</p>
            </td></tr>
            <tr><td align="center" style="padding: 15px;">
              <table cellpadding="0" cellspacing="0" border="0">
                <tr><td style="background: ${values.buttonBg}; border-radius: 4px;">
                  <a href="${values.buttonLink.values.href}" target="${values.buttonLink.values.target}"
                     style="display: inline-block; color: #fff; padding: 12px 24px;
                            text-decoration: none;">
                    ${values.buttonText}
                  </a>
                </td></tr>
              </table>
            </td></tr>
          </table>
        `;
      },
    },

    head: {
      css(values) {
        return `#${values._meta.htmlID} img { max-width: 100%; height: auto; }`;
      },
      js(values) { return ''; },
    },
  },

  validator(data) {
    const { values, defaultErrors } = data;
    const errors = [];
    if (!values.productTitle) {
      errors.push({
        id: 'PRODUCT_TITLE_REQUIRED',
        icon: 'fa-warning',
        severity: 'ERROR',
        title: 'Missing product title',
        description: 'Product title is required',
      });
    }
    if (!values.productImage?.url) {
      errors.push({
        id: 'PRODUCT_IMAGE_REQUIRED',
        icon: 'fa-warning',
        severity: 'ERROR',
        title: 'Missing product image',
        description: 'Product image is required',
      });
    }
    return [...errors, ...defaultErrors];
  },
});

Register it at init time with the custom# prefix:

unlayer.init({
  tools: {
    'custom#product_card': {          // REQUIRED: custom# prefix
      data: {
        apiEndpoint: '/api/products', // Custom data accessible in renderer
      },
      properties: {
        // Override default property values or dropdown options
      },
    },
  },
});

Widget Value Access Reference

How to read each widget type's value in your renderer:

WidgetDefault ValueAccess in render(values)
text'Hello'values.myField → 'Hello'
rich_text'<p>Hello</p>'values.myField → '<p>Hello</p>'
html'<div>...</div>'values.myField → '<div>...</div>'
color_picker'#FF0000'values.myField → '#FF0000'
alignment'center'values.myField → 'center'
font_family{label:'Arial', value:'arial'}values.myField.value → 'arial'
image{url: 'https://...'}values.myField.url → 'https://...'
togglefalsevalues.myField → false
link{name:'web', values:{href,target}}values.myField.values.href → 'https://...'
counter'10'values.myField → '10' (string!)
dropdown'option1'values.myField → 'option1'
datetime'2025-01-01'values.myField → '2025-01-01'
border{borderTopWidth:'1px',...}values.myField.borderTopWidth → '1px'

Dropdown options — pass via unlayer.init() under the tool's properties config:

unlayer.init({
  tools: {
    'custom#product_card': {
      properties: {
        department: {
          editor: {
            data: {
              options: [
                { label: 'Sales', value: 'sales' },
                { label: 'Support', value: 'support' },
              ],
            },
          },
        },
      },
    },
  },
});

Custom Property Editor (React)

For controls beyond built-in widgets:

const RangeSlider = ({ label, value, updateValue, data }) => (
  <div>
    <label>{label}: {value}px</label>
    <input
      type="range"
      min={data.min || 0}
      max={data.max || 100}
      value={parseInt(value)}
      onChange={(e) => updateValue(e.target.value + 'px')}
    />
  </div>
);

unlayer.registerPropertyEditor({
  name: 'range_slider',
  Widget: RangeSlider,
});

// Use in your tool:
borderRadius: {
  label: 'Corner Radius',
  defaultValue: '4px',
  widget: 'range_slider',
  data: { min: 0, max: 50 },
},

Validator Return Format

Each error must include id, icon, severity, title, and description:

validator(data) {
  const { values, defaultErrors } = data;
  const errors = [];

  if (!values.productTitle) {
    errors.push({
      id: 'PRODUCT_TITLE_REQUIRED',     // Unique error ID
      icon: 'fa-warning',                // FontAwesome icon
      severity: 'ERROR',                 // 'ERROR' | 'WARNING'
      title: 'Missing product title',    // Short label
      description: 'Product title is required',  // Detailed message
    });
  }

  if (values.price && !values.price.startsWith('$')) {
    errors.push({
      id: 'PRICE_MISSING_CURRENCY',
      icon: 'fa-dollar-sign',
      severity: 'WARNING',
      title: 'Missing currency symbol',
      description: 'Price should include currency symbol',
      labelPath: 'price',               // Optional — highlights the property in the panel
    });
  }

  return [...errors, ...defaultErrors];  // Merge with built-in errors
}

Email-Safe HTML Patterns

Email clients (Outlook, Gmail) require table-based HTML. Copy-paste these patterns:

Button:

<table cellpadding="0" cellspacing="0" border="0">
  <tr><td style="background: #007bff; border-radius: 4px;">
    <a href="URL" style="display: inline-block; color: #fff; padding: 12px 24px; text-decoration: none;">
      Button Text
    </a>
  </td></tr>
</table>

Two columns:

<table width="100%" cellpadding="0" cellspacing="0">
  <tr>
    <td width="50%" valign="top" style="padding: 10px;">Left</td>
    <td width="50%" valign="top" style="padding: 10px;">Right</td>
  </tr>
</table>

Safe CSS properties: color, background-color, font-size, font-family, font-weight, text-align, padding, margin, border, width, max-width, display: block/inline-block.

Unsafe (avoid in email): flexbox, grid, position, float, box-shadow, border-radius (partial support), calc().


Common Mistakes

MistakeFix
Missing custom# prefixTools MUST use custom#my_tool in tools config at init
Div-based email exporterEmail exporters MUST return table-based HTML
Forgetting _meta.htmlIDScope CSS: #${values._meta.htmlID} {...}
Hardcoded values in rendererUse values object — let property editors drive content
Wrong dropdown options formatPass options via unlayer.init() under tools['custom#name'].properties.prop.editor.data.options

Troubleshooting

ProblemFix
Tool doesn't appear in editorCheck supportedDisplayModes includes current mode
Properties panel is emptyCheck options structure — needs group → options nesting
Custom editor doesn't updateEnsure updateValue() is called with the new value
Exported HTML looks differentCheck both Viewer.render() and exporters.email/web()

Resources

平台分布

Codex

37.07%
按下载量换算211

Claude

29.28%
按下载量换算167

Cursor

20.71%
按下载量换算118

Gemini CLI

9.39%
按下载量换算53

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。目前只有一个公开来源,建议同时核对源仓库。

继续浏览同类 Skills