Token导航 LogoToken导航

Agent Skill · 待分类

push-notifications(推送通知)

用于辅助安全审计、权限检查、凭据风险、认证流程和常见漏洞排查。它适合让 Agent 梳理敏感配置、检查依赖风险、分析鉴权逻辑或生成安全复核清单。使用时不能把工具输出直接当最终结论,涉及密钥、令牌、用户数据或生产系统时,应先确认最小权限、脱敏方式和操作边界。

push-notifications 是待分类类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为需要联网;许可证为 MIT,第三方扫描有提醒项,按安装量在待分类类的 11,209 个 Skill 里排第 6,484。

总安装 447周安装 19GitHub Stars 8下载 157核验 2026-05-01

安装说明

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

安装方式

通过对话安装

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

请帮我安装这个 Agent Skill:push-notifications(推送通知)
来源仓库:https://github.com/kaakati/rails-enterprise-dev
仓库路径:skills/push-notifications
安装命令:
npx skills add https://github.com/kaakati/rails-enterprise-dev --skill push-notifications
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。不同来源提供的安装方式可能略有差异;本站展示可直接复制的安装命令,安装前请核对来源页面。

skills.shnpx skills
npx skills add https://github.com/kaakati/rails-enterprise-dev --skill push-notifications

这个 Skill 是做什么的

这个 Skill 的分类还没最终确定,用法以来源仓库的说明为准。

push-notifications 用于辅助安全审计、权限检查、凭据风险、认证流程和常见漏洞排查。适用于梳理敏感配置、检查依赖风险、分析鉴权逻辑或生成安全复核清单的场景。使用时不能把工具输出直接当最终结论,涉及密钥、令牌、用户数据或生产系统时需谨慎。应先确认最小权限、脱敏方式和操作边界,避免安全风险。

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

来源仓库内路径:skills/push-notifications

上手步骤

4 步
  1. 1

    确认自己的宿主工具

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

  2. 2

    拿到安装命令

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

  3. 3

    安装后重启宿主

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

  4. 4

    按最小权限试用

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

常见问题

6 条

push-notifications 怎么安装?

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

push-notifications 支持哪些 AI 工具?

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

push-notifications 需要什么权限?

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

push-notifications 是免费的吗?

来源标注的许可证是 MIT。本站不代收任何费用,也不托管安装包;能否商用、要不要署名,以许可证原文为准。

push-notifications 用的人多吗?

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

push-notifications 安全吗?

第三方扫描有提醒项,说明存在需要人工确认的行为。安装前请打开源码看清它读了什么、联网到哪里。

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

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 3 个目录同时收录,说明这个 Skill 流传比较广。
  • 许可证明确为 MIT,商用和分发边界好判断。

先想清楚的地方

  • 第三方扫描存在提醒项或未通过项,需要人工逐条确认。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

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

不太适合

  • 需要厂商级技术支持和 SLA 的生产系统。

待分类类 Skill 对比

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

infsh CLI · Codex、Claude

4,374,576382权限未标注未明确未收录
ai-video-generation

AI 视频生成 · Codex、Claude

4,313,376382调用外部服务未明确有提醒
lark-openapi-explorer

云雀 OpenAPI 浏览器 · Codex、Claude

2,064,1209,011需要联网未明确有提醒
lark-workflow-standup-report

Lark 工作流程站立报告 · Codex、Claude

2,041,6328,978需要联网未明确通过
use-my-browser

使用我的浏览器 · Codex、Claude

1,848,49140操作浏览器未明确未通过
push-notifications

推送通知 · 本页

4478需要联网MIT有提醒

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

SKILL.md 原文

Push Notifications — Expert Decisions

Expert decision frameworks for notification choices. Claude knows UNUserNotificationCenter and APNs — this skill provides judgment calls for permission timing, delivery strategies, and architecture trade-offs.


Decision Trees

Permission Request Timing

When should you ask for notification permission?
├─ User explicitly wants notifications
│  └─ After user taps "Enable Notifications" button
│     Highest acceptance rate (70-80%)
│
├─ After demonstrating value
│  └─ After user completes key action
│     "Get notified when your order ships?"
│     Context-specific, 50-60% acceptance
│
├─ First meaningful moment
│  └─ After onboarding, before home screen
│     Explain why, 30-40% acceptance
│
└─ On app launch
   └─ AVOID — lowest acceptance (15-20%)
      No context, feels intrusive

