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

@securecoms/agent-runtime

v0.7.0

Published

A first-party SecureComs agent. It attaches to the local SecureComs daemon and runs a BYO (bring-your-own) OpenAI-compatible LLM against incoming messages, with skills, per-channel memory, and a local tool gate — no AgentVault backend involved.

Readme

@securecoms/agent-runtime

A first-party SecureComs agent. It attaches to the local SecureComs daemon and runs a BYO (bring-your-own) OpenAI-compatible LLM against incoming messages, with skills, per-channel memory, and a local tool gate — no AgentVault backend involved.

Install and run

npm i -g @securecoms/agent-runtime
securecoms-agent --identity <identity_id>

<identity_id> (or SECURECOMS_IDENTITY) selects which local daemon connection this process serves.

Running it at login (service install)

securecoms-agent install --identity <identity_id> [--llm-endpoint <url>] \
  [--llm-model <model>] [--llm-key-file <path>] [--skills-dir <dir>] \
  [--data-dir <dir>] [--brave-key-file <path>] [--tavily-key-file <path>] \
  [--skills-config <path>] [--exec-path <node>]
securecoms-agent install --identity <identity_id> --dry-run
securecoms-agent uninstall --identity <identity_id>

Every install flag also reads its SECURECOMS_* variable from the environment when the flag is absent (SECURECOMS_IDENTITY, SECURECOMS_LLM_ENDPOINT, SECURECOMS_LLM_MODEL, SECURECOMS_LLM_API_KEY_FILE, SECURECOMS_SKILLS_DIR, SECURECOMS_DATA_DIR, SECURECOMS_BRAVE_KEY_FILE, SECURECOMS_TAVILY_KEY_FILE, SECURECOMS_SKILLS_CONFIG), so an operator who already exports the run-mode environment can just run securecoms-agent install. The flag always wins. --exec-path is the one exception: it is never taken from the environment. --dry-run prints the resolved Node path, the service file path, the log path, the key file, the brave/tavily key files and the skills config path — each as a PATH only, never a key value — and the rendered plist/unit without writing anything or calling launchctl/systemctl — use it to inspect an install before making it.

install writes a per-user service that starts the agent at login and restarts it on crash — a launchd LaunchAgent at ~/Library/LaunchAgents/com.securecoms.agent.<slug>.plist on macOS (logs at ~/Library/Logs/securecoms-agent-<slug>.log), a systemd --user unit at ~/.config/systemd/user/securecoms-agent-<slug>.service on Linux (journalctl --user -u securecoms-agent-<slug> -f). <slug> is the first eight characters of the identity when they are lowercase hex (the usual case: identities are UUIDs), otherwise the whole identity slugified to [a-z0-9-]. Any other platform is refused.

Only the non-secret settings you pass are baked into the service definition, under their SECURECOMS_* names; anything you omit falls back to the runtime's own default. The installer never accepts or writes an API key value — only --llm-key-file <path>, so no secret reaches the plist, the unit file, or a process listing. If --llm-key-file is given, install checks the file is readable and refuses otherwise — a key file the agent cannot read would make it exit 2 on every start, and the service manager would restart-loop that failure.

The Node interpreter baked into the service must survive a reboot and an upgrade, so install does not simply use the Node you ran it with: a path whose location encodes a version (a Homebrew Cellar keg, or nvm/asdf/fnm/volta/n) is resolved to a stable equivalent — /usr/local/bin/node, /opt/homebrew/bin/node, /opt/homebrew/opt/node/bin/node, /usr/bin/node, or ~/.local/bin/node, preferring one that is the same real file as the Node you ran, and requiring Node >= 22 on the running architecture. If no stable Node exists, install refuses and tells you to install one or pass --exec-path <path>, which overrides the choice outright.

Connection and identity

The runtime never reads a token from the environment. It reads the connection file at ~/.securecoms/connections/<identity_id>.json, which names a token file on disk; the token itself is read from that file, not passed on the command line or in an env var. Before any socket opens, the runtime checks a two-anchor identity gate: the identity baked into this process (from --identity/SECURECOMS_IDENTITY) must match both the identity recorded in the connection file and the identity the local daemon reports on its health endpoint. Any mismatch refuses the connection outright.

Configuration

All configuration is via SECURECOMS_* environment variables (see src/config.ts for the source of truth):

