Token导航 LogoToken导航

Agent Skill · 开发

client-scripts(客户端脚本)

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

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

总安装 212周安装 9GitHub Stars 16下载 74核验 2026-05-01

安装说明

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

安装方式

通过对话安装

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

请帮我安装这个 Agent Skill:client-scripts(客户端脚本)
来源仓库:https://github.com/unityappsuite/frappe-claude
仓库路径:skills/client-scripts
安装命令:
npx skills add https://github.com/unityappsuite/frappe-claude --skill client-scripts
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

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

skills.shnpx skills
npx skills add https://github.com/unityappsuite/frappe-claude --skill client-scripts

这个 Skill 是做什么的

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

client-scripts 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要围绕仓库状态、代码变更或协作事项进行整理时使用。

适用于 UnityAppSuite Frappe Claude 项目的客户端脚本管理、代码协作和问题跟踪等场景。通过 GitHub 仓库安装,使用 npx skills add 命令添加指定技能。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

适用宿主包括 Codex、Claude、Cursor、Gemini CLI,接入前应确认版本、权限和运行环境要求。

来源仓库内路径:skills/client-scripts

上手步骤

4 步
  1. 1

    确认自己的宿主工具

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

  2. 2

    拿到安装命令

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

  3. 3

    安装后重启宿主

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

  4. 4

    按最小权限试用

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

常见问题

6 条

client-scripts 怎么安装?

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

client-scripts 支持哪些 AI 工具?

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

client-scripts 需要什么权限?

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

client-scripts 是免费的吗?

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

client-scripts 用的人多吗?

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

client-scripts 安全吗?

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

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

值得选的地方

  • 同时适配 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有提醒
client-scripts

客户端脚本 · 本页

21216需要联网未明确通过

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

SKILL.md 原文

Frappe Client Scripts Reference

Complete reference for client-side JavaScript development in Frappe Framework.

When to Use This Skill

  • Writing form scripts (refresh, validate, field events)
  • Manipulating form fields (show/hide, require, read-only)
  • Creating dialogs and prompts
  • Making API calls from client
  • Customizing list views
  • Adding custom buttons
  • Handling child table events

Form Script Location

my_app/
└── my_module/
    └── doctype/
        └── my_doctype/
            └── my_doctype.js    # Client script

Form Events

Complete Event Reference

frappe.ui.form.on('My DocType', {
    // === LOAD EVENTS ===

    setup: function(frm) {
        // Called once when form is created (before data loads)
        // Use for: setting queries, initializing variables
        frm.set_query('customer', () => ({ filters: { status: 'Active' } }));
    },

    onload: function(frm) {
        // Called when form data is loaded (before refresh)
        // Use for: setting defaults for new docs
        if (frm.is_new()) {
            frm.set_value('posting_date', frappe.datetime.nowdate());
        }
    },

    onload_post_render: function(frm) {
        // Called after form is rendered
        // Use for: DOM manipulation, focus setting
        frm.get_field('customer').focus();
    },

    refresh: function(frm) {
        // Called every time form refreshes
        // Use for: custom buttons, field toggles, indicators
        if (!frm.is_new()) {
            frm.add_custom_button(__('Action'), () => do_action(frm));
        }
        frm.toggle_display('section_name', frm.doc.show_section);
    },

    // === SAVE EVENTS ===

    validate: function(frm) {
        // Called before save - return false to prevent
        if (frm.doc.end_date < frm.doc.start_date) {
            frappe.msgprint(__('End Date cannot be before Start Date'));
            return false;
        }
    },

    before_save: function(frm) {
        // Called after validate, before server request
        frm.doc.last_updated_by = frappe.session.user;
    },

    after_save: function(frm) {
        // Called after successful save
        frappe.show_alert({
            message: __('Saved successfully'),
            indicator: 'green'
        });
    },

    // === WORKFLOW EVENTS ===

    before_submit: function(frm) {
        // Called before document submission
    },

    on_submit: function(frm) {
        // Called after successful submission
    },

    before_cancel: function(frm) {
        // Called before cancellation
    },

    after_cancel: function(frm) {
        // Called after cancellation
    },

    // === FIELD EVENTS ===

    customer: function(frm) {
        // Called when 'customer' field changes
        if (frm.doc.customer) {
            fetch_customer_details(frm);
        }
    },

    posting_date: function(frm) {
        // Called when 'posting_date' field changes
        calculate_due_date(frm);
    }
});