The trap: Requesting permission on first launch. Users deny reflexively. Wait for a moment when notifications clearly add value.

Silent vs Visible Notification

What's the notification purpose?
├─ Background data sync
│  └─ Silent notification (content-available: 1)
│     No user interruption, wakes app
│
├─ User needs to know immediately
│  └─ Visible alert
│     Messages, time-sensitive info
│
├─ Informational, not urgent
│  └─ Badge + silent
│     User sees count, checks when ready
│
└─ Needs user action
   └─ Visible with actions
      Reply, accept/decline buttons

Notification Extension Strategy

Do you need to modify notifications?
├─ Download images/media
│  └─ Notification Service Extension
│     mutable-content: 1 in payload
│
├─ Decrypt end-to-end encrypted content
│  └─ Notification Service Extension
│     Required for E2EE messaging
│
├─ Custom notification UI
│  └─ Notification Content Extension
│     Long-press/3D Touch custom view
│
└─ Standard text/badge
   └─ No extension needed
      Less complexity, faster delivery

Token Management

How should you handle device tokens?
├─ Single device per user
│  └─ Replace token on registration
│     Simple, most apps need this
│
├─ Multiple devices per user
│  └─ Register all tokens
│     Send to all active devices
│
├─ Token changed (reinstall/restore)
│  └─ Deduplicate on server
│     Same device, new token
│
└─ User logged out
   └─ Deregister token from user
      Prevents notifications to wrong user

NEVER Do

Permission Handling

NEVER request permission without context:

// ❌ First thing on app launch — user denies
func application(_ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { _, _ in }
    return true
}

// ✅ After user action that demonstrates value
func userTappedEnableNotifications() {
    showPrePermissionExplanation {
        Task {
            let granted = try? await UNUserNotificationCenter.current()
                .requestAuthorization(options: [.alert, .badge, .sound])
            if granted == true {
                await MainActor.run { registerForRemoteNotifications() }
            }
        }
    }
}

NEVER ignore denied permission:

// ❌ Keeps trying, annoys user
func checkNotifications() {
    Task {
        let settings = await UNUserNotificationCenter.current().notificationSettings()
        if settings.authorizationStatus == .denied {
            // Ask again!  <- User already said no
            requestPermission()
        }
    }
}

// ✅ Respect denial, offer settings path
func checkNotifications() {
    Task {
        let settings = await UNUserNotificationCenter.current().notificationSettings()
        switch settings.authorizationStatus {
        case .denied:
            showSettingsPrompt()  // "Enable in Settings to receive..."
        case .notDetermined:
            showPrePermissionScreen()
        case .authorized, .provisional, .ephemeral:
            ensureRegistered()
        @unknown default:
            break
        }
    }
}

Token Handling

NEVER cache device tokens long-term in app:

// ❌ Token may change without app knowing
class TokenManager {
    static var cachedToken: String?  // Stale after reinstall!

    func getToken() -> String? {
        return Self.cachedToken  // May be invalid
    }
}

// ✅ Always use fresh token from registration callback
func application(_ application: UIApplication,
    didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    let token = deviceToken.hexString

    // Send to server immediately — this is the source of truth
    Task {
        await sendTokenToServer(token)
    }
}

NEVER assume token format:

// ❌ Token format is not guaranteed
let tokenString = String(data: deviceToken, encoding: .utf8)  // Returns nil!

// ✅ Convert bytes to hex
extension Data {
    var hexString: String {
        map { String(format: "%02x", $0) }.joined()
    }
}

let tokenString = deviceToken.hexString

Silent Notifications

NEVER rely on silent notifications for time-critical delivery:

// ❌ Silent notifications are low priority
// Server sends: {"aps": {"content-available": 1}}
// Expecting: Immediate delivery
// Reality: iOS may delay minutes/hours or drop entirely

