Skip to main content
Plugins let you hook into CyberStrike’s runtime to intercept tool calls, modify LLM parameters, register custom tools, add authentication providers, and more.

Installation

Plugins use the @cyberstrike-io/plugin package for types. CyberStrike installs this automatically when it detects a plugin directory, but you can add it manually:
npm install @cyberstrike-io/plugin

File locations

Place plugin files in one of these locations and CyberStrike will load them automatically:
  • .cyberstrike/plugin/my-plugin.ts — project-level plugin
  • .cyberstrike/plugins/my-plugin.ts — alternative project-level path
  • ~/.cyberstrike/plugin/my-plugin.ts — user-level plugin (applies to all projects)
You can also register an npm package as a plugin explicitly in cyberstrike.json:
{
  "plugin": ["my-cyberstrike-plugin", "@scope/[email protected]"]
}
Plugin files in .cyberstrike/plugin/ are auto-discovered and do not need to be listed in cyberstrike.json. Entries in the "plugin" array are for npm packages or paths outside the standard directories.

The Plugin type

A plugin is a function that receives a PluginInput and returns a Promise<Hooks>:
import type { Plugin } from "@cyberstrike-io/plugin"

const plugin: Plugin = async (input) => {
  return {
    // hooks go here
  }
}

export default plugin

PluginInput fields

A fully initialized CyberStrike SDK client (ReturnType<typeof createCyberstrikeClient>). Use it to interact with sessions, messages, and projects programmatically.
The current Project object from the SDK. Contains metadata about the active project.
The current working directory for the session (string). Prefer this over process.cwd() when resolving paths.
The project worktree root (string). Use this to generate stable relative paths, e.g. path.relative(input.worktree, absPath).
The URL of the running CyberStrike server (URL). Useful for making API calls back to the server from your plugin.
A BunShell instance for running shell commands from your plugin. Supports template literal syntax: await input.$`ls -la`.

Hooks

Return a Hooks object from your plugin function. All hooks are optional.

event

Called for every event emitted by CyberStrike.
"event": async ({ event }) => {
  console.log("event received:", event)
}

config

Called with the resolved Config object after all config files are merged. Use this to inspect or react to configuration at startup.
"config": async (config) => {
  console.log("active model:", config.model)
}

tool

Register custom tools that the agent can call. The value is a map of tool names to ToolDefinition objects (see custom tools below).
"tool": {
  my_tool: tool({
    description: "Does something useful",
    args: {
      target: tool.schema.string().describe("The target to operate on"),
    },
    async execute({ target }, context) {
      return `Result for ${target}`
    },
  }),
}

auth

Register a custom authentication provider for LLM access. See AuthHook below.

chat.message

Called when a new user message is received, before the agent processes it.
"chat.message": async (input, output) => {
  // input: { sessionID, agent?, model?, messageID?, variant? }
  // output: { message: UserMessage, parts: Part[] }
  console.log("new message in session:", input.sessionID)
}

chat.params

Modify sampling parameters sent to the LLM. Mutate the output object to change values.
"chat.params": async (input, output) => {
  // input: { sessionID, agent, model, provider, message }
  // output: { temperature, topP, topK, options }
  if (input.agent === "web-application") {
    output.temperature = 0.2
  }
}

chat.headers

Add custom HTTP headers to LLM API requests.
"chat.headers": async (input, output) => {
  // output: { headers: Record<string, string> }
  output.headers["X-Custom-Header"] = "my-value"
}

permission.ask

Intercept permission requests before the user is prompted. Mutate output.status to pre-approve or deny.
"permission.ask": async (input, output) => {
  // input: Permission object
  // output: { status: "ask" | "deny" | "allow" }
  if (input.type === "bash" && input.command?.startsWith("ls")) {
    output.status = "allow"
  }
}

command.execute.before

Called before a slash command executes. Mutate output.parts to replace the command’s rendered output.
"command.execute.before": async (input, output) => {
  // input: { command: string, sessionID: string, arguments: string }
  // output: { parts: Part[] }
  console.log(`running command: /${input.command}`)
}

tool.execute.before

Intercept a tool call before it runs. Mutate output.args to modify the arguments passed to the tool.
"tool.execute.before": async (input, output) => {
  // input: { tool: string, sessionID: string, callID: string }
  // output: { args: any }
  if (input.tool === "bash") {
    console.log("bash args:", output.args)
  }
}

tool.execute.after

