npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dsh-keyword-injector

v0.6.0

Published

Context injection + behavioural guards for DSH: keyword triggers on user input or agent activity, tool-window guards, and agent-manageable trigger tools. POWERFUL AND DANGEROUS: lets AI agents shape each other's behaviour at any step — read the warnings b

Readme

dsh-keyword-injector

Keyword-triggered context injection for the DeepSeek Harness (DSH).

Define keywords once; the plugin watches a session and, when a trigger fires, injects your payload into the conversation as a Context injection message — the same delivery surface memos-local-memory uses. The reminder renders in the Web GUI as a collapsed Context injection · dsh-keyword-injector row and reaches the model with its next request. The agent's flow is never interrupted: no turns are started, nothing is steered, no extra model calls are made for the delivery itself.

user:  "please deploy to staging now"
       └─ Context injection · dsh-keyword-injector   ← collapsed row, model sees it too
          [KEYWORD-INJECTOR REMINDER] …rules you configured… [END KEYWORD-INJECTOR REMINDER]
agent: (answers with your rules applied)

⚠️ DANGER — read this before you use it

This plugin lets AI agents shape the behaviour of other AI agents — at any moment, in any session, persistently across restarts. Trigger rules are global by default (all-sessions), survive daemon restarts, and are delivered to the model with a "configured by the user" trust framing. One injected payload silently re-programs every session it reaches. If you would not hand a stranger your system prompt, do not hand them this plugin's write tools.

This plugin injects instructions into your agent's context automatically. That is its job — and exactly why it must be treated like loaded weaponry. Misconfigured, it can be turned against you.

0. Agent-to-agent coordination is the sharpest edge. The keyword_injector_* tools are registered for every agent session in the profile. Any agent — an executor on a task card, a subagent, an agent processing untrusted input — can create, edit, or delete triggers, and those changes apply to all other agents, including orchestrators and supervisors. Two agents can thus establish a private instruction channel: agent A plants a trigger keyed to agent B's vocabulary, and B obeys A's payload believing it came from you. A manipulated agent can also delete your guardrails while telling you everything is fine. In multi-agent setups, audit the trigger list (keyword_injector_list_triggers, or the Keywords panel) after any session that touched untrusted content, and treat unexpected triggers as incidents.

1. Agent-source triggers match EVERYTHING your agent reads. The agent source scans reasoning, tool calls, and tool results. Tool results include web pages, file contents, code comments, task cards, and messages from other agents. Untrusted content can deliberately contain your keyword and fire your trigger — an attacker who knows your keyword can inject your payload at a moment of their choosing. If any session with an agent-source trigger fetches external content, assume the trigger is remotely fireable by strangers.

2. Triggers are a covert instruction channel. Injected reminders render as a collapsed row and carry implicit "configured by the user" trust. Anyone who can edit your trigger list — via the settings file, the GUI, or the agent tools — can silently shape every session's behavior. Treat trigger configuration with the same seriousness as your system prompt.

3. The agent can edit its own triggers. This plugin registers write tools (create/update/delete) for the model. A manipulated agent can be talked into editing its own triggers — including removing your guardrails. In untrusted workflows, audit the trigger list afterwards, or don't expose the tools.

4. Broad matches = context flooding and cost amplification. contains: "the" or regex: .* on the agent source fires every turn, stuffing your payload into every request — token spend, degraded reasoning, pushed-out context. Keep keywords narrow; check the live-test panel; prefer exact for common words.

5. Payloads inherit user trust. Whatever you write in a payload, the model is told the user configured it. Never put secrets in payloads. Never configure payloads on behalf of someone you don't fully trust. A steer-skill trigger auto-loads and runs skill instructions — that's code execution adjacent.

Hard rules we recommend:

  • Only edit triggers from a trusted seat (your machine, your settings file).
  • Prefer user-source triggers; use agent-source deliberately, with narrow keywords, on sessions that handle trusted input.
  • Never use keywords likely to appear in code, data, or fetched content.
  • Audit ~/.dsh/settings.yaml (namespace dsh-keyword-injector) after letting agents near untrusted content.
  • Keep payloads short, factual, and secret-free.

The plugin itself sends no network traffic and stores no secrets — but it automates instruction delivery. The safety of that automation is entirely in how you scope your triggers.

Why two trigger sources

Most injectors only watch user input. Real prompts often don't contain the word that matters — the agent's own reasoning does:

User: "resolve the issue" → agent thinks "I need to triage this first" → *triage rules arrive on their own.