// ✅ Use visible notification for time-critical content
// Or use silent for prefetch, visible for alert
{
    "aps": {
        "alert": {"title": "New Message", "body": "..."},
        "content-available": 1  // Also prefetch in background
    }
}

NEVER do heavy work in silent notification handler:

// ❌ System will kill your app
func application(_ application: UIApplication,
    didReceiveRemoteNotification userInfo: [AnyHashable: Any]) async -> UIBackgroundFetchResult {

    await downloadLargeFiles()  // Takes too long!
    await processAllData()       // iOS terminates app

    return .newData
}

// ✅ Quick fetch, defer heavy processing
func application(_ application: UIApplication,
    didReceiveRemoteNotification userInfo: [AnyHashable: Any]) async -> UIBackgroundFetchResult {

    // 30 seconds max — fetch metadata only
    do {
        let hasNew = try await checkForNewContent()
        if hasNew {
            scheduleBackgroundProcessing()  // BGProcessingTask
        }
        return hasNew ? .newData : .noData
    } catch {
        return .failed
    }
}

Notification Service Extension

NEVER forget expiration handler:

// ❌ System shows unmodified notification
class NotificationService: UNNotificationServiceExtension {
    override func didReceive(_ request: UNNotificationRequest,
        withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {

        // Start async work...
        downloadImage { image in
            // Never called if timeout!
            contentHandler(modifiedContent)
        }
    }

    // Missing serviceExtensionTimeWillExpire!
}

// ✅ Always implement expiration handler
class NotificationService: UNNotificationServiceExtension {
    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?

    override func didReceive(_ request: UNNotificationRequest,
        withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        self.contentHandler = contentHandler
        bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent

        downloadImage { [weak self] image in
            guard let self, let content = self.bestAttemptContent else { return }
            if let image { content.attachments = [image] }
            contentHandler(content)
        }
    }

    override func serviceExtensionTimeWillExpire() {
        // Called ~30 seconds — deliver what you have
        if let content = bestAttemptContent {
            contentHandler?(content)
        }
    }
}

Essential Patterns

Permission Flow with Pre-Permission

@MainActor
final class NotificationPermissionManager: ObservableObject {
    @Published var status: UNAuthorizationStatus = .notDetermined

    func checkStatus() async {
        let settings = await UNUserNotificationCenter.current().notificationSettings()
        status = settings.authorizationStatus
    }

    func requestPermission() async -> Bool {
        do {
            let granted = try await UNUserNotificationCenter.current()
                .requestAuthorization(options: [.alert, .badge, .sound])

            if granted {
                UIApplication.shared.registerForRemoteNotifications()
            }

            await checkStatus()
            return granted
        } catch {
            return false
        }
    }

    func openSettings() {
        guard let url = URL(string: UIApplication.openSettingsURLString) else { return }
        UIApplication.shared.open(url)
    }
}

// Pre-permission screen
struct NotificationPermissionView: View {
    @StateObject private var manager = NotificationPermissionManager()
    @State private var showSystemPrompt = false

    var body: some View {
        VStack(spacing: 24) {
            Image(systemName: "bell.badge")
                .font(.system(size: 60))

            Text("Stay Updated")
                .font(.title)

            Text("Get notified about new messages, order updates, and important alerts.")
                .multilineTextAlignment(.center)

            Button("Enable Notifications") {
                Task { await manager.requestPermission() }
            }
            .buttonStyle(.borderedProminent)

            Button("Not Now") { dismiss() }
                .foregroundColor(.secondary)
        }
        .padding()
    }
}

Notification Action Handler

@MainActor
final class NotificationHandler: NSObject, UNUserNotificationCenterDelegate {
    static let shared = NotificationHandler()

    private let router: DeepLinkRouter

    func userNotificationCenter(_ center: UNUserNotificationCenter,
        willPresent notification: UNNotification) async -> UNNotificationPresentationOptions {
        // App is in foreground
        let userInfo = notification.request.content.userInfo

        // Check if we should show banner or handle silently
        if shouldShowInForeground(userInfo) {
            return [.banner, .sound, .badge]
        } else {
            handleSilently(userInfo)
            return []
        }
    }