Intercept a tool result after it runs. Mutate output.output to change what the agent sees.
"tool.execute.after": async (input, output) => {
  // input: { tool, sessionID, callID, args }
  // output: { title: string, output: string, metadata: any }
  if (input.tool === "bash") {
    output.output = output.output.slice(0, 4096) // truncate large outputs
  }
}

shell.env

Inject environment variables into shells spawned by CyberStrike tools (e.g. the bash tool).
"shell.env": async (input, output) => {
  // input: { cwd: string }
  // output: { env: Record<string, string> }
  output.env["MY_API_KEY"] = "secret"
  output.env["TARGET_HOST"] = "192.168.1.1"
}

tool.definition

Modify tool descriptions and parameter schemas sent to the LLM. Use this to clarify or restrict tool usage for specific workflows.
"tool.definition": async (input, output) => {
  // input: { toolID: string }
  // output: { description: string, parameters: any }
  if (input.toolID === "bash") {
    output.description += "\nOnly run read-only commands."
  }
}

Experimental hooks

These hooks are unstable and may change between releases.
Transform the full message history before it is sent to the LLM. Mutate output.messages to reorder, filter, or modify messages.
"experimental.chat.messages.transform": async (input, output) => {
  // output: { messages: Array<{ info: Message, parts: Part[] }> }
}
Transform the system prompt. Mutate output.system (an array of strings that are joined) to add or replace content.
"experimental.chat.system.transform": async (input, output) => {
  // input: { sessionID?, model }
  // output: { system: string[] }
  output.system.push("Always respond in valid JSON.")
}
Called before a session compaction starts. Set output.context to append strings to the default compaction prompt, or set output.prompt to replace it entirely.
"experimental.session.compacting": async (input, output) => {
  // input: { sessionID: string }
  // output: { context: string[], prompt?: string }
  output.context.push("Preserve all vulnerability findings verbatim.")
}
Intercept text completions. Mutate output.text to change the result.
"experimental.text.complete": async (input, output) => {
  // input: { sessionID, messageID, partID }
  // output: { text: string }
}

Custom tools

Use the tool helper from @cyberstrike-io/plugin to define tools. It uses Zod for argument schemas via tool.schema.
import { tool, type Plugin } from "@cyberstrike-io/plugin"

const plugin: Plugin = async (input) => {
  return {
    tool: {
      port_check: tool({
        description: "Check if a TCP port is open on a host",
        args: {
          host: tool.schema.string().describe("Target hostname or IP"),
          port: tool.schema.number().int().describe("TCP port number"),
        },
        async execute({ host, port }, context) {
          const result = await input.$`nc -zv ${host} ${port}`.nothrow().text()
          return result
        },
      }),
    },
  }
}

export default plugin
The execute function receives the parsed args and a ToolContext with:
FieldTypeDescription
sessionIDstringID of the current session
messageIDstringID of the current message
agentstringName of the calling agent
directorystringCurrent project directory
worktreestringProject worktree root
abortAbortSignalSignal fired when the tool call is cancelled
metadata()functionSet a display title or metadata for the tool call
ask()functionRequest permission from the user

The AuthHook type

Use auth to register a custom LLM authentication provider. This lets you add new OAuth or API key flows accessible from the CyberStrike auth UI.
import type { Plugin, AuthHook } from "@cyberstrike-io/plugin"

const plugin: Plugin = async (input) => {
  const auth: AuthHook = {
    provider: "my-provider",
    methods: [
      {
        type: "api",
        label: "API Key",
        prompts: [
          {
            type: "text",
            key: "apiKey",
            message: "Enter your API key",
            placeholder: "sk-...",
            validate: (value) => {
              if (!value.startsWith("sk-")) return "Key must start with sk-"
            },
          },
        ],
        async authorize(inputs) {
          if (!inputs?.apiKey) return { type: "failed" }
          return { type: "success", key: inputs.apiKey }
        },
      },
    ],
  }
  return { auth }
}

export default plugin
The loader field (optional) is called to load existing credentials for the provider. methods supports "oauth" (browser-based redirect flow) and "api" (key/credential entry) types.

Example: log every tool call

import type { Plugin } from "@cyberstrike-io/plugin"

const plugin: Plugin = async (input) => {
  return {
    "tool.execute.before": async (toolInput, output) => {
      console.log(`Tool called: ${toolInput.tool}`)
    },
  }
}

export default plugin
Save this to .cyberstrike/plugin/logger.ts and CyberStrike will load it automatically on the next startup.

Build docs developers (and LLMs) love