| Variable | Default | Purpose | | --- | --- | --- | | SECURECOMS_IDENTITY | — (or --identity <id>) | Which local daemon connection to serve | | SECURECOMS_LLM_ENDPOINT | http://127.0.0.1:11434 | Base URL of an OpenAI-compatible LLM server, with or without a trailing /v1 | | SECURECOMS_LLM_MODEL | llama3 | Model name to request | | SECURECOMS_LLM_API_KEY | unset | API key for the LLM endpoint, if it requires one | | SECURECOMS_LLM_API_KEY_FILE | unset (or --llm-key-file <path>) | File holding the API key (trimmed on read). Preferred: it wins over SECURECOMS_LLM_API_KEY, and it is the only key form the service installer accepts. An unreadable or empty file, or --llm-key-file given anywhere (argv or env), makes the runtime an EXPLICIT source; a resolution failure fails that turn only — the agent boots and logs llm: source unavailable — <reason>, it does not exit | | SECURECOMS_LLM_SOURCE | unset | Any value fails the LLM source closed with no read of the managed files. Reserved for a future bridge-provided source; not a customer-facing setting today | | SECURECOMS_BRAVE_KEY_FILE | unset (or --brave-key-file <path>) | File holding a Brave Search API key. A path, never a value — required for web_search | | SECURECOMS_TAVILY_KEY_FILE | unset (or --tavily-key-file <path>) | File holding a Tavily API key. Used only when the Brave key file is absent | | SECURECOMS_SKILLS_DIR | this package's own skills/ | Directory of skill manifests to load | | SECURECOMS_DATA_DIR | ~/.securecoms/agent-runtime | Root directory for per-channel state | | SECURECOMS_SKILLS_CONFIG | <dataDir>/skills.json (or --skills-config <path>) | Path to skills.json — named endpoints, databases and flags | | SECURECOMS_LOG_LEVEL | info | One of debug, info, warn, error | | SECURECOMS_TOOL_ALLOW | echo, web_search, fetch_url, http_request, sql_query, current_time | Comma-separated tool allow-list | | SECURECOMS_TOOL_DENY | unset | Comma-separated tool deny-list (wins over allow) | | SECURECOMS_TOOL_DEFAULT | deny | Fallback policy for a tool matched by neither list |

The LLM source: explicit vs managed

An explicit source (any of SECURECOMS_LLM_ENDPOINT, SECURECOMS_LLM_API_KEY, SECURECOMS_LLM_API_KEY_FILE, or --llm-key-file on argv) always wins and is read once per turn straight from the env/cfg above — the managed files are never touched. With none of those set, the runtime instead looks for ~/.securecoms/runtime-config/<identityId>/llm-source.json ({provider, model}) plus, when that provider needs one, llm.key and llm.key.host beside it — written by the desktop app (or, later, the bridge), never by this runtime. The endpoint is always derived from a compiled provider table, never read from the managed file, and llm.key is refused unless its llm.key.host sidecar names the same host the provider resolves to. Any resolution failure in either tier fails just that turn; it never crashes the process.

Memory

Memory is local only, never sent anywhere but the configured LLM endpoint, and scoped per channel under <dataDir>/channels/<channel>/ — one channel's history and captured memories never leak into another channel's turn.

Adding a skill

A skill is either a JSON manifest or a SKILL.md file, dropped under SECURECOMS_SKILLS_DIR in one of three layouts:

  • flat <skillsDir>/<name>.json
  • <skillsDir>/<dir>/manifest.json
  • <skillsDir>/<dir>/SKILL.md

All three keep working side by side. The loader reads them in that order — flat .json, then manifest.json, then SKILL.md — sorted within each bucket for a deterministic result; if two files declare the same skill name, the one loaded later (later bucket, or later alphabetically) wins, with a warning naming both paths.

