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

@aicoo/dm-agent

v0.4.3

Published

Let a teammate ask your local agent. They DM you on Aicoo, a Claude Code session on your machine answers, and every file it opens is approved by you, one call at a time.

Downloads

1,545

Readme

@aicoo/dm-agent

Chat-rails local agent for Aicoo. Your peers DM you in Aicoo; this daemon feeds those messages to a local Claude session on your machine and DMs the answer back. It can read a folder you share and run commands you declared — and every one of those suspends until you, the owner, approve it.

No bridge, no endpoints, no grants, no parallel realtime stack: transport is 100% the existing Aicoo chat pipeline (GET /api/v1/conversations to read, POST /api/v1/agent/message human path to write). The invention budget is spent only where chat has no answer: the owner-approval gate and the local runtime control.

Quick start

npm install
AICOO_TOKEN=aicoo_sk_...  node src/cli.js start \
  --peer <their-username> \
  --workspace ~/shared-folder \
  --reachout "Hi — this is my local agent. Reply to this message to talk to it."

Run it in a terminal you keep open: tool-call approvals appear there as [y/N] prompts. Headless (no TTY)? Approvals become pending files you resolve from any terminal:

node src/cli.js pending --peer <their-username>
node src/cli.js approve <id> --allow --peer <their-username>

Semantics

  • Inbound: messages from the peer (senderType: human) in your direct DM thread and in the shared-agent thread. History is never replayed: on first sight of a conversation the client baselines its cursor and only processes newer messages.
  • Suspend-then-reply: text-only questions are answered immediately; a question that needs Read/Glob/Grep suspends mid-turn, asks the owner, and only replies after the tool result (or a denial) comes back. Approval timeout = deny, fail closed.
  • Hard walls under the approval: only Read/Glob/Grep and the owner's declared commands exist; every path is realpath-checked into a granted folder; .. patterns rejected; everything else (Bash/Write/Edit/Web) is disallowed at session launch, not just denied. Out-of-workspace and non-read attempts are refused by the wall without waking the owner — the prompt is reserved for calls that are actually in scope.
  • Why a PreToolUse hook, not just canUseTool: Claude Code's built-in rules auto-allow reads inside cwd, so canUseTool is never consulted for them (permissionMode: "dontAsk" goes further and resolves everything itself, auto-allowing in-cwd reads and auto-denying the rest). Either way the approval callback is dead code. The PreToolUse hook fires for every tool call regardless of permission rules, so the gate lives there; canUseTool stays as a second layer, and one shared memoized decision keeps the owner from being asked twice for the same call.
  • Untrusted framing: inbound content is wrapped as untrusted external material; the session's system prompt forbids treating it as instructions.
  • Loop safety: replies go out via the API and are stored as senderType: agent; the client only ever processes senderType: human rows, so two agents can never auto-reply to each other in this version (a future --a2a mode will relax this with a depth cap).
  • Memory: turns share one provider session (resumed by id, persisted in the state dir), so the conversation has continuity across messages and restarts.

Letting a peer run something (declared commands)

Reading files answers "what is configured". Some questions need "what happens when you run it" — and that is the one thing no cloud agent and no file transfer can substitute.

Write a policy.json next to the state file (aicoo-dm-agent start --policy <file> to put it elsewhere):

{
  "folders": ["~/aicoo-demo"],
  "commands": {
    "run-tests": { "argv": ["npm", "test"], "describe": "run the unit tests" },
    "docker-ps": { "argv": ["docker", "ps", "--format", "{{.Names}}"] }
  }
}

The peer's agent can invoke run-tests by name and nothing else. Each run stops for your approval, and the prompt names the command and its exact argv:

== OWNER APPROVAL REQUIRED ==
   tool: command:run-tests
   run "run-tests" (npm test) in ~/aicoo-demo
   allow? [y/N]

Why a declared list and not just Bash. With raw shell the approval line becomes an arbitrary command string, and allow? bash -c 'curl … | sh' gets a y often enough to make the gate decorative. A name plus the argv you wrote is a decision a person can actually make.

Why argv and not a string. A command written as "npm test" only means something once something splits it, and whatever splits it becomes the attack surface. The array is passed straight to execFile with no shell, so &&, pipes and $(…) are impossible by construction — a bare string is rejected at load rather than quietly run through a shell. If you need a pipeline, declare a script file.

The peer chooses a name; they cannot pass arguments.

Codex works too, through app-server

codex exec had no way in — anything its sandbox could not satisfy was refused on the spot, so the peer was told no on the owner's behalf without the owner hearing about it. Driving Codex through app-server turns that into a JSON-RPC request we answer, so a human decides on this runtime as well. (A turn survives a five-minute hold, so a real round trip fits.)

Two honest differences from the Claude path:

  • Granularity. Claude asks about every tool call. Codex's sandbox serves reads inside the shared folder silently and asks only about what it cannot serve — closer to a standing grant on the folder, which is defensible since that folder is what the owner shared, but it is not the same promise.
  • Only quote-free commands are offered. Codex composes its own shell string and re-quotes whatever the prompt gave it, so a declared command needing embedded quotes cannot be matched reliably in either direction. Those are skipped with a startup line saying so; wrap them in a script file and declare the script path. npm test, pytest -q, git status — the ordinary cases — work unchanged.

The same invariant holds on both runtimes: a command matching nothing the owner declared is refused without a prompt, so an arbitrary shell string never reaches the owner as a yes/no. That is not theoretical — on the first live run, Codex's own memory plugin tried to mkdir … && printf … into a directory outside the workspace, and the gate refused it silently.

Two things that leave the machine, and what stops them

  • Files — the path wall (realpath, symlink-safe) plus per-call approval, with the runtime's own OS sandbox underneath as defence in depth. --no-sandbox only if your platform cannot start one; the default is to fail rather than claim confinement it does not have.
  • The reply itself — a sandbox governs what a process may touch, never what a model may say. Values assigned to sensitively-named variables in env files under the granted folders are collected each turn and replaced in the outgoing reply with [redacted: value from .env] — marked, so you can see it fired.

Every decision lands in audit.jsonl next to the state file: who asked, what tool, what target, allowed or denied, and which rule decided.

Commands

| command | what | | --- | --- | | start --peer <p> [--workspace <dir>] [--policy <file>] [--poll <ms>] [--approve-timeout <s>] [--auto-allow-read] [--no-sandbox] [--reachout "…"] [--model <m>] | run the agent loop | | start --dry-run-message "…" [--workspace <dir>] | one local turn, prints the reply, no network send | | send --peer <p> --text "…" | one-shot DM (reachout) | | whoami | identity of AICOO_TOKEN | | pending / approve <id> --allow\|--deny | headless approval resolution (scope with --peer) |

Known limitations (MVP)

  • Split-thread quirk: replies land in the shared-agent thread ("your agent" in the peer's app), even when the peer asked in the plain DM. Single-thread needs a small server addition (API-key send into a direct conversation) — tracked as the next step.
  • One device per account: run the client on two machines and both will answer. (Needs an active-device lease server-side.)
  • Polling (default 3s), not push. Good enough for chat; an SSE upgrade is server work, not client work.
  • If the peer's message lands in the shared-agent thread and the account also has a cloud COO configured with credits, the cloud agent may answer too. Ask in the direct DM to avoid double replies until the server-side mute flag exists.