    func userNotificationCenter(_ center: UNUserNotificationCenter,
        didReceive response: UNNotificationResponse) async {
        let userInfo = response.notification.request.content.userInfo

        switch response.actionIdentifier {
        case UNNotificationDefaultActionIdentifier:
            // User tapped notification
            await handleNotificationTap(userInfo)

        case "REPLY_ACTION":
            if let textResponse = response as? UNTextInputNotificationResponse {
                await handleReply(text: textResponse.userText, userInfo: userInfo)
            }

        case "MARK_READ_ACTION":
            await markAsRead(userInfo)

        case UNNotificationDismissActionIdentifier:
            // User dismissed
            break

        default:
            await handleCustomAction(response.actionIdentifier, userInfo: userInfo)
        }
    }

    private func handleNotificationTap(_ userInfo: [AnyHashable: Any]) async {
        guard let deepLink = userInfo["deep_link"] as? String,
              let url = URL(string: deepLink) else { return }

        await router.navigate(to: url)
    }
}

Rich Notification Service

class NotificationService: UNNotificationServiceExtension {
    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?

    override func didReceive(_ request: UNNotificationRequest,
        withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        self.contentHandler = contentHandler
        bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent

        guard let content = bestAttemptContent else {
            contentHandler(request.content)
            return
        }

        Task {
            // Download and attach media
            if let mediaURL = request.content.userInfo["media_url"] as? String {
                if let attachment = await downloadAttachment(from: mediaURL) {
                    content.attachments = [attachment]
                }
            }

            // Decrypt if needed
            if let encrypted = request.content.userInfo["encrypted_body"] as? String {
                content.body = decrypt(encrypted)
            }

            contentHandler(content)
        }
    }

    override func serviceExtensionTimeWillExpire() {
        if let content = bestAttemptContent {
            contentHandler?(content)
        }
    }

    private func downloadAttachment(from urlString: String) async -> UNNotificationAttachment? {
        guard let url = URL(string: urlString) else { return nil }

        do {
            let (localURL, response) = try await URLSession.shared.download(from: url)

            let fileExtension = (response as? HTTPURLResponse)?
                .mimeType.flatMap { mimeToExtension[$0] } ?? "jpg"

            let destURL = FileManager.default.temporaryDirectory
                .appendingPathComponent(UUID().uuidString)
                .appendingPathExtension(fileExtension)

            try FileManager.default.moveItem(at: localURL, to: destURL)

            return try UNNotificationAttachment(identifier: "media", url: destURL)
        } catch {
            return nil
        }
    }
}

Quick Reference

Payload Structure

FieldPurposeValue
alertVisible notification{title, subtitle, body}
badgeApp icon badgeNumber
soundNotification sound"default" or filename
content-availableSilent/background1
mutable-contentService extension1
categoryAction buttonsCategory identifier
thread-idNotification groupingThread identifier

Permission States

StatusMeaningAction
notDeterminedNever askedShow pre-permission
deniedUser declinedShow settings prompt
authorizedFull accessRegister for remote
provisionalQuiet deliveryConsider upgrade prompt
ephemeralApp clip temporaryLimited time

Extension Limits

ExtensionTime LimitUse Case
Service Extension~30 secondsDownload media, decrypt
Content ExtensionUser interactionCustom UI
Background fetch~30 secondsData refresh

Red Flags

SmellProblemFix
Permission on launchLow acceptanceWait for user action
Cached device tokenMay be staleAlways use callback
String(data:encoding:) for tokenReturns nilUse hex encoding
Silent for time-criticalMay be delayedUse visible notification
Heavy work in silent handlerApp terminatedQuick fetch, defer work
No serviceExtensionTimeWillExpireUnmodified content shownAlways implement
Ignoring denied statusFrustrates userOffer settings path

平台分布

Antigravity

28.17%
按下载量换算44

Gemini CLI

21%
按下载量换算33

OpenCode

15.43%
按下载量换算24

Claude Code

12.77%
按下载量换算20

Codex

7.53%
按下载量换算12

windsurf

3.61%
按下载量换算6

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

可疑

权限和风险

需要联网

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

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在提醒或未通过项,安装前请自行复核。

继续浏览同类 Skills