A SKILL.md is YAML frontmatter (between two --- lines) followed by free-text guidance appended to the model-facing description (capped at 1 kB). The frontmatter accepts a deliberately small subset of YAML — anything else throws with the file and line, rather than silently misreading it:

  • scalars (key: value, optionally quoted)
  • flow lists (key: [a, b, c])
  • flow maps (key: { a: 1, b: 2 })
  • nested mappings (an indented block under a bare key:), two-space indents only, no tabs
  • comments (#) and blank lines
  • block scalars (|, >), anchors, aliases and other YAML constructs are not supported — use a JSON manifest for anything that needs them

Example:

---
name: web_search
description: Search the web for recent information
parameters:
  type: object
  properties:
    query: { type: string }
    count: { type: number }
  required: [query]
handler: builtin:web_search
requires: [search_provider]
---
Use this when the user asks about something time-sensitive or outside your
training data. Prefer a specific query over a broad one.

name, description, parameters (a nested mapping) and handler are required. handler is one of:

  • builtin:<name> — one of the runtime's built-in handlers (see "Curated skills" below).
  • script:<path> — an executable file, path relative to skillsDir and contained within it (symlinks included — a link that resolves outside skillsDir is refused). It runs with its arguments as JSON on stdin, cwd set to skillsDir, and a minimal environment (no inherited secrets).

A manifest or SKILL.md with any other handler shape, or a missing required field, is skipped with a warning, and a skill invocation that throws is turned into a tool_failed tool message rather than dropping the turn.

Gating a skill on configuration

A manifest or SKILL.md may carry requires: [...], and the skill is offered to the model only when every entry is configured: endpoint:<name> and database:<name> name entries in skills.json, search_provider means a readable, non-empty search key file, any_endpoint / any_database mean at least one endpoint or database is configured, and anything else is matched against skills.json's flags.

The shipped http_request and sql_query skills declare requires: [any_endpoint] and requires: [any_database]: the endpoint and database names belong to the customer, so the shipped copies cannot name one, but neither is offered to the model until skills.json gives it something to reach. With no endpoints configured, startup prints unavailable: http_request (missing any_endpoint). To narrow one to a particular system, copy the folder and change the line — for example requires: [endpoint:crm] in skills/http_request/SKILL.md.

Curated skills

Six built-in handlers ship with the runtime. All network built-ins refuse localhost and any private, loopback or link-local resolved address, and every tool result is capped at 20 kB (truncated, not an error). A redirect to a different origin is followed without the caller's headers and without the request body, so a configured header value or a search key can never be handed to a host the operator did not name.

| Skill | Arguments | Notes | | --- | --- | --- | | echo | text | Returns the text unchanged. No requires. | | current_time | timezone (an IANA name such as America/New_York; defaults to UTC) | The server clock, ISO 8601. No requires. | | fetch_url | url | Fetches a page and returns extracted text (HTML is stripped to text). Up to 3 redirects, 10 s timeout, 2 MB response cap. | | web_search | query, count (1–10, default 5) | requires: [search_provider] — needs a readable, non-empty SECURECOMS_BRAVE_KEY_FILE or SECURECOMS_TAVILY_KEY_FILE; Brave is used first when both are set. | | http_request | name (a configured endpoint:<name>), path (a query string is kept, a #fragment is dropped), method (GET/POST/PUT/DELETE), body | requires: [any_endpoint]. The model names an endpoint and a path, never a host; a path that would leave the endpoint's baseUrl origin is refused. | | sql_query | name (a configured database:<name>), sql (read-only SELECT only; a ; or a write keyword anywhere in the text — inside a string literal or a comment included — is refused) | requires: [any_database]. SQLite or Postgres (pg required separately for Postgres). Results capped at 200 rows. |

skills.json

skills.json names the endpoints and databases the curated skills are allowed to reach, plus arbitrary boolean flags for gating. It carries paths, never secrets — a header value or a Postgres connection string is read from the file it names, at call time, and never logged. Relative paths (a sqlite, postgresUrlFile or headersFile value) resolve against the data dir, not the current working directory.

{
  "endpoints": {
    "crm": { "baseUrl": "https://crm.example.com/api", "headersFile": "crm-headers.json" }
  },
  "databases": {
    "reports": { "sqlite": "reports.db" },
    "warehouse": { "postgresUrlFile": "warehouse-url.txt" }
  },
  "flags": ["beta_tools"]
}

A headersFile is a flat JSON object of header name to string value, read fresh on every http_request call — never cached, never logged. A Postgres database needs the optional pg package installed separately (npm i -g pg); SQLite needs nothing extra (node:sqlite).

skills.json may also carry a tools block — {"tools": {"allow": [...], "deny": [...]}} — the owner's on/off choices for the curated skills. A skill that is switched off is not offered to the model at all. The environment wins: see "Managed configuration" below.

By default skills.json lives at <dataDir>/skills.json; point elsewhere with SECURECOMS_SKILLS_CONFIG or --skills-config <path>. A managed copy (see below) wins over both. A missing file means no endpoints, no databases and no flags — not an error.

Persona and memory

Three optional files make up an agent's persona, looked up along the config search path (see "Managed configuration" below) and read fresh on every turn — no restart needed after an edit:

| File | Heading | | --- | --- | | IDENTITY.md | ## Identity | | SOUL.md | ## Soul | | AGENTS.md | ## Operating rules |

Whichever of the three exist are joined, in that fixed order, under their fixed headings; each is capped at 8 kB (truncated, with a warning, if larger). If none exist, a built-in default prompt is used instead. Either way, a fixed, non-removable safety preamble is appended last, so it is the part of the prompt read most recently.

securecoms-agent persona init [--identity <id>] [--data-dir <dir>] copies the shipped default persona files into the data dir — it never overwrites a file that already exists, so a customized IDENTITY.md is safe to keep across a re-init. securecoms-agent persona show prints the assembled system prompt exactly as a turn would see it, useful for checking an edit landed.

Legacy MEMORY.md files are not active configuration. Earlier runtime versions injected one agent-wide file into every channel, which crossed channel audiences. On startup the runtime moves each legacy file to a mode-0600 MEMORY.quarantined*.md file in the same directory without overwriting an existing quarantine. The quarantined content is never injected or writable by a tool. The runtime inventories that quarantine plus inactive per-channel JSON stores for the owner-review surface. Review offers a passphrase-encrypted export and exact-source deletion; the 90-day clock begins only after the machine successfully reports the source. Explicit fact-to-channel assignment is a separate migration step and never guesses or fans out an audience.

Managed configuration (from the web app)

When an agent is managed from the SecureComs web app, the owner's saved configuration is written to disk by the customer's daemon at:

~/.securecoms/runtime-config/<identity-id>/

The runtime reads its configuration along a search path: that managed directory first, then the data dir. The lookup is per FILE, so an owner who saved only IDENTITY.md in the app keeps the machine's own SOUL.md; a file with no managed copy still comes from the data dir, and an agent with no managed directory at all behaves exactly as it did before. skills.json is looked up the same way, and re-read on every turn — a new endpoint or a skill switched off in the app takes effect on the next message, with no restart.

One deliberate exception:

  • The environment still wins for the tool policy. SECURECOMS_TOOL_ALLOW overrides the allow list in the tools block of skills.json, so an install that pins its allow list in its service unit is unaffected by a save in the app. SECURECOMS_TOOL_DENY is UNIONED with the file's deny list rather than replacing it: deny is a safety mechanism, so one entry in the service unit must not silently un-deny every tool the owner switched off in the app. Everything the environment denies stays denied either way.

Runtime-owned writes go to the data directory, not the managed directory. Legacy MEMORY.md is handled only by the quarantine behavior described above.

The runtime also writes mode-0600 local-config.json into the managed directory when one exists: what this agent's OWN persona files say, booleans for readable search/managed-LLM keys, and a bounded inventory of inert legacy-memory files. It is written at startup and rewritten whenever those answers or the inventory change. The daemon revalidates current regular-file bytes, removes local paths, and only then reports it upward. Nothing in the runtime reads the report back, and it holds no key value.

Per-turn channel authorization

Installation configuration is only a ceiling. On every inbound message the bridge supplies an immutable, server-derived authorization claim containing the sender audience, membership state, and channel tool grants. The runtime intersects that claim with installed skills, configuration requirements, and the local allow/deny gate before showing tools to the model, then repeats the same channel-grant check immediately before execution. Missing or malformed claims, inactive/unknown membership, and absent grants all fail closed.

This staged release issues no channel grants yet, so no generic tool is offered even when it is installed and enabled. Read grants can enable only the curated read-only tools; http_request is additionally limited to GET at execution. Side-effect grant fields are reserved in the protocol, but side-effect calls remain denied until exact-operation confirmation, expiry, and idempotency are implemented end to end.

The generated tool paragraph is an installation summary

The AGENTS.md the app delivers ends in a generated paragraph naming the tools configured for the installation, and explicitly tells the model that the per-turn offered list is authoritative. The backend derives the installation list from saved configuration plus the search-key boolean above; it does not see the running process or a turn's channel grants. These things can therefore make the installation summary differ from what the runtime offers:

  • a SECURECOMS_TOOL_ALLOW or SECURECOMS_TOOL_DENY set in the service unit, which the backend cannot see;
  • a customer skill folder whose requires: is narrower than the shipped one, so the runtime withholds a tool the backend counted;
  • a custom skill the backend knows nothing about, so it can be eligible but unnamed;
  • the current channel has no matching server-issued read grant.

The paragraph is therefore an advisory configuration summary, not an authority claim. Channel authorization and the local gate are both enforced at call time, so a configured but ungranted or denied tool is still refused.

Provenance

See PROVENANCE.md for which files were ported from agentvault-federal's reference agent-runtime package and how the AgentVault package-dependency cord was cut.