Field Manipulation

Display Properties

// Show/hide field
frm.toggle_display('fieldname', true);  // Show
frm.toggle_display('fieldname', false); // Hide
frm.toggle_display(['field1', 'field2'], condition);

// Set read-only
frm.set_df_property('fieldname', 'read_only', 1);
frm.toggle_enable('fieldname', false);  // Disable

// Set required
frm.set_df_property('fieldname', 'reqd', 1);
frm.toggle_reqd('fieldname', true);
frm.toggle_reqd(['field1', 'field2'], condition);

// Set hidden
frm.set_df_property('fieldname', 'hidden', 1);

// Change label
frm.set_df_property('fieldname', 'label', 'New Label');

// Change description
frm.set_df_property('fieldname', 'description', 'Help text');

// Change options (for Select)
frm.set_df_property('fieldname', 'options', 'Option1\nOption2\nOption3');

// Refresh after changes
frm.refresh_field('fieldname');
frm.refresh_fields();

Set Values

// Set single value
frm.set_value('fieldname', value);

// Set multiple values
frm.set_value({
    'field1': 'value1',
    'field2': 'value2',
    'field3': 'value3'
});

// Set with callback
frm.set_value('fieldname', value).then(() => {
    // After value is set
});

// Clear field
frm.set_value('fieldname', null);
frm.set_value('fieldname', '');

// Set default value
frm.set_df_property('fieldname', 'default', 'default_value');

Link Field Queries

// Basic filter
frm.set_query('customer', function() {
    return {
        filters: {
            status: 'Active',
            customer_type: 'Company'
        }
    };
});

// Dynamic filter based on form values
frm.set_query('item_code', function() {
    return {
        filters: {
            item_group: frm.doc.item_group,
            is_stock_item: 1
        }
    };
});

// Filter in child table
frm.set_query('item_code', 'items', function(doc, cdt, cdn) {
    let row = locals[cdt][cdn];
    return {
        filters: {
            warehouse: row.warehouse || doc.default_warehouse
        }
    };
});

// Custom query (server method)
frm.set_query('supplier', function() {
    return {
        query: 'my_app.api.get_suppliers',
        filters: {
            region: frm.doc.region
        }
    };
});

// Clear query
frm.set_query('fieldname', null);

Custom Buttons

refresh: function(frm) {
    // Simple button
    frm.add_custom_button(__('Do Something'), function() {
        do_something(frm);
    });

    // Button in group/dropdown
    frm.add_custom_button(__('Action 1'), function() {
        action_1(frm);
    }, __('Actions'));

    frm.add_custom_button(__('Action 2'), function() {
        action_2(frm);
    }, __('Actions'));

    // Primary button (highlighted)
    frm.add_custom_button(__('Submit'), function() {
        submit_doc(frm);
    }).addClass('btn-primary');

    // Button with icon
    let btn = frm.add_custom_button(__('Print'), function() {
        print_doc(frm);
    });
    btn.prepend('<i class="fa fa-print"></i> ');

    // Conditional buttons
    if (frm.doc.status === 'Draft') {
        frm.add_custom_button(__('Submit for Review'), function() {
            submit_for_review(frm);
        });
    }

    // Remove button
    frm.remove_custom_button(__('Do Something'));
    frm.remove_custom_button(__('Action 1'), __('Actions'));

    // Clear all buttons
    frm.clear_custom_buttons();

    // Page actions
    frm.page.set_primary_action(__('Save'), function() {
        frm.save();
    });

    frm.page.set_secondary_action(__('Cancel'), function() {
        frappe.set_route('List', 'My DocType');
    });
}