| Source | Watches | Fires when | |---|---|---| | user (default) | What the user types | The triggering message lands in the session inbox — the reminder joins that message's step (reminder first, one model request) | | agent | The agent's own reasoning and actions (assistant messages incl. thinking, tool calls + arguments, tool results) | The agent's step boundary — e.g. it thinks "I need to triage this" mid-task and the rules are injected for its next step of the same turn |

The matcher enforces source isolation (user triggers never fire on agent text and vice versa), and agent-source triggers fire once per turn per trigger — the agent echoing the keyword after reading the rules never re-triggers.

Real use cases

  1. Standing rules on demand — trigger Follow the rules (user source, static-text) injects your personal operating law: keep the session to-do list updated, use SEQUENTIAL THINKING for long problems, escalate to the advisor when stuck, never work around broken system tools, clean up leftover scripts. Three words typed; the full rulebook arrives as context.
  2. Skill steering — trigger context manager (user source, steer-skill) injects "load the context-manager skill" — the agent picks up the whole methodology without pasted instructions.
  3. Triage rules the agent asks for itself — the agent-source example above: user says "resolve the issue", the agent's own reasoning matches triage, rules arrive at its next step.
  4. Deploy guardrail — trigger deploy to staging rides a smoke-test reminder along with every matching request, in every session.

Trigger model

| Field | Values | Notes | |---|---|---| | keyword | any text (≤ 200 chars) | what to match | | matchType | exact / contains / regex | regex validated at save time; ReDoS-shaped patterns (e.g. (a+)+) rejected up front; runtime matcher has a sample cap + budget as a second guard; case-sensitivity toggle | | mode | static-text / steer-skill | inject a verbatim payload, or an instruction to load a named skill (validated against the live skills catalog at save time) | | source | user / agent | see above | | scope | all-sessions / this-session | this-session only settable from inside a session | | enabled | on/off | toggle straight from the table |

Payloads are wrapped in fixed [KEYWORD-INJECTOR REMINDER] … [END KEYWORD-INJECTOR REMINDER] delimiters and sanitised (invisible control bytes stripped), so user-supplied payload text can never pose as system instructions — the model always sees it as data inside a marked block.

UI

Two surfaces, same manager:

  1. Sidebar → Keywords — a main panel alongside Task Board / SSH / Skill Center.
  2. Settings → Keyword Injector.

Both provide: trigger table (enabled toggle, keyword, match type, mode, source, payload preview, scope, edit/delete), filter box, and an edit modal with a live test panel — a host-side dry run using the exact runtime matcher, so the panel is always honest about what will fire.

Agent tools

The model can manage triggers itself (useful for self-configuring agents — and dangerous; see warning 0 above):

  • keyword_injector_list_triggers
  • keyword_injector_create_trigger (skill targets validated)
  • keyword_injector_update_trigger
  • keyword_injector_delete_trigger
  • keyword_injector_test_trigger

There is no allowlist, approval step, or per-session restriction on these tools: any agent in the profile can rewrite the rulebook of every other agent. If you only want you to configure triggers, audit the list frequently and treat unexpected triggers as incidents.

Guards (behavioural triggers)

Besides keyword triggers, the plugin ships guard triggers — conditions over session behaviour rather than message text. Guards carry no keyword; they are evaluated at each agent step and fire (subject to a cooldown, in tool-call steps) by injecting their payload as a binding reminder.

| Guard | Condition | Fires when | |---|---|---| | todo-compliance | { window: N } | N tool calls passed with no todo_write / taskboard_* in the window — nudges the agent to maintain its in-session task list | | orphan-process | — | a background job/agent exists with no reminder/schedule covering it — nudges the agent to set a reminder before answering | | tool-window (v0.4.0) | { window: N, noneOf?: [tool globs], anyOf?: [tool globs], cooldownSteps } | over the LAST N tool calls: no call matched any noneOf glob, and (when anyOf is set) none of those appeared either |

tool-window examples: {window: 8, noneOf: ["mcp__*sequentialthinking*"]} = "reason step-by-step if you haven't for 8 calls"; {window: 15, anyOf: ["skill"]} = "load the matching skill if you haven't recently". Any "do X whenever the agent hasn't done Y recently" rule becomes a one-line config. Guards keep a bounded 64-name ring buffer of recent tool calls per session; globs are cached, not recompiled per step.

Guard limits (by design): the engine sees tool names only — not tool arguments, not whether a loaded skill is semantically right for the job, not wall-clock time. Those need an LLM judge or a scheduler; deliberate scope cut.

Install

On npm as dsh-keyword-injector (beta channel):

dsh plugin --profile web add dsh-keyword-injector@beta

or with pnpm directly:

cd ~/.dsh/profiles/web
pnpm add dsh-keyword-injector@beta
# then add "dsh-keyword-injector" to dsh.profile.bundles in package.json

Prefer the source? Install straight from GitHub:

dsh plugin --profile web add github:markelayan/dsh-keyword-injector

Local-development (link:) installs: clone anywhere, run npm install (pulls schemastery), then dsh plugin --profile web add link:<path>. Note for link: users: DSH resolves a plugin's imports from its realpath, so the cloned copy needs its own node_modules (the npm install above provides it).

Restart dsh web and hard-refresh the browser after installing.

Requires a DSH core from the 0.1.1-rc line (tested on 0.1.1-rc.2 and 0.1.6-alpha.2) — see Compatibility for the full matrix.

Architecture

lib/
├── index.js            host half — events, persistence, HTTP routes, agent tools
├── client.js           client half SOURCE (sidebar entry, panel, settings page)
├── client.bundled.js   SHIPPED client — esbuild bundle in ModuleLoader format
├── schema.js           trigger validation (all trust boundaries)
├── matcher.js          runtime matching (regex cache, budgets)
└── reminder.js         reminder text formatting + sanitisation
  • Persistence: settings namespace dsh-keyword-injector (ends up in ~/.dsh/settings.yaml), registered on boot with a permissive schemastery schema; every read passes strict validateDocument, so a corrupted store degrades to an empty trigger list — never a crash.
  • Client ↔ host: bundle clients have no host.call bridge, so the UI talks to plain HTTP routes on the host: POST /api/dsh-keyword-injector/{list,create,update,delete,test,skills}.
  • Delivery: reminders are durable, plugin-sourced user/message events inserted through the agent inbox (agent.inject() → next-step queue), so they are claimed together with the triggering message — reminder first — and render as Context injection rows. The inbox claims exactly one next-turn message per turn, so the plugin never prepends to that queue (that would strand user messages).
  • Events used: agent/inbox/inserted (user source) and agent/pre-step (agent-source scanner).

Rebuilding the client

The browser loader requires a single self-contained window.__ModuleLoader__.load file, so the client ships bundled. After editing lib/client.js:

npm install                      # once
npx esbuild lib/client.js --bundle --format=cjs \
  --external:react --external:react-dom --external:react-dom/client \
  --outfile=/tmp/kwi.cjs
# re-wrap with the ModuleLoader factory — see the exact steps in the header
# comment of lib/client.bundled.js

Then restart dsh web and hard-refresh.

Security & privacy

  • No network calls. The client fetches same-origin DSH routes only; the host touches local files (the DSH settings store) and in-process APIs. No telemetry, no analytics, no outbound requests, ever.
  • No secrets. The plugin stores no credentials and reads none.
  • Injection-hardened. Reminder payloads are fenced in constant delimiters, sanitised, and length-capped; the keyword itself is sanitised before embedding. The model is told, in-band, that the block is configured by the user — not instructions from the payload author.
  • ReDoS guards. Regex triggers are shape-checked at save time and sample-capped + budgeted at match time.
  • Self-matching impossible. The scanner reads only genuine user input or assistant/tool events — never the plugin's own injections — and fires at most once per message (user source) or per turn (agent source).

Compatibility

| Requirement | Details | |---|---| | DSH core | 0.1.1-rc+ required; tested on 0.1.1-rc.2 and 0.1.6-alpha.1/alpha.2. Uses tools/pre-execute hooks, agent.inject() inbox delivery, settings namespaces, web bundle loading, and ctx.webServer HTTP routes — stable across those versions. The pre-0.1.6 agent.session.event hook is NOT used (removed in dsh 0.1.6). | | Install shape | Standard DSH bundle plugin: declares dsh.bundle.patch: ./cordis.patch.yml and mounts its own insert row into the profile — never add a second - insert: for this id in the profile's main cordis.patch.yml (duplicate insert ids crash dsh web on boot). | | Runtime | Node ≥ 18 (ESM). No native deps; the only runtime dep is schemastery. | | Client | Web GUI bundle (esbuild → ModuleLoader format). Sidebar Keywords panel + Settings page; hard-refresh the browser after install/upgrade. | | Storage | Trigger list lives in the dsh-keyword-injector namespace of ~/.dsh/settings.yaml — plain YAML, survives restarts, included in normal settings backups. | | Network | None. No outbound calls, no telemetry. All matching is local. | | Upgrades | Update the package in ~/.dsh/profiles/web, then restart dsh web (user-only) — bundle source changes never hot-reload; only the patch file does. Trigger data is untouched by upgrades. |

Changelog

See CHANGELOG.md. MIT license — see LICENSE.