open-neko · 6 plugins

OpenNeko Official

First-party plugins for OpenNeko. Published by the maintainers under @open-neko/* on npm. Written, tested, and supported by the OpenNeko team.

Third parties: publish your own marketplace.json at any HTTPS URL — see the publish guide for the schema, integrity-hash recipe, and CI checks.

How operators install

openneko install <name> writes the manifest entry and pulls the pinned version from npm. On the next worker start, every listed plugin boots in its own microsandbox VM.

Marketplace URL

open-neko.github.io/plugins/marketplace.json

Trusted by default — the openneko CLI fetches it on every install. Third parties: openneko marketplace add <url>.

Plugins listed

6

Plugins

Click view source for each plugin's repository. Every plugin runs inside a microsandbox microVM with outbound network limited to the hosts shown on the card — the manifest declaration is enforced at the VM boundary.

Telegram channel

@open-neko/channel-telegram v0.7.2

Bidirectional Telegram channel: delivers the agent's Briefing, findings, and approvals to a chat (HTML + inline Approve/Reject buttons) and turns replies and button taps back into agent intents. Authenticated via a bot token from @BotFather, stored in the per-user secrets file.

api.telegram.org
prompts at install for:
🔒 TELEGRAM_BOT_TOKEN🔒 TELEGRAM_WEBHOOK_SECRET?
openneko install @open-neko/channel-telegram view source ↗

Parallel.ai Search MCP

@open-neko/plugin-parallel-search v0.3.1

Web search + page fetch via Parallel.ai's Search MCP over Streamable HTTP. Anonymous tier supported; Bearer-token authenticated tier optional. Two actions: web_search and web_fetch.

web_searchweb_fetchsearch.parallel.ai
openneko install @open-neko/plugin-parallel-search view source ↗

Scalekit SSO

@open-neko/plugin-scalekit v0.2.1

Enterprise SSO via Scalekit. One integration that fronts Okta, Entra ID, Google Workspace, JumpCloud, and other identity providers. Implements OpenNeko's generic OIDC auth contract — install this plugin and the 'Sign in with Scalekit' button lights up in the web app.

SSO provider: Scalekit*.scalekit.com
prompts at install for:
SCALEKIT_ENVIRONMENT_URLSCALEKIT_CLIENT_ID🔒 SCALEKIT_CLIENT_SECRET
openneko install @open-neko/plugin-scalekit view source ↗

Google Workspace connector

@open-neko/connector-google-workspace v0.2.1

Per-operator Google Workspace connector — PKCE OAuth2 against your own Cloud Console OAuth client. Each operator authorises independently; OpenNeko persists their refresh token in the per-operator section of the secrets file and rotates the access token on demand. Three actions: send_gmail, list_calendar_events, append_sheet_row.

send_gmaillist_calendar_eventsappend_sheet_rowaccounts.google.comoauth2.googleapis.comgmail.googleapis.comwww.googleapis.comsheets.googleapis.comdocs.googleapis.com
prompts at install for:
GOOGLE_CLIENT_ID🔒 GOOGLE_CLIENT_SECRET
openneko install @open-neko/connector-google-workspace view source ↗

Shopify Admin connector

@open-neko/plugin-shopify v0.2.1

Per-deployment Shopify Admin API connector — list orders, fetch order detail, update internal order notes. Bound to one *.myshopify.com store via a deployment-wide Admin API access token. Three actions: list_shopify_orders, get_shopify_order, update_shopify_order_note.

list_shopify_ordersget_shopify_orderupdate_shopify_order_note*.myshopify.com
prompts at install for:
SHOPIFY_STORE_DOMAIN🔒 SHOPIFY_ACCESS_TOKENSHOPIFY_API_VERSION?
openneko install @open-neko/plugin-shopify view source ↗

Slack

@open-neko/plugin-slack v0.5.0

A bidirectional Slack channel — DM the agent, @-mention it in a channel, and run /openneko slash commands (inbound over Socket Mode) — plus four separately approvable actions: post messages, send DMs, react with emoji, and look up users/channels. Authenticated via a Slack bot token (xoxb-); inbound adds an app-level token (xapp-).

send_slack_messagesend_slack_dmreact_slack_messagelookup_slack_entityslack.com
prompts at install for:
🔒 SLACK_BOT_TOKEN🔒 SLACK_SIGNING_SECRET?🔒 SLACK_APP_TOKEN?
openneko install @open-neko/plugin-slack view source ↗

Trust model

This is the official OpenNeko marketplace — written, tested, and supported by the OpenNeko team. Anyone else who ships plugins to OpenNeko users publishes their own marketplace.json. Operators trust each one explicitly.

First-party

Listed here

The OpenNeko maintainers write these. Code lives in open-neko/plugins; we yank a version if it ever turns malicious.

Third-party

Add your own

Publish your marketplace.json at any HTTPS URL, then operators trust it with openneko marketplace add <url>. OpenNeko makes no representation about non-official marketplaces.

Bypass

openneko install <name> --unverified

Skip every marketplace and install straight from npm. Loud warning. For plugin authoring or emergency hotfixes — the integrity hash is taken on trust, but sandboxing and capability enforcement still apply.

For developers

The exact bytes the openneko CLI fetches when an operator runs openneko marketplace add or openneko install. Use the schema if you're publishing your own marketplace.json. Each block is also served at a stable URL so you can curl it.

marketplace.json GET https://open-neko.github.io/plugins/marketplace.json · 49.1 KB
{
  "$schema": "https://open-neko.github.io/plugins/marketplace.schema.json",
  "name": "OpenNeko Official",
  "owner": "open-neko",
  "homepage": "https://open-neko.github.io/plugins/",
  "description": "First-party plugins for OpenNeko. Published by the maintainers under @open-neko/* on npm. Written, tested, and supported by the OpenNeko team.",
  "plugins": [
    {
      "name": "@open-neko/channel-telegram",
      "title": "Telegram channel",
      "description": "Bidirectional Telegram channel: delivers the agent's Briefing, findings, and approvals to a chat (HTML + inline Approve/Reject buttons) and turns replies and button taps back into agent intents. Authenticated via a bot token from @BotFather, stored in the per-user secrets file.",
      "source": "https://github.com/open-neko/plugins/tree/main/packages/telegram",
      "homepage": "https://core.telegram.org/bots/api",
      "maintainers": [
        {
          "name": "open-neko",
          "url": "https://github.com/open-neko"
        }
      ],
      "versions": [
        {
          "version": "0.3.1",
          "integrity": "sha512-fhQgHLdnVkOCfCNDnnAyUmDMNrIZhFzerllcqZGBLJp+Vmrn9lLLxa18wyMXrSD2GEdmZLcI0TlRXYq7A3WDKA==",
          "permissions": {
            "network": [
              "api.telegram.org"
            ],
            "env": [
              {
                "key": "TELEGRAM_BOT_TOKEN",
                "required": true,
                "secret": true,
                "description": "Bot token from @BotFather (e.g. 123456789:AAH...). The plugin calls https://api.telegram.org/bot<token>/sendMessage to deliver."
              },
              {
                "key": "TELEGRAM_WEBHOOK_SECRET",
                "required": false,
                "secret": true,
                "description": "Optional secret_token set via setWebhook; verify_inbound checks Telegram's X-Telegram-Bot-Api-Secret-Token header against it. Not needed for getUpdates long-poll."
              }
            ]
          },
          "capabilities": {
            "channel": {
              "providerLabel": "Telegram",
              "directions": [
                "outbound",
                "inbound"
              ],
              "ingress": "webhook",
              "profile": {
                "modalities": [
                  "text"
                ],
                "richMedia": {
                  "markdown": true,
                  "cards": false,
                  "charts": false,
                  "images": true,
                  "interactiveControls": true
                },
                "interaction": {
                  "turnTaking": "async",
                  "canApproveInline": true,
                  "quickReplies": true
                },
                "constraints": {
                  "maxOutboundChars": 4096,
                  "latencyClass": "interactive",
                  "attentionModel": "push"
                },
                "fidelity": "summary"
              }
            }
          },
          "publishedAt": "2026-06-02"
        },
        {
          "version": "0.3.0",
          "integrity": "sha512-dKx2Ro0X0KdjZkMxTR/tFJt3bIyUl+byq8CUfC/iwNgcs0A6/R6Vxzv1EWniKxEay+h91vE1MLCtXtOr75+XGg==",
          "permissions": {
            "network": [
              "api.telegram.org"
            ],
            "env": [
              {
                "key": "TELEGRAM_BOT_TOKEN",
                "required": true,
                "secret": true,
                "description": "Bot token from @BotFather (e.g. 123456789:AAH...). The plugin calls https://api.telegram.org/bot<token>/sendMessage to deliver."
              },
              {
                "key": "TELEGRAM_WEBHOOK_SECRET",
                "required": false,
                "secret": true,
                "description": "Optional secret_token set via setWebhook; verify_inbound checks Telegram's X-Telegram-Bot-Api-Secret-Token header against it. Not needed for getUpdates long-poll."
              }
            ]
          },
          "capabilities": {
            "channel": {
              "providerLabel": "Telegram",
              "directions": [
                "outbound",
                "inbound"
              ],
              "ingress": "webhook",
              "profile": {
                "modalities": [
                  "text"
                ],
                "richMedia": {
                  "markdown": true,
                  "cards": false,
                  "charts": false,
                  "images": true,
                  "interactiveControls": true
                },
                "interaction": {
                  "turnTaking": "async",
                  "canApproveInline": true,
                  "quickReplies": true
                },
                "constraints": {
                  "maxOutboundChars": 4096,
                  "latencyClass": "interactive",
                  "attentionModel": "push"
                },
                "fidelity": "summary"
              }
            }
          },
          "publishedAt": "2026-05-25"
        },
        {
          "version": "0.2.0",
          "integrity": "sha512-6DCDiVh+CMU6wCuyp3N2RMIhpehs/C0vC2nP08XzFzo/8uK1GLO0YpN4wXYp3RUMcrYBiOvCLyx4+gbuNE/yEw==",
          "provenanceWaived": true,
          "permissions": {
            "network": [
              "api.telegram.org"
            ],
            "env": [
              {
                "key": "TELEGRAM_BOT_TOKEN",
                "required": true,
                "secret": true,
                "description": "Bot token from @BotFather (e.g. 123456789:AAH...). The plugin calls https://api.telegram.org/bot<token>/sendMessage to deliver."
              },
              {
                "key": "TELEGRAM_WEBHOOK_SECRET",
                "required": false,
                "secret": true,
                "description": "Optional secret_token set via setWebhook; verify_inbound checks Telegram's X-Telegram-Bot-Api-Secret-Token header against it. Not needed for getUpdates long-poll."
              }
            ]
          },
          "capabilities": {
            "channel": {
              "providerLabel": "Telegram",
              "directions": [
                "outbound",
                "inbound"
              ],
              "ingress": "webhook",
              "profile": {
                "modalities": [
                  "text"
                ],
                "richMedia": {
                  "markdown": true,
                  "cards": false,
                  "charts": false,
                  "images": true,
                  "interactiveControls": true
                },
                "interaction": {
                  "turnTaking": "async",
                  "canApproveInline": true,
                  "quickReplies": true
                },
                "constraints": {
                  "maxOutboundChars": 4096,
                  "latencyClass": "interactive",
                  "attentionModel": "push"
                },
                "fidelity": "summary"
              }
            }
          },
          "publishedAt": "2026-05-25"
        },
        {
          "version": "0.4.0",
          "integrity": "sha512-VyemqbEcmj3q90JWyQd6m9WOQP7ml8J2Aa7IUSvVPoc48bzyIStBSj14UDNshex6bZDz+uN+Ytih2tyT04zr3A==",
          "permissions": {
            "network": [
              "api.telegram.org"
            ],
            "env": [
              {
                "key": "TELEGRAM_BOT_TOKEN",
                "required": true,
                "secret": true,
                "description": "Bot token from @BotFather (e.g. 123456789:AAH...). The plugin calls https://api.telegram.org/bot<token>/sendMessage to deliver."
              },
              {
                "key": "TELEGRAM_WEBHOOK_SECRET",
                "required": false,
                "secret": true,
                "description": "Optional secret_token set via setWebhook; verify_inbound checks Telegram's X-Telegram-Bot-Api-Secret-Token header against it. Not needed for getUpdates long-poll."
              }
            ]
          },
          "capabilities": {
            "channel": {
              "providerLabel": "Telegram",
              "directions": [
                "outbound",
                "inbound"
              ],
              "ingress": "webhook",
              "profile": {
                "modalities": [
                  "text"
                ],
                "richMedia": {
                  "markdown": true,
                  "cards": false,
                  "charts": false,
                  "images": true,
                  "interactiveControls": true
                },
                "interaction": {
                  "turnTaking": "async",
                  "canApproveInline": true,
                  "quickReplies": true
                },
                "constraints": {
                  "maxOutboundChars": 4096,
                  "latencyClass": "interactive",
                  "attentionModel": "push"
                },
                "fidelity": "summary"
              }
            }
          },
          "publishedAt": "2026-06-11"
        },
        {
          "version": "0.5.0",
          "integrity": "sha512-FqMkx69a97sACbBGQOjGGFbpljJsScsDDNweiT0/sNXU6iM6B8Eo9Vl1OYt9owFLcT5DvIA3GxFhcGJS0R2WFQ==",
          "permissions": {
            "network": [
              "api.telegram.org"
            ],
            "env": [
              {
                "key": "TELEGRAM_BOT_TOKEN",
                "required": true,
                "secret": true,
                "description": "Bot token from @BotFather (e.g. 123456789:AAH…). The plugin calls https://api.telegram.org/bot<token>/sendMessage to deliver. When unset at exec time the plugin runs in dry-run mode — it still projects the native payload but sends nothing."
              },
              {
                "key": "TELEGRAM_WEBHOOK_SECRET",
                "required": false,
                "secret": true,
                "description": "Optional secret_token you set when registering a webhook via setWebhook. When present, verify_inbound requires Telegram's X-Telegram-Bot-Api-Secret-Token header to match it (constant-time). Not needed for long-polling (getUpdates)."
              }
            ]
          },
          "capabilities": {
            "channel": {
              "providerLabel": "Telegram",
              "directions": [
                "outbound",
                "inbound"
              ],
              "ingress": "webhook",
              "profile": {
                "modalities": [
                  "text"
                ],
                "richMedia": {
                  "markdown": true,
                  "cards": false,
                  "charts": false,
                  "images": true,
                  "interactiveControls": true
                },
                "interaction": {
                  "turnTaking": "async",
                  "canApproveInline": true,
                  "quickReplies": true
                },
                "constraints": {
                  "maxOutboundChars": 4096,
                  "latencyClass": "interactive",
                  "attentionModel": "push"
                },
                "fidelity": "summary"
              }
            }
          },
          "publishedAt": "2026-06-24"
        },
        {
          "version": "0.6.0",
          "integrity": "sha512-Lq9rKJD0GdvsgEi/L3K5aLIoAJQlKAHypNJ4/tBqYpCDtQ1QukwfthA2KL4op7SdcuDfB47HjA4eZ5YCbfatLA==",
          "permissions": {
            "network": [
              "api.telegram.org"
            ],
            "env": [
              {
                "key": "TELEGRAM_BOT_TOKEN",
                "required": true,
                "secret": true,
                "inject": "egress",
                "description": "Bot token from @BotFather (e.g. 123456789:AAH…). The plugin calls https://api.telegram.org/bot<token>/sendMessage to deliver. When unset at exec time the plugin runs in dry-run mode — it still projects the native payload but sends nothing."
              },
              {
                "key": "TELEGRAM_WEBHOOK_SECRET",
                "required": false,
                "secret": true,
                "description": "Optional secret_token you set when registering a webhook via setWebhook. When present, verify_inbound requires Telegram's X-Telegram-Bot-Api-Secret-Token header to match it (constant-time). Not needed for long-polling (getUpdates)."
              }
            ]
          },
          "capabilities": {
            "channel": {
              "providerLabel": "Telegram",
              "directions": [
                "outbound",
                "inbound"
              ],
              "ingress": "webhook",
              "profile": {
                "modalities": [
                  "text"
                ],
                "richMedia": {
                  "markdown": true,
                  "cards": false,
                  "charts": false,
                  "images": true,
                  "interactiveControls": true
                },
                "interaction": {
                  "turnTaking": "async",
                  "canApproveInline": true,
                  "quickReplies": true
                },
                "constraints": {
                  "maxOutboundChars": 4096,
                  "latencyClass": "interactive",
                  "attentionModel": "push"
                },
                "fidelity": "summary"
              }
            }
          },
          "publishedAt": "2026-06-26"
        },
        {
          "version": "0.7.0",
          "integrity": "sha512-f1VEYn3YMIHc7sjnirjJ9kSi4m2lLR3bPz3NvcyfLM7JNHE2GPkdcaFDARrxPFVY+Z4pcilH4IbJCbllu26B3w==",
          "permissions": {
            "network": [
              "api.telegram.org"
            ],
            "env": [
              {
                "key": "TELEGRAM_BOT_TOKEN",
                "required": true,
                "secret": true,
                "inject": "egress",
                "description": "Bot token from @BotFather (e.g. 123456789:AAH…). The plugin calls https://api.telegram.org/bot<token>/sendMessage to deliver. When unset at exec time the plugin runs in dry-run mode — it still projects the native payload but sends nothing."
              },
              {
                "key": "TELEGRAM_WEBHOOK_SECRET",
                "required": false,
                "secret": true,
                "description": "Optional secret_token you set when registering a webhook via setWebhook. When present, verify_inbound requires Telegram's X-Telegram-Bot-Api-Secret-Token header to match it (constant-time). Not needed for long-polling (getUpdates)."
              }
            ]
          },
          "capabilities": {
            "channel": {
              "providerLabel": "Telegram",
              "directions": [
                "outbound",
                "inbound"
              ],
              "ingress": "webhook",
              "profile": {
                "modalities": [
                  "text"
                ],
                "richMedia": {
                  "markdown": true,
                  "cards": false,
                  "charts": false,
                  "images": true,
                  "interactiveControls": true
                },
                "interaction": {
                  "turnTaking": "async",
                  "canApproveInline": true,
                  "quickReplies": true
                },
                "constraints": {
                  "maxOutboundChars": 4096,
                  "latencyClass": "interactive",
                  "attentionModel": "push"
                },
                "fidelity": "summary"
              }
            }
          },
          "publishedAt": "2026-06-26"
        },
        {
          "version": "0.7.2",
          "integrity": "sha512-aU3rW2ab5Nn6kjEp8WHJP9nAn1W2jL3QoEkYJhMGM6z2T2GdPPJ9sFc3UFr4lh/pA8ROYRNVCbCw9Jdb05JdyA==",
          "permissions": {
            "network": [
              "api.telegram.org"
            ],
            "env": [
              {
                "key": "TELEGRAM_BOT_TOKEN",
                "required": true,
                "secret": true,
                "inject": "egress",
                "description": "Bot token from @BotFather (e.g. 123456789:AAH…). The plugin calls https://api.telegram.org/bot<token>/sendMessage to deliver. When unset at exec time the plugin runs in dry-run mode — it still projects the native payload but sends nothing."
              },
              {
                "key": "TELEGRAM_WEBHOOK_SECRET",
                "required": false,
                "secret": true,
                "description": "Optional secret_token you set when registering a webhook via setWebhook. When present, verify_inbound requires Telegram's X-Telegram-Bot-Api-Secret-Token header to match it (constant-time). Not needed for long-polling (getUpdates)."
              }
            ]
          },
          "capabilities": {
            "channel": {
              "providerLabel": "Telegram",
              "directions": [
                "outbound",
                "inbound"
              ],
              "ingress": "webhook",
              "profile": {
                "modalities": [
                  "text"
                ],
                "richMedia": {
                  "markdown": true,
                  "cards": false,
                  "charts": false,
                  "images": true,
                  "interactiveControls": true
                },
                "interaction": {
                  "turnTaking": "async",
                  "canApproveInline": true,
                  "quickReplies": true
                },
                "constraints": {
                  "maxOutboundChars": 4096,
                  "latencyClass": "interactive",
                  "attentionModel": "push"
                },
                "fidelity": "summary"
              }
            }
          },
          "publishedAt": "2026-06-26"
        }
      ]
    },
    {
      "name": "@open-neko/plugin-parallel-search",
      "title": "Parallel.ai Search MCP",
      "description": "Web search + page fetch via Parallel.ai's Search MCP over Streamable HTTP. Anonymous tier supported; Bearer-token authenticated tier optional. Two actions: web_search and web_fetch.",
      "source": "https://github.com/open-neko/plugins/tree/main/packages/parallel-search",
      "homepage": "https://docs.parallel.ai/integrations/mcp/search-mcp",
      "maintainers": [
        {
          "name": "open-neko",
          "url": "https://github.com/open-neko"
        }
      ],
      "versions": [
        {
          "version": "0.3.1",
          "integrity": "sha512-n5yLgTAVBqoGyhZjn9KJB1Z2m6kf8lurgnpQ2Z4Qk2FpiHSae29QPwv1xy/HFgmSqnhjXxFyU/l3l+lReWnaGA==",
          "permissions": {
            "network": [
              "search.parallel.ai"
            ],
            "env": []
          },
          "capabilities": {
            "action": {
              "kinds": [
                {
                  "kind": "web_search",
                  "description": "Search the web via Parallel.ai's Search MCP.",
                  "default_mode": "auto"
                },
                {
                  "kind": "web_fetch",
                  "description": "Fetch a single page via Parallel.ai's Search MCP.",
                  "default_mode": "auto"
                }
              ]
            }
          },
          "publishedAt": "2026-06-02"
        },
        {
          "version": "0.2.0",
          "integrity": "sha512-gX6PcjZvyl2ggW1HLy0v9KANPHoBi96DhsBVzDmY4v2nDiQHjufDacazwaHi+OInT8+wz+Hf+gqSZETLDse79A==",
          "provenanceWaived": true,
          "permissions": {
            "network": [
              "search.parallel.ai"
            ],
            "env": []
          },
          "capabilities": {
            "action": {
              "kinds": [
                {
                  "kind": "web_search",
                  "description": "Search the web via Parallel.ai's Search MCP.",
                  "default_mode": "auto"
                },
                {
                  "kind": "web_fetch",
                  "description": "Fetch a single page via Parallel.ai's Search MCP.",
                  "default_mode": "auto"
                }
              ]
            }
          },
          "publishedAt": "2026-05-17"
        },
        {
          "version": "0.3.0",
          "integrity": "sha512-NMAgy8RvNKhrmHFBEc6BGdbzVlsUkCALK3XYvZ8k7QqxUBYrgy5ogpIlJJMlmVckDDr42dZ+GflsKMTVC8qpBw==",
          "permissions": {
            "network": [
              "search.parallel.ai"
            ],
            "env": []
          },
          "capabilities": {
            "action": {
              "kinds": [
                {
                  "kind": "web_search",
                  "description": "Search the web via Parallel.ai's Search MCP.",
                  "default_mode": "auto"
                },
                {
                  "kind": "web_fetch",
                  "description": "Fetch a single page via Parallel.ai's Search MCP.",
                  "default_mode": "auto"
                }
              ]
            }
          },
          "publishedAt": "2026-05-24"
        }
      ]
    },
    {
      "name": "@open-neko/plugin-scalekit",
      "title": "Scalekit SSO",
      "description": "Enterprise SSO via Scalekit. One integration that fronts Okta, Entra ID, Google Workspace, JumpCloud, and other identity providers. Implements OpenNeko's generic OIDC auth contract — install this plugin and the 'Sign in with Scalekit' button lights up in the web app.",
      "source": "https://github.com/open-neko/plugins/tree/main/packages/scalekit",
      "homepage": "https://docs.scalekit.com/sso/quickstart",
      "maintainers": [
        {
          "name": "open-neko",
          "url": "https://github.com/open-neko"
        }
      ],
      "versions": [
        {
          "version": "0.2.1",
          "integrity": "sha512-COH/05vyBYCyNNHJ+GjBKTHFRvJf6cXkCYTpqrbtUJ4jXFM0AFWSl1YPW/zXROf+2IC1xCH+NChDKQTTdxNR5Q==",
          "permissions": {
            "network": [
              "*.scalekit.com"
            ],
            "env": [
              {
                "key": "SCALEKIT_ENVIRONMENT_URL",
                "required": true,
                "secret": false,
                "description": "Your Scalekit environment URL, e.g. https://your-app.scalekit.com — copied from the Scalekit dashboard under API config."
              },
              {
                "key": "SCALEKIT_CLIENT_ID",
                "required": true,
                "secret": false,
                "description": "The OAuth client_id from the Scalekit dashboard. Public — paired with the secret below for token exchange."
              },
              {
                "key": "SCALEKIT_CLIENT_SECRET",
                "required": true,
                "secret": true,
                "description": "The OAuth client_secret from the Scalekit dashboard. Stored in the per-user secrets file, never echoed."
              }
            ]
          },
          "capabilities": {
            "auth": {
              "providerLabel": "Scalekit"
            }
          },
          "publishedAt": "2026-06-02"
        },
        {
          "version": "0.1.0",
          "integrity": "sha512-XTDCF4qaMptWHrD4d0ojd+gs5fb7q3GHe0Qhi4dBzGwerZilQDBGorUVGy4TRiFVTo/s7Rbx6lzD8LbXNV5RMA==",
          "provenanceWaived": true,
          "permissions": {
            "network": [
              "*.scalekit.com"
            ],
            "env": [
              {
                "key": "SCALEKIT_ENVIRONMENT_URL",
                "required": true,
                "secret": false,
                "description": "Your Scalekit environment URL, e.g. https://your-app.scalekit.com — copied from the Scalekit dashboard."
              },
              {
                "key": "SCALEKIT_CLIENT_ID",
                "required": true,
                "secret": false,
                "description": "OAuth client_id from the Scalekit dashboard."
              },
              {
                "key": "SCALEKIT_CLIENT_SECRET",
                "required": true,
                "secret": true,
                "description": "OAuth client_secret from the Scalekit dashboard. Stored in the per-user secrets file."
              }
            ]
          },
          "capabilities": {
            "auth": {
              "providerLabel": "Scalekit"
            }
          },
          "publishedAt": "2026-05-18"
        },
        {
          "version": "0.2.0",
          "integrity": "sha512-uMaoqLnHW//RI0mm+f1Dnr+89FKE/R0MtR/9FIGFi1ZmOxpEBbEWcrBjgSln+0cq6salv8O7dlBwYqsqF+Tbsg==",
          "permissions": {
            "network": [
              "*.scalekit.com"
            ],
            "env": [
              {
                "key": "SCALEKIT_ENVIRONMENT_URL",
                "required": true,
                "secret": false,
                "description": "Your Scalekit environment URL, e.g. https://your-app.scalekit.com — copied from the Scalekit dashboard under API config."
              },
              {
                "key": "SCALEKIT_CLIENT_ID",
                "required": true,
                "secret": false,
                "description": "The OAuth client_id from the Scalekit dashboard. Public — paired with the secret below for token exchange."
              },
              {
                "key": "SCALEKIT_CLIENT_SECRET",
                "required": true,
                "secret": true,
                "description": "The OAuth client_secret from the Scalekit dashboard. Stored in the per-user secrets file, never echoed."
              }
            ]
          },
          "capabilities": {
            "auth": {
              "providerLabel": "Scalekit"
            }
          },
          "publishedAt": "2026-05-24"
        }
      ]
    },
    {
      "name": "@open-neko/connector-google-workspace",
      "title": "Google Workspace connector",
      "description": "Per-operator Google Workspace connector — PKCE OAuth2 against your own Cloud Console OAuth client. Each operator authorises independently; OpenNeko persists their refresh token in the per-operator section of the secrets file and rotates the access token on demand. Three actions: send_gmail, list_calendar_events, append_sheet_row.",
      "source": "https://github.com/open-neko/plugins/tree/main/packages/google-workspace",
      "homepage": "https://developers.google.com/identity/protocols/oauth2",
      "maintainers": [
        {
          "name": "open-neko",
          "url": "https://github.com/open-neko"
        }
      ],
      "versions": [
        {
          "version": "0.2.1",
          "integrity": "sha512-RD07xb8mmk27Vl0ebIpvTWtmPi+oOBWibfiO2fbolsQX66fMDJLdW3jeZ0sdDTJWL47BUmO2vsZPXzZNElPIKw==",
          "permissions": {
            "network": [
              "accounts.google.com",
              "oauth2.googleapis.com",
              "gmail.googleapis.com",
              "www.googleapis.com",
              "sheets.googleapis.com",
              "docs.googleapis.com"
            ],
            "env": [
              {
                "key": "GOOGLE_CLIENT_ID",
                "required": true,
                "secret": false,
                "description": "OAuth 2.0 Client ID from Google Cloud Console (deployment-wide — all operators share one client). Create at https://console.cloud.google.com/apis/credentials → Create credentials → OAuth client ID → Web application; set the redirect URI to your deployment's connect callback."
              },
              {
                "key": "GOOGLE_CLIENT_SECRET",
                "required": true,
                "secret": true,
                "description": "OAuth 2.0 Client Secret paired with the client ID above. Stored in the per-user secrets file, never echoed."
              }
            ]
          },
          "capabilities": {
            "connect": {
              "providerLabel": "Google Workspace",
              "scopes": [
                "openid",
                "https://www.googleapis.com/auth/userinfo.email",
                "https://www.googleapis.com/auth/gmail.send",
                "https://www.googleapis.com/auth/calendar.events.readonly",
                "https://www.googleapis.com/auth/spreadsheets",
                "https://www.googleapis.com/auth/documents"
              ],
              "flow": "oauth2-pkce"
            },
            "action": {
              "kinds": [
                {
                  "kind": "send_gmail",
                  "description": "Send an email from the connected user's Gmail account.",
                  "default_mode": "ask"
                },
                {
                  "kind": "list_calendar_events",
                  "description": "List upcoming events on the connected user's primary calendar.",
                  "default_mode": "auto"
                },
                {
                  "kind": "append_sheet_row",
                  "description": "Append a row to a Google Sheet the connected user can edit.",
                  "default_mode": "ask"
                }
              ]
            }
          },
          "publishedAt": "2026-06-02"
        },
        {
          "version": "0.2.0",
          "integrity": "sha512-VzsTtkvvk75QRZ8tt1EdCMX6+ltjqgXaI2nVjo05aSKeL6UEouAkauDPBTD84cim1cbc2IV4/B2y/2yAji1vMg==",
          "permissions": {
            "network": [
              "accounts.google.com",
              "oauth2.googleapis.com",
              "gmail.googleapis.com",
              "www.googleapis.com",
              "sheets.googleapis.com",
              "docs.googleapis.com"
            ],
            "env": [
              {
                "key": "GOOGLE_CLIENT_ID",
                "required": true,
                "secret": false,
                "description": "OAuth 2.0 Client ID from Google Cloud Console (deployment-wide — all operators share one client). Create at https://console.cloud.google.com/apis/credentials → Create credentials → OAuth client ID → Web application; set the redirect URI to your deployment's connect callback."
              },
              {
                "key": "GOOGLE_CLIENT_SECRET",
                "required": true,
                "secret": true,
                "description": "OAuth 2.0 Client Secret paired with the client ID above. Stored in the per-user secrets file, never echoed."
              }
            ]
          },
          "capabilities": {
            "connect": {
              "providerLabel": "Google Workspace",
              "scopes": [
                "openid",
                "https://www.googleapis.com/auth/userinfo.email",
                "https://www.googleapis.com/auth/gmail.send",
                "https://www.googleapis.com/auth/calendar.events.readonly",
                "https://www.googleapis.com/auth/spreadsheets",
                "https://www.googleapis.com/auth/documents"
              ],
              "flow": "oauth2-pkce"
            },
            "action": {
              "kinds": [
                {
                  "kind": "send_gmail",
                  "description": "Send an email from the connected user's Gmail account.",
                  "default_mode": "ask"
                },
                {
                  "kind": "list_calendar_events",
                  "description": "List upcoming events on the connected user's primary calendar.",
                  "default_mode": "auto"
                },
                {
                  "kind": "append_sheet_row",
                  "description": "Append a row to a Google Sheet the connected user can edit.",
                  "default_mode": "ask"
                }
              ]
            }
          },
          "publishedAt": "2026-05-24"
        }
      ]
    },
    {
      "name": "@open-neko/plugin-shopify",
      "title": "Shopify Admin connector",
      "description": "Per-deployment Shopify Admin API connector — list orders, fetch order detail, update internal order notes. Bound to one *.myshopify.com store via a deployment-wide Admin API access token. Three actions: list_shopify_orders, get_shopify_order, update_shopify_order_note.",
      "source": "https://github.com/open-neko/plugins/tree/main/packages/shopify",
      "homepage": "https://shopify.dev/docs/api/admin-rest",
      "maintainers": [
        {
          "name": "open-neko",
          "url": "https://github.com/open-neko"
        }
      ],
      "versions": [
        {
          "version": "0.2.1",
          "integrity": "sha512-P/dC8a8UMMIYBASc2QRzhN+vDbB9y1IqTuhW7A4a/zPujpImSeV0quFNDo5i8EP8eKbDk7b9HDgNZZFBgzXpaQ==",
          "permissions": {
            "network": [
              "*.myshopify.com"
            ],
            "env": [
              {
                "key": "SHOPIFY_STORE_DOMAIN",
                "required": true,
                "secret": false,
                "description": "Your store's permanent myshopify.com subdomain — e.g. acme.myshopify.com. Use the .myshopify.com domain even if you've configured a custom storefront domain; the Admin API only honors the canonical one."
              },
              {
                "key": "SHOPIFY_ACCESS_TOKEN",
                "required": true,
                "secret": true,
                "description": "Shopify Admin API access token (starts with shpat_). Create at admin → Settings → Apps and sales channels → Develop apps → Create an app → Configuration → Admin API access scopes. Needs read_orders + write_orders. Token is shown ONCE on install — copy it immediately."
              },
              {
                "key": "SHOPIFY_API_VERSION",
                "required": false,
                "secret": false,
                "description": "Shopify Admin API version to pin (defaults to 2026-01). Shopify rotates quarterly; pin a stable version to avoid surprise breakage."
              }
            ]
          },
          "capabilities": {
            "action": {
              "kinds": [
                {
                  "kind": "list_shopify_orders",
                  "description": "List recent orders, optionally filtered by financialStatus, fulfillmentStatus, or a date range (createdAtMin/createdAtMax).",
                  "default_mode": "auto"
                },
                {
                  "kind": "get_shopify_order",
                  "description": "Fetch full detail for one order by id — line items, customer, addresses, refunds, tags.",
                  "default_mode": "auto"
                },
                {
                  "kind": "update_shopify_order_note",
                  "description": "Set or append the internal note on an order (user-visible in Shopify admin only; not customer-facing).",
                  "default_mode": "ask"
                }
              ]
            }
          },
          "publishedAt": "2026-06-02"
        },
        {
          "version": "0.2.0",
          "integrity": "sha512-JnzuEr8ag+U7/yL1eyTk/VwwK84zoP9GTRxPF6LTGukNTk1CJOloeE3XZmK+U8+5lCZ2dZrdv0yMbFDTDBp7gA==",
          "permissions": {
            "network": [
              "*.myshopify.com"
            ],
            "env": [
              {
                "key": "SHOPIFY_STORE_DOMAIN",
                "required": true,
                "secret": false,
                "description": "Your store's permanent myshopify.com subdomain — e.g. acme.myshopify.com. Use the .myshopify.com domain even if you've configured a custom storefront domain; the Admin API only honors the canonical one."
              },
              {
                "key": "SHOPIFY_ACCESS_TOKEN",
                "required": true,
                "secret": true,
                "description": "Shopify Admin API access token (starts with shpat_). Create at admin → Settings → Apps and sales channels → Develop apps → Create an app → Configuration → Admin API access scopes. Needs read_orders + write_orders. Token is shown ONCE on install — copy it immediately."
              },
              {
                "key": "SHOPIFY_API_VERSION",
                "required": false,
                "secret": false,
                "description": "Shopify Admin API version to pin (defaults to 2026-01). Shopify rotates quarterly; pin a stable version to avoid surprise breakage."
              }
            ]
          },
          "capabilities": {
            "action": {
              "kinds": [
                {
                  "kind": "list_shopify_orders",
                  "description": "List recent orders, optionally filtered by financialStatus, fulfillmentStatus, or a date range (createdAtMin/createdAtMax).",
                  "default_mode": "auto"
                },
                {
                  "kind": "get_shopify_order",
                  "description": "Fetch full detail for one order by id — line items, customer, addresses, refunds, tags.",
                  "default_mode": "auto"
                },
                {
                  "kind": "update_shopify_order_note",
                  "description": "Set or append the internal note on an order (user-visible in Shopify admin only; not customer-facing).",
                  "default_mode": "ask"
                }
              ]
            }
          },
          "publishedAt": "2026-05-24"
        }
      ]
    },
    {
      "name": "@open-neko/plugin-slack",
      "title": "Slack",
      "description": "A bidirectional Slack channel — DM the agent, @-mention it in a channel, and run /openneko slash commands (inbound over Socket Mode) — plus four separately approvable actions: post messages, send DMs, react with emoji, and look up users/channels. Authenticated via a Slack bot token (xoxb-); inbound adds an app-level token (xapp-).",
      "source": "https://github.com/open-neko/plugins/tree/main/packages/slack",
      "homepage": "https://api.slack.com/web",
      "maintainers": [
        {
          "name": "open-neko",
          "url": "https://github.com/open-neko"
        }
      ],
      "versions": [
        {
          "version": "0.2.1",
          "integrity": "sha512-4M9fp8UcSJWJahiHAJnfUHPB2IUo56sq3bR6rM4mLNphPqNZB2q3Z59Qp+fSpfOO7YtBR5YeSQSyV6paddrZxw==",
          "permissions": {
            "network": [
              "slack.com"
            ],
            "env": [
              {
                "key": "SLACK_BOT_TOKEN",
                "required": true,
                "secret": true,
                "description": "xoxb-... bot token from your Slack app. Needs scopes: chat:write, im:write (for DMs), reactions:write, users:read, users:read.email (for email lookup), channels:read, groups:read (for channel lookup)."
              }
            ]
          },
          "capabilities": {
            "action": {
              "kinds": [
                {
                  "kind": "send_slack_message",
                  "description": "Post a message to a Slack channel.",
                  "default_mode": "auto"
                },
                {
                  "kind": "send_slack_dm",
                  "description": "Send a direct message to a Slack user. `user` may be a name, email, or Slack id.",
                  "default_mode": "auto"
                },
                {
                  "kind": "react_slack_message",
                  "description": "Add an emoji reaction to a Slack message.",
                  "default_mode": "auto"
                },
                {
                  "kind": "lookup_slack_entity",
                  "description": "Look up a Slack user or channel by name or ID.",
                  "default_mode": "auto"
                }
              ]
            }
          },
          "publishedAt": "2026-06-02"
        },
        {
          "version": "0.1.0",
          "integrity": "sha512-+H6gDUp8vNfs5pVvffnEByN5ZDt9c5HiYGF1uQ2Hq5b6NZYNn7DHlQtMMAXXv1BOKuiHnCPBmYyL5+ltrMa8rQ==",
          "provenanceWaived": true,
          "permissions": {
            "network": [
              "slack.com"
            ],
            "env": [
              {
                "key": "SLACK_BOT_TOKEN",
                "required": true,
                "secret": true,
                "description": "xoxb-... bot token from your Slack app. Needs scopes: chat:write, im:write, reactions:write, users:read, users:read.email, channels:read, groups:read (depending on which actions you'll allow)."
              }
            ]
          },
          "capabilities": {
            "action": {
              "kinds": [
                {
                  "kind": "send_slack_message",
                  "description": "Post a message to a Slack channel.",
                  "default_mode": "ask"
                },
                {
                  "kind": "send_slack_dm",
                  "description": "Send a direct message to a Slack user.",
                  "default_mode": "ask"
                },
                {
                  "kind": "react_slack_message",
                  "description": "Add an emoji reaction to a Slack message.",
                  "default_mode": "ask"
                },
                {
                  "kind": "lookup_slack_entity",
                  "description": "Look up a Slack user or channel by name or ID.",
                  "default_mode": "auto"
                }
              ]
            }
          },
          "publishedAt": "2026-05-17"
        },
        {
          "version": "0.2.0",
          "integrity": "sha512-rg/Md77UMQCKGiCLYHOF1ecTPAtU35VIkLF7sRDZNtYAj5YjKPzi19DZ+a05ezPWn2BCMtujrJT7nYm4bQvW8w==",
          "permissions": {
            "network": [
              "slack.com"
            ],
            "env": [
              {
                "key": "SLACK_BOT_TOKEN",
                "required": true,
                "secret": true,
                "description": "xoxb-... bot token from your Slack app. Needs scopes: chat:write, im:write (for DMs), reactions:write, users:read, users:read.email (for email lookup), channels:read, groups:read (for channel lookup)."
              }
            ]
          },
          "capabilities": {
            "action": {
              "kinds": [
                {
                  "kind": "send_slack_message",
                  "description": "Post a message to a Slack channel.",
                  "default_mode": "auto"
                },
                {
                  "kind": "send_slack_dm",
                  "description": "Send a direct message to a Slack user. `user` may be a name, email, or Slack id.",
                  "default_mode": "auto"
                },
                {
                  "kind": "react_slack_message",
                  "description": "Add an emoji reaction to a Slack message.",
                  "default_mode": "auto"
                },
                {
                  "kind": "lookup_slack_entity",
                  "description": "Look up a Slack user or channel by name or ID.",
                  "default_mode": "auto"
                }
              ]
            }
          },
          "publishedAt": "2026-05-24"
        },
        {
          "version": "0.4.0",
          "integrity": "sha512-oRknzVVbidkkl2c1p46IEGgG26cs0w9gGFEkZ6Q8yIcak+IOIp04GeA4Iyd6a0RDDDT+3Z2QEX7oGQcA0TVLog==",
          "permissions": {
            "network": [
              "slack.com"
            ],
            "env": [
              {
                "key": "SLACK_BOT_TOKEN",
                "required": true,
                "secret": true,
                "description": "xoxb-... bot token from your Slack app. Scopes: chat:write, im:write, im:history (DMs), app_mentions:read (mentions), reactions:write, users:read, users:read.email (email lookup), channels:read, groups:read (channel lookup)."
              },
              {
                "key": "SLACK_APP_TOKEN",
                "required": false,
                "secret": true,
                "description": "xapp-... app-level token with connections:write. Enables inbound (DMs, @-mentions, slash commands) over Socket Mode. Omit for outbound/action-only installs."
              },
              {
                "key": "SLACK_SIGNING_SECRET",
                "required": false,
                "secret": true,
                "description": "Slack signing secret — only for the legacy webhook ingress; Socket Mode (the default) doesn't use it."
              }
            ]
          },
          "capabilities": {
            "action": {
              "kinds": [
                {
                  "kind": "send_slack_message",
                  "description": "Post a message to a Slack channel.",
                  "default_mode": "auto"
                },
                {
                  "kind": "send_slack_dm",
                  "description": "Send a direct message to a Slack user. `user` may be a name, email, or Slack id.",
                  "default_mode": "auto"
                },
                {
                  "kind": "react_slack_message",
                  "description": "Add an emoji reaction to a Slack message.",
                  "default_mode": "auto"
                },
                {
                  "kind": "lookup_slack_entity",
                  "description": "Look up a Slack user or channel by name or ID.",
                  "default_mode": "auto"
                }
              ]
            },
            "channel": {
              "providerLabel": "Slack",
              "directions": [
                "outbound",
                "inbound"
              ],
              "ingress": "socket",
              "profile": {
                "modalities": [
                  "text",
                  "visual"
                ],
                "richMedia": {
                  "markdown": true,
                  "cards": true,
                  "charts": false,
                  "images": true,
                  "interactiveControls": true
                },
                "interaction": {
                  "turnTaking": "async",
                  "canApproveInline": true,
                  "quickReplies": true
                },
                "constraints": {
                  "latencyClass": "interactive",
                  "attentionModel": "push"
                },
                "fidelity": "summary"
              }
            }
          },
          "publishedAt": "2026-06-15"
        },
        {
          "version": "0.5.0",
          "integrity": "sha512-SMNIMM27wyYHZgW2J8tXfOIi7i/3W350g2Zr663QD1V26o+I4EC7at0T9uVWrmAujdSpI4jDE5aBH8WWPUYu3g==",
          "permissions": {
            "network": [
              "slack.com"
            ],
            "env": [
              {
                "key": "SLACK_BOT_TOKEN",
                "required": true,
                "secret": true,
                "inject": "egress",
                "description": "xoxb-... bot token from your Slack app. Scopes: chat:write, im:write, im:history (DMs), app_mentions:read (mentions), reactions:write, users:read, users:read.email (email lookup), channels:read, groups:read (channel lookup)."
              },
              {
                "key": "SLACK_SIGNING_SECRET",
                "required": false,
                "secret": true,
                "description": "Slack app signing secret - verifies inbound webhook requests (X-Slack-Signature over v0:<ts>:<body>). Only needed for webhook ingress; Socket Mode (the default) doesn't use it."
              },
              {
                "key": "SLACK_APP_TOKEN",
                "required": false,
                "secret": true,
                "inject": "egress",
                "description": "xapp-... app-level token with connections:write. Enables inbound (DMs, @-mentions, slash commands) over Socket Mode. Omit for outbound/action-only installs. Subscribe bot events message.im + app_mention and add a /openneko slash command in the Slack app."
              }
            ]
          },
          "capabilities": {
            "action": {
              "kinds": [
                {
                  "kind": "send_slack_message",
                  "description": "Post a message to a Slack channel.",
                  "default_mode": "auto"
                },
                {
                  "kind": "send_slack_dm",
                  "description": "Send a direct message to a Slack user. `user` may be a name, email, or Slack id.",
                  "default_mode": "auto"
                },
                {
                  "kind": "react_slack_message",
                  "description": "Add an emoji reaction to a Slack message.",
                  "default_mode": "auto"
                },
                {
                  "kind": "lookup_slack_entity",
                  "description": "Look up a Slack user or channel by name or ID.",
                  "default_mode": "auto"
                }
              ]
            },
            "channel": {
              "providerLabel": "Slack",
              "directions": [
                "outbound",
                "inbound"
              ],
              "ingress": "socket",
              "profile": {
                "modalities": [
                  "text",
                  "visual"
                ],
                "richMedia": {
                  "markdown": true,
                  "cards": true,
                  "charts": false,
                  "images": true,
                  "interactiveControls": true
                },
                "interaction": {
                  "turnTaking": "async",
                  "canApproveInline": true,
                  "quickReplies": true
                },
                "constraints": {
                  "latencyClass": "interactive",
                  "attentionModel": "push"
                },
                "fidelity": "summary"
              }
            }
          },
          "publishedAt": "2026-06-26"
        }
      ]
    }
  ]
}
marketplace.schema.json GET https://open-neko.github.io/plugins/marketplace.schema.json · 11.9 KB
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://open-neko.github.io/plugins/marketplace.schema.json",
  "title": "OpenNeko plugin marketplace",
  "description": "Single-file catalog of plugins, modeled on Claude Code's marketplace.json plus the security fields the OpenNeko CLI enforces (integrity, permissions, capabilities). Publish your own marketplace.json at a stable URL and operators can trust it with `openneko marketplace add <url>`.",
  "type": "object",
  "required": ["name", "owner", "description", "plugins"],
  "additionalProperties": true,
  "properties": {
    "$schema": { "type": "string" },
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 80,
      "description": "Human-readable name. Shown when the operator adds this marketplace."
    },
    "owner": {
      "type": "string",
      "minLength": 1,
      "description": "GitHub org / individual / company that publishes this marketplace."
    },
    "homepage": { "type": "string", "format": "uri" },
    "description": {
      "type": "string",
      "minLength": 10,
      "maxLength": 800
    },
    "plugins": {
      "type": "array",
      "items": { "$ref": "#/$defs/plugin" }
    }
  },
  "$defs": {
    "plugin": {
      "type": "object",
      "required": ["name", "title", "description", "source", "versions"],
      "additionalProperties": false,
      "properties": {
        "name": {
          "type": "string",
          "pattern": "^(@[a-z0-9-]+/)?[a-z0-9][a-z0-9-_]*$",
          "description": "npm package name."
        },
        "title": { "type": "string", "minLength": 3, "maxLength": 80 },
        "description": { "type": "string", "minLength": 10, "maxLength": 800 },
        "source": {
          "type": "string",
          "format": "uri",
          "pattern": "^https://(github\\.com|gitlab\\.com|codeberg\\.org)/"
        },
        "homepage": { "type": "string", "format": "uri" },
        "maintainers": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "object",
            "required": ["name"],
            "additionalProperties": false,
            "properties": {
              "name": { "type": "string" },
              "url": { "type": "string", "format": "uri" }
            }
          }
        },
        "versions": {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/$defs/version" }
        }
      }
    },
    "version": {
      "type": "object",
      "required": ["version", "integrity", "permissions", "capabilities", "publishedAt"],
      "additionalProperties": false,
      "properties": {
        "version": {
          "type": "string",
          "pattern": "^\\d+\\.\\d+\\.\\d+(-[0-9A-Za-z-.]+)?$",
          "description": "Pinned semver — no ranges."
        },
        "integrity": {
          "type": "string",
          "pattern": "^sha512-[A-Za-z0-9+/=]+$",
          "description": "Pinned npm dist.integrity hash. Use the all-`a` placeholder pattern ONLY in combination with `draft: true` while the package is still pre-publish."
        },
        "permissions": { "$ref": "#/$defs/permissions" },
        "capabilities": { "$ref": "#/$defs/capabilities" },
        "publishedAt": { "type": "string", "format": "date" },
        "yanked": { "type": "boolean", "default": false },
        "yanked_reason": { "type": "string" },
        "draft": {
          "type": "boolean",
          "default": false,
          "description": "Set to true when the version isn't on npm yet (chicken-and-egg case for a brand-new package whose first publish hasn't fired). `validate-live` skips every live check for draft entries — they're treated as informational. Flip to false and pin the real integrity hash once the publish workflow has succeeded."
        },
        "provenanceWaived": {
          "type": "boolean",
          "default": false,
          "description": "Set to true for legacy versions that were published before the publish workflow started using --provenance. `validate-live` still enforces integrity but skips the provenance check. Don't use on new versions — bump and re-publish with --provenance instead."
        }
      }
    },
    "permissions": {
      "type": "object",
      "description": "What the plugin needs from the runtime: outbound network hosts and operator-supplied env vars. Same shape lives in the installed manifest entry.",
      "additionalProperties": false,
      "properties": {
        "network": {
          "type": "array",
          "default": [],
          "items": {
            "type": "string",
            "pattern": "^[a-z0-9*]([a-z0-9-.*])*[a-z0-9*]$"
          },
          "description": "Hostnames the sandbox is allowed to reach. Enforced at the VM boundary."
        },
        "env": {
          "type": "array",
          "default": [],
          "items": { "$ref": "#/$defs/envRequirement" },
          "description": "Env vars the operator must supply. `openneko install` prompts for required ones and refuses to complete if missing."
        }
      }
    },
    "envRequirement": {
      "type": "object",
      "required": ["key", "description"],
      "additionalProperties": false,
      "properties": {
        "key": {
          "type": "string",
          "pattern": "^[A-Z][A-Z0-9_]*$",
          "maxLength": 64,
          "description": "UPPER_SNAKE_CASE env var name the plugin reads."
        },
        "required": { "type": "boolean", "default": true },
        "secret": {
          "type": "boolean",
          "default": true,
          "description": "Hide the value at prompt; the CLI will not echo it back."
        },
        "inject": {
          "enum": ["egress", "box"],
          "description": "How a secret reaches the plugin VM. 'egress': held gateway-side and substituted by the proxy onto outbound requests — the box sees only a placeholder (for credentials sent to an external API, e.g. a bot token). 'box' (default): the value lives in the VM, for secrets compared locally and never sent out (e.g. a webhook signing secret)."
        },
        "description": {
          "type": "string",
          "minLength": 1,
          "maxLength": 280
        }
      }
    },
    "capabilities": {
      "type": "object",
      "description": "Surfaces this plugin contributes. The keyset declares what the plugin does — `action`, `auth`, and/or `connect`. At least one must be present.",
      "additionalProperties": false,
      "minProperties": 1,
      "properties": {
        "action": {
          "type": "object",
          "description": "The plugin contributes one or more named action handlers.",
          "required": ["kinds"],
          "additionalProperties": false,
          "properties": {
            "kinds": {
              "type": "array",
              "minItems": 1,
              "items": { "$ref": "#/$defs/actionDeclaration" }
            }
          }
        },
        "auth": {
          "type": "object",
          "description": "The plugin acts as the SSO provider. Singleton — only one installed plugin may declare this.",
          "additionalProperties": false,
          "properties": {
            "providerLabel": {
              "type": "string",
              "minLength": 1,
              "description": "Short label rendered on the sign-in button (e.g. \"Scalekit\", \"Okta\")."
            }
          }
        },
        "connect": {
          "type": "object",
          "description": "The plugin offers a per-operator OAuth connector. Non-singleton — any number of installed plugins may declare this, and each operator connects independently. Credentials land in the per-operator section of the secrets store.",
          "required": ["providerLabel", "scopes"],
          "additionalProperties": false,
          "properties": {
            "providerLabel": {
              "type": "string",
              "minLength": 1,
              "description": "Display label shown on the Connect button + status row (e.g. \"Google Workspace\", \"GitHub\")."
            },
            "scopes": {
              "type": "array",
              "minItems": 1,
              "items": { "type": "string", "minLength": 1, "maxLength": 256 },
              "description": "OAuth scopes the connector will request at consent time."
            },
            "flow": {
              "type": "string",
              "enum": ["oauth2-pkce"],
              "default": "oauth2-pkce",
              "description": "Flow type the worker should drive. v1 supports oauth2-pkce only."
            }
          }
        },
        "channel": {
          "type": "object",
          "description": "The plugin is a frontend (channel) — Slack, Telegram, voice, etc. Non-singleton; the worker projects modality-free InteractionEvents into the substrate inside the plugin VM and normalizes inbound payloads back into agent intents.",
          "required": ["providerLabel", "profile", "directions"],
          "additionalProperties": false,
          "properties": {
            "providerLabel": {
              "type": "string",
              "minLength": 1,
              "description": "Display label for the channel (e.g. \"Telegram\", \"Slack\")."
            },
            "directions": {
              "type": "array",
              "minItems": 1,
              "items": { "type": "string", "enum": ["inbound", "outbound"] },
              "description": "Which directions the channel supports."
            },
            "ingress": {
              "type": "string",
              "enum": ["webhook", "socket", "none"],
              "default": "none",
              "description": "How inbound arrives: an HTTP webhook, a persistent socket, or none (outbound-only)."
            },
            "profile": { "$ref": "#/$defs/capabilityProfile" }
          }
        }
      }
    },
    "capabilityProfile": {
      "type": "object",
      "description": "What the substrate can carry — the only thing a projection may branch on. Additive: new fields default such that existing channels are unaffected, so unknown properties are tolerated.",
      "required": ["modalities", "richMedia", "interaction", "constraints", "fidelity"],
      "properties": {
        "modalities": {
          "type": "array",
          "minItems": 1,
          "items": { "type": "string", "enum": ["text", "visual", "voice", "haptic", "neural"] }
        },
        "richMedia": { "type": "object" },
        "interaction": { "type": "object" },
        "constraints": { "type": "object" },
        "fidelity": { "type": "string", "enum": ["headline", "summary", "full"] }
      }
    },
    "actionDeclaration": {
      "type": "object",
      "required": ["kind", "description"],
      "additionalProperties": false,
      "properties": {
        "kind": {
          "type": "string",
          "pattern": "^[a-z][a-z0-9_]*$",
          "description": "Snake_case action identifier."
        },
        "description": {
          "type": "string",
          "minLength": 1,
          "description": "Short description the agent uses to pick this kind."
        },
        "default_mode": {
          "description": "Seeded approval policy when the plugin is freshly installed. auto = run inline with no human gate (read-only kinds). ask = suspend agent turn, render approval card to the user (externally observable kinds). deny = never invokable without explicit opt-in. Operators can override later in /settings/rules. Defaults to ask when omitted — safer to surprise the user with a prompt than a side effect. Accepts either a scalar (applies to all scopes) or a per-scope object {external?, internal?} when the kind needs different defaults depending on the scope it's invoked under.",
          "oneOf": [
            { "type": "string", "enum": ["auto", "ask", "deny"] },
            {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "external": { "type": "string", "enum": ["auto", "ask", "deny"] },
                "internal": { "type": "string", "enum": ["auto", "ask", "deny"] }
              }
            }
          ]
        }
      }
    }
  }
}