agentsclimarketplace

Create container plugin agent hooks

Skill yukihirop/nagi/.claude/skills/create-container-plugin-agent-hooks

Scaffold a new agent-hooks plugin for nagi containers. Generates index.mjs with hook factories and deploy/templates/container/claude-code/entry.template.ts registration. Triggers on "create agent hooks plugin", "new agent hooks", "scaffold agent hooks".From its SKILL.md

Install
npx -y skills add yukihirop/nagi --skill create-container-plugin-agent-hooks

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

6.8 KB, ~1.6k tokens by cl100k_base, as published. Nobody here has run it

Create Agent-Hooks Plugin

Step 0: Language selection

Before proceeding with any other steps in this skill, ask the user which language to continue in using AskUserQuestion. Keep this initial prompt in English because the preferred language is not yet known.

  • Question: Which language should I continue in?
  • Options: English, 日本語 (Japanese)

Use the selected language for all subsequent user-facing messages and for every further AskUserQuestion prompt in this skill. Do not translate code, file paths, shell commands, or file contents.

Scaffold a new agent-hooks plugin that runs inside agent containers and sends notifications to chat channels via IPC, following the established pattern (agent-hooks).

UX Note: Use AskUserQuestion for all user-facing questions.

Step 1: Gather information

AskUserQuestion:

  1. Plugin name (lowercase — e.g., "open-code", "cursor", "windsurf")
  2. One-line description (e.g., "Tool execution and session notifications for Open Code")
  3. Which hook types to support? (PostToolUse, SessionStart, or both)

The full plugin name will be agent-hooks-{name}.

Step 2: Choose target agent

AskUserQuestion: Which agent should this plugin be created for?

  • Claude Codecontainer/claude-code/plugins/agent-hooks-{name}/
  • Open Codecontainer/open-code/plugins/agent-hooks-{name}/
  • Both — Create in both

Step 3: Generate plugin

Create the plugin in the selected directory with a single file:

index.mjs

Agent-hooks plugins are pure JavaScript ES Modules — no TypeScript, no build step, no package.json.

Generate from this template:

/**
 * Agent Hooks: {Name}
 * {description}
 */

import fs from "node:fs";
import path from "node:path";

const MESSAGES_DIR = "/workspace/ipc/messages";

function writeIpcMessage(chatJid, groupFolder, text) {
  fs.mkdirSync(MESSAGES_DIR, { recursive: true });
  const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}.json`;
  const filepath = path.join(MESSAGES_DIR, filename);
  const tempPath = `${filepath}.tmp`;
  fs.writeFileSync(tempPath, JSON.stringify({
    type: "message",
    chatJid,
    text,
    groupFolder,
    timestamp: new Date().toISOString(),
  }));
  fs.renameSync(tempPath, filepath);
}

If PostToolUse is supported, add:

const DEFAULT_SKIP_TOOLS = ["mcp__nagi__send_message", "mcp__nagi__list_tasks"];

export function createPostToolUseHook(chatJid, groupFolder, extraSkipTools, log) {
  const skipTools = new Set([...DEFAULT_SKIP_TOOLS, ...(extraSkipTools ?? [])]);
  return async (input) => {
    try {
      const name = input.tool_name;
      log(`[hook:PostToolUse] tool=${name} chatJid=${chatJid}`);
      if (!name || !chatJid || skipTools.has(name)) return {};
      // TODO: Customize tool display format for {name}
      const text = `\u{2699}\uFE0F \`${name}\``;
      writeIpcMessage(chatJid, groupFolder, text);
      log(`[hook:PostToolUse] sent: ${text}`);
    } catch (err) {
      log(`[hook:PostToolUse] error: ${err}`);
    }
    return {};
  };
}

If SessionStart is supported, add:

export function createSessionStartHook(chatJid, groupFolder, log) {
  return async (input) => {
    try {
      log(`[hook:SessionStart] chatJid=${chatJid} source=${input?.source}`);
      if (!chatJid) return {};
      writeIpcMessage(chatJid, groupFolder, "\u{1F4AD} Thinking...");
      log("[hook:SessionStart] sent thinking message");
    } catch (err) {
      log(`[hook:SessionStart] error: ${err}`);
    }
    return {};
  };
}

Replace {name}, {Name}, {description} placeholders.

Step 4: Add to container entry.template.ts

AskUserQuestion: Which agent's entry.template.ts should register this plugin?

  • Claude Codedeploy/templates/container/claude-code/entry.template.ts
  • Open Codedeploy/templates/container/open-code/entry.template.ts (create if missing)
  • Both — Add to both

Add a new try/catch block after any existing agent-hooks blocks:

try {
  const pluginPath = "/app/agent-plugins/agent-hooks-{name}/index.mjs";
  const agentHooks = await import(/* webpackIgnore: true */ pluginPath);

  plugins.push({
    name: "agent-hooks-{name}",
    createHooks: (
      chatJid: string,
      groupFolder: string,
      hooksConfig: { postToolUse?: boolean; sessionStart?: boolean; skipTools?: string[] } | undefined,
      log: (msg: string) => void,
    ) => ({
      // Include PostToolUse if supported:
      ...(hooksConfig?.postToolUse !== false ? {
        PostToolUse: [{ hooks: [agentHooks.createPostToolUseHook(chatJid, groupFolder, hooksConfig?.skipTools, log)] }],
      } : {}),
      // Include SessionStart if supported:
      ...(hooksConfig?.sessionStart !== false ? {
        SessionStart: [{ hooks: [agentHooks.createSessionStartHook(chatJid, groupFolder, log)] }],
      } : {}),
    }),
  });
} catch {
  // Plugin not available, skip
}

Remove hook type entries that were not selected in Step 1.

Step 5: Verify

No build step needed — agent-hooks plugins are pure .mjs files loaded at runtime.

The plugin directory is automatically mounted into containers at /app/plugins/ by the orchestrator (via container/plugins//app/plugins/ bind mount).

Verify TypeScript still compiles:

pnpm exec tsc --noEmit

Step 6: Next steps

Tell the user:

  1. Implement hooks — Edit container/plugins/agent-hooks-{name}/index.mjs to customize notification format and behavior
  2. Sync container entry — Run /deploy to add the plugin to your local entry.ts (select the agent you chose)
  3. Rebuild Docker image./container/claude-code/build.sh and/or ./container/open-code/build.sh
  4. Restart nagi — Run /nagi-restart
  5. Test — Send a message in Slack/Discord that triggers tool use

Key design rules

  • Pure JavaScript — No TypeScript, no build step. Single index.mjs file
  • IPC messaging — Write JSON files to /workspace/ipc/messages/ for the host to consume
  • Atomic writes — Always write to .tmp then rename (prevents partial reads)
  • Never throw — Wrap hook logic in try/catch, log errors and return {}
  • Skip internal tools — Always skip mcp__nagi__send_message to avoid notification loops
  • Export factory functionscreatePostToolUseHook() and/or createSessionStartHook() that return async hook callbacks

Reference

Existing agent-hooks plugins to study:

  • container/claude-code/plugins/agent-hooks/ — PostToolUse (tool icons, summary) + SessionStart ("Thinking...")

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,144. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.