Child Table Operations

Events

frappe.ui.form.on('My DocType Item', {
    // Row added
    items_add: function(frm, cdt, cdn) {
        let row = locals[cdt][cdn];
        row.warehouse = frm.doc.default_warehouse;
        frm.refresh_field('items');
    },

    // Before row removed (can prevent)
    before_items_remove: function(frm, cdt, cdn) {
        let row = locals[cdt][cdn];
        if (row.is_mandatory) {
            frappe.throw(__('Cannot remove mandatory item'));
        }
    },

    // Row removed
    items_remove: function(frm, cdt, cdn) {
        calculate_total(frm);
    },

    // Field in row changes
    qty: function(frm, cdt, cdn) {
        let row = locals[cdt][cdn];
        row.amount = flt(row.qty) * flt(row.rate);
        frm.refresh_field('items');
        calculate_total(frm);
    },

    rate: function(frm, cdt, cdn) {
        let row = locals[cdt][cdn];
        row.amount = flt(row.qty) * flt(row.rate);
        frm.refresh_field('items');
        calculate_total(frm);
    },

    item_code: function(frm, cdt, cdn) {
        let row = locals[cdt][cdn];
        if (row.item_code) {
            frappe.call({
                method: 'my_app.api.get_item_details',
                args: { item_code: row.item_code },
                callback: function(r) {
                    if (r.message) {
                        frappe.model.set_value(cdt, cdn, {
                            'rate': r.message.rate,
                            'uom': r.message.uom,
                            'description': r.message.description
                        });
                    }
                }
            });
        }
    }
});

function calculate_total(frm) {
    let total = 0;
    frm.doc.items.forEach(item => {
        total += flt(item.amount);
    });
    frm.set_value('total', total);
}

Manipulating Rows

// Add row
let row = frm.add_child('items', {
    item_code: 'ITEM-001',
    qty: 10,
    rate: 100
});
frm.refresh_field('items');

// Get row by index
let first_row = frm.doc.items[0];

// Get row by name
let row = locals['My DocType Item'][cdn];

// Update row
frappe.model.set_value(cdt, cdn, 'fieldname', value);
frappe.model.set_value(cdt, cdn, {
    'field1': 'value1',
    'field2': 'value2'
});

// Remove row
frm.get_field('items').grid.grid_rows[0].remove();
frm.refresh_field('items');

// Remove all rows
frm.clear_table('items');
frm.refresh_field('items');

// Iterate rows
frm.doc.items.forEach((item, idx) => {
    console.log(idx, item.item_code);
});

Dialogs

Simple Prompt

// Single field
frappe.prompt(
    {
        fieldname: 'reason',
        fieldtype: 'Small Text',
        label: 'Reason',
        reqd: 1
    },
    function(values) {
        console.log(values.reason);
    },
    __('Enter Reason'),
    __('Submit')
);

Multi-field Prompt

frappe.prompt([
    {
        fieldname: 'customer',
        fieldtype: 'Link',
        options: 'Customer',
        label: 'Customer',
        reqd: 1
    },
    {
        fieldname: 'date',
        fieldtype: 'Date',
        label: 'Date',
        default: frappe.datetime.nowdate()
    },
    {
        fieldname: 'priority',
        fieldtype: 'Select',
        label: 'Priority',
        options: 'Low\nMedium\nHigh',
        default: 'Medium'
    }
], function(values) {
    process_data(values);
}, __('Enter Details'), __('Process'));

Custom Dialog

let dialog = new frappe.ui.Dialog({
    title: __('Custom Dialog'),
    fields: [
        {
            fieldname: 'customer',
            fieldtype: 'Link',
            options: 'Customer',
            label: __('Customer'),
            reqd: 1,
            get_query: function() {
                return { filters: { status: 'Active' } };
            },
            change: function() {
                // Field change handler
                let value = dialog.get_value('customer');
                if (value) {
                    dialog.set_value('customer_name', 'Loading...');
                }
            }
        },
        { fieldtype: 'Column Break' },
        {
            fieldname: 'customer_name',
            fieldtype: 'Data',
            label: __('Customer Name'),
            read_only: 1
        },
        { fieldtype: 'Section Break', label: 'Items' },
        {
            fieldname: 'items',
            fieldtype: 'Table',
            label: __('Items'),
            cannot_add_rows: false,
            in_place_edit: true,
            fields: [
                {
                    fieldname: 'item',
                    fieldtype: 'Link',
                    options: 'Item',
                    in_list_view: 1,
                    label: __('Item')
                },
                {
                    fieldname: 'qty',
                    fieldtype: 'Float',
                    in_list_view: 1,
                    label: __('Qty')
                }
            ]
        }
    ],
    size: 'large', // small, large, extra-large
    primary_action_label: __('Submit'),
    primary_action: function(values) {
        console.log(values);
        dialog.hide();
        process_dialog(values);
    },
    secondary_action_label: __('Cancel')
});

dialog.show();

// Set values
dialog.set_value('customer', 'CUST-001');
dialog.set_values({
    'customer': 'CUST-001',
    'date': frappe.datetime.nowdate()
});

// Get values
let values = dialog.get_values();
let customer = dialog.get_value('customer');

// Access fields
let field = dialog.get_field('customer');
field.set_description('Select active customer');

Confirmation Dialog

frappe.confirm(
    __('Are you sure you want to delete this?'),
    function() {
        // On Yes
        delete_record();
    },
    function() {
        // On No (optional)
    }
);

API Calls

frappe.call

// Basic call
frappe.call({
    method: 'my_app.api.get_data',
    args: {
        customer: frm.doc.customer
    },
    callback: function(r) {
        if (r.message) {
            frm.set_value('data', r.message);
        }
    }
});

// With loading indicator
frappe.call({
    method: 'my_app.api.process',
    args: { data: frm.doc },
    freeze: true,
    freeze_message: __('Processing...'),
    callback: function(r) {
        frappe.msgprint(__('Done!'));
    },
    error: function(r) {
        frappe.msgprint(__('Error occurred'));
    }
});

// Async/await
async function getData() {
    const r = await frappe.call({
        method: 'my_app.api.get_data',
        args: { id: 123 }
    });
    return r.message;
}

// Promise chain
frappe.call({
    method: 'my_app.api.get_data'
}).then(r => {
    return frappe.call({
        method: 'my_app.api.process',
        args: { data: r.message }
    });
}).then(r => {
    console.log('Done', r.message);
});

Messages & Alerts

// Toast alert
frappe.show_alert({
    message: __('Success!'),
    indicator: 'green'  // green, blue, orange, red
}, 5);  // seconds

// Message dialog
frappe.msgprint({
    title: __('Information'),
    message: __('This is important'),
    indicator: 'blue'
});

// Error (stops execution)
frappe.throw(__('Cannot proceed'));

// Confirmation required
frappe.validated = false;  // In validate event

Utilities

// Date/Time
frappe.datetime.nowdate();           // "2024-01-15"
frappe.datetime.now_datetime();      // "2024-01-15 10:30:00"
frappe.datetime.add_days("2024-01-15", 7);
frappe.datetime.add_months("2024-01-15", 1);

// Formatting
frappe.format(1234.56, {fieldtype: 'Currency'});
format_currency(1234.56, 'USD');
flt(value);  // Float
cint(value); // Integer

// Navigation
frappe.set_route('Form', 'Customer', 'CUST-001');
frappe.set_route('List', 'Customer');
frappe.new_doc('Customer');

// Translation
__('Translate this');
__('Hello {0}', [name]);

平台分布

Codex

33.9%
按下载量换算25

Claude

31.94%
按下载量换算24

Cursor

19.2%
按下载量换算14

Gemini CLI

8.42%
按下载量换算6

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

需要联网

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

安装前确认

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

继续浏览同类 Skills