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

agsmith

v0.8.1

Published

Smith CLI — your extensible agent toolkit

Downloads

570

Readme

Agent Smith

Smith CLI — your extensible agent toolkit.

Installation

npm install
npm run build
npm link

Requires Node.js >= 18.

Configuration

All config and credentials live in ~/.agsmith/ (override the directory by setting AGSMITH_CONFIG_DIR).

~/.agsmith/
  config.json                               # Slack + OpenClaw + Outlook settings
  credentials/
    gmail-client.json                       # Gmail OAuth client secret
    gmail-user-readonly-token.json          # Gmail read-only token
    gmail-user-modify-token.json            # Gmail modify token
    outlook-token-cache.json                # Outlook (Microsoft Graph) MSAL token cache

Commands

agsmith config

agsmith config show       Show current config with secrets masked
agsmith config path       Print the path to the config file

agsmith slack

Setup

agsmith slack set api-key <value>         Set the Slack bot/user OAuth token (xoxb-... or xoxp-...)
agsmith slack set main-user-id <value>    Set the primary Slack user ID (e.g. U0123456789)

Commands

agsmith slack channel list                List all channels in the workspace
  --include-archived                    Include archived channels
  --private-only                        Show only private channels
  --public-only                         Show only public channels
  --wrap                                Wrap cell content in table output
  --truncate                            Truncate cell content to column widths

agsmith slack channel create <name>       Create a channel in the workspace
  --private                             Create as a private channel

agsmith slack send <channel> <text...>    Send a message to a channel

<channel> accepts a channel name with or without a leading #, or a channel ID. Names are resolved to an ID via conversations.list before posting, so the bot token needs channels:read (and groups:read for private channels) in addition to chat:write.


agsmith gmail

Setup

agsmith gmail import-credentials <path>   Copy OAuth client secret to ~/.agsmith/credentials/gmail-client.json

agsmith gmail auth                        Run the OAuth flow (read-only, default)
  --readonly                            Request read-only access (default)
  --modify                              Request modify access (required for label operations)

Tokens are saved automatically to ~/.agsmith/credentials/.

Commands

agsmith gmail list                        List Gmail messages
  --filter <filter>                     Gmail search query (e.g. "is:unread")
  --limit <n>                           Max messages to fetch (default: 20)
  --offset <n>                          Number of messages to skip (default: 0)
  --json                                Output JSON
  --wrap                                Wrap cell content in table output
  --truncate                            Truncate cell content to column widths

agsmith gmail message <message-id>        Show a single message by ID
  --json                                Output JSON
  --pdf [output-path]                   Render the message to a PDF file

agsmith gmail labels                      List all Gmail labels
  --json                                Output JSON
  --wrap                                Wrap cell content in table output
  --truncate                            Truncate cell content to column widths

agsmith gmail update-labels [message-ids...]   Add or remove labels on messages
  --filter <filter>                     Gmail search query to select messages
  --add <label-id>                      Label ID to add (repeatable)
  --remove <label-id>                   Label ID to remove (repeatable)
  --add-name <label-name>               Label name to add (repeatable)
  --remove-name <label-name>            Label name to remove (repeatable)
  --json                                Output JSON

agsmith gmail attachment list <message-id>             List attachments on a message
  --json                                             Output JSON

agsmith gmail attachment download <message-id> <attachment-id>   Download an attachment
  --output-dir <dir>                    Directory to write the file into (default: .)
  --filename <name>                     Override the output filename
  --json                                Output JSON

agsmith outlook

Read-only access to Outlook / Microsoft 365 mail via Microsoft Graph.

Setup

You'll need an Azure Entra app registration (free — see the Microsoft docs for "App registrations"). Register a public client / native app with the redirect URI http://localhost, enable "Allow public client flows", and grant the delegated permissions Mail.Read, User.Read, and offline_access. Then save your client ID:

agsmith outlook set client-id <azure-app-client-id>     Required
agsmith outlook set tenant-id <tenant-id>               Optional (default: "common"); set to a tenant
                                                        GUID if your app is single-tenant
agsmith outlook set token-cache-path <path>             Optional; override the MSAL token cache location

agsmith outlook auth                                    Run the OAuth flow in the browser

The MSAL token cache is saved automatically to ~/.agsmith/credentials/outlook-token-cache.json.

Commands

agsmith outlook list                       List Outlook messages via Microsoft Graph
  --filter <odata>                       Raw OData $filter expression
  --search <query>                       Free-text $search (subject/body/from/etc.)
  --from <address>                       Shortcut for $filter on from/emailAddress/address
  --to <address>                         Shortcut for $filter on toRecipients
  --subject <text>                       Shortcut for $filter contains(subject, ...)
  --in <folder>                          Well-known folder (inbox, sentitems, drafts,
                                         deleteditems, junkemail, archive)
  --unread                               $filter isRead eq false
  --starred                              $filter flag/flagStatus eq 'flagged'
  --has-attachment                       $filter hasAttachments eq true
  --before <date>                        $filter receivedDateTime lt <date> (YYYY-MM-DD)
  --after <date>                         $filter receivedDateTime ge <date> (YYYY-MM-DD)
  --on <date>                            $filter receivedDateTime within a single day
  --limit <n>                            Max messages to fetch (default: 20)
  --offset <n>                           Number of messages to skip (default: 0)
  --json                                 Output JSON
  --wrap                                 Wrap cell content in table output
  --truncate                             Truncate cell content to column widths

agsmith outlook message <message-id>      Show a single message by Graph ID
  --json                                 Output JSON
  --pdf [output-path]                    Render the message to a PDF file (inline cid: images
                                         and image attachments are embedded)

agsmith openclaw

Setup

agsmith openclaw set config-dir <path>      Set the path to the OpenClaw config directory
agsmith openclaw set default-model <model>  Set the default model for new agents

Commands

agsmith openclaw agent create <agent-name>

Creates an OpenClaw agent, a matching Slack channel, and binds them together. If the channel or agent already exists, the existing resources are reused.


agsmith iterm

Requires macOS with iTerm2 installed.

agsmith iterm list                         List all iTerm2 windows, tabs, and sessions,
                                           showing each session's agsmith tag
  --json                                 Output JSON

agsmith iterm new <session> [cwd] [command] [args...]
                                         Create a new tab, optionally cd and run a command

agsmith iterm read <session>             Read the visible screen contents of a named session

agsmith iterm rename <session> <new-name>
                                         Rename a named session

agsmith iterm run <session> <command> [args...]
                                         Run a command in a named session (sends Ctrl+C first)

agsmith iterm send <session> <text...>   Type raw text into a session (no Ctrl+C, no auto-Enter)
  -e, --enter                            Press Enter after the text

agsmith iterm send-ctrl <session> <keys...>
                                         Send control codes / named keys, in order
                                         Single letters become Ctrl+<letter> (`c` = Ctrl+C);
                                         named keys: enter, return, esc, tab, space,
                                         backspace, bs, newline
  --delay <ms>                           Delay between keys (default 400)

agsmith iterm split <direction> <target> [session]
                                         Split a named session horizontally or vertically
                                         <direction> is `horizontal` or `vertical`

agsmith iterm tag <target> <tag>         Tag a session so it stays addressable after its
                                         title changes
  --index                                Read <target> as a window.tab.session index from
                                         `iterm list` (session part defaults to 1)

Sessions created by iterm new and iterm split are tagged with the user.agsmithTerminal iTerm2 variable, and every <session> lookup above matches that tag before falling back to the session name — names get rewritten by whatever runs in the tab. See agsmith cc below.

Use iterm tag to adopt a session agsmith did not create, or one whose title has already drifted. Both of these tag the same session, after which TMS works as the <session> argument to every command above:

agsmith iterm tag "✳ TMS" TMS      # by current title, quoting it exactly
agsmith iterm tag 1.13 TMS --index  # by position: window 1, tab 13, session 1

Look-up by name only matches untagged sessions, since a tag always wins. To re-tag a session that already has one, pass its current tag as <target>, or address it by --index.


agsmith cc

Claude Code session helpers.

agsmith cc rc <path>                       Open a new iTerm2 tab, cd to <path>, and launch
                                           `claude --remote-control "<session>"`
  -t, --terminal <name>                  iTerm2 terminal to reuse (no new tab). If no such
                                         terminal exists, a new tab with this name is created
  -s, --session <name>                   Remote-control session name (default: <dirname>-MMDD)
  -p, --prompt <text>                    Initial prompt to send to Claude on startup
  -r, --resume [id]                      Resume this terminal's Claude conversation; pass a
                                         session id to resume a specific one

With no flags, a new tab is opened, named after the target directory, and the remote-control session is <dirname>-MMDD.

-t never restarts a Claude that is already running. What it does depends on the terminal:

| Terminal state | What happens | |---|---| | Does not exist | New tab is created, then claude --remote-control is launched in it | | Exists, running Claude | --prompt is typed into that Claude session; --session is ignored | | Exists, running Claude, no --prompt | Nothing — reports that Claude is already running | | Exists, plain shell | cds and launches claude --remote-control in it |

"Running Claude" is detected by looking for a claude process on the session's tty. The iTerm2 session.jobName variable is not usable for this: it reports the deepest child process, so a Claude session that has spawned an LSP or caffeinate reports that child instead.

Checking on sessions

agsmith cc status [terminal]              Report the state of Claude sessions in iTerm2
  --all                                 Include terminals not running Claude
  --json                                Output JSON
┌──────────────────┬─────────┬────────┬────────┬──────────────┬───────────────┐
│ Terminal         │ State   │ Remote │ Tagged │ Conversation │ Last activity │
│ copybara-1       │ idle    │ yes    │ yes    │ 8f6674ba     │ 1h 50m ago    │
│ ✳ TMS            │ idle    │ no     │ no     │ -            │ 10m ago       │
└──────────────────┴─────────┴────────┴────────┴──────────────┴───────────────┘

State is working when the status bar offers esc to interrupt, prompt when Claude is waiting on a menu choice (folder trust, MCP approval, resume size) and will answer nothing until it is dealt with, and idle otherwise. The status bar is the signal to trust: a replayed transcript can look mid-task on screen while Claude sits idle.

Unsent composer text is deliberately not reported. Claude renders inline autocomplete suggestions in the composer, and iTerm2 hands over screen contents with all styling stripped, so the dim text of a suggestion is indistinguishable from text you typed. Anything claiming to show unsent input would report Claude's own suggestions as your work.

Remote reports whether remote control is still attached — a session can be running and healthy with remote control dropped, which is what a disconnected session usually is. Last activity is the mtime of the conversation transcript.

Claude is launched as env -u CLAUDE_CODE_CHILD_SESSION CLAUDE_CODE_FORCE_SESSION_PERSISTENCE=1 claude .... A terminal that inherits CLAUDE_CODE_CHILD_SESSION starts Claude with its transcript disabled, which silently breaks --resume and Last activity — both read the transcript — so the variable is cleared and persistence forced at launch.

Reading a session's conversation id

agsmith cc session-id <terminal>          Read the id from Claude's /status panel and record it
  --no-record                           Print it without storing it on the terminal

This is the most reliable source: /status labels the id outright, and unlike the line Claude prints on exit — which is a session name for a named session, and a name is a picker search term rather than an id — it is readable while the session is running. It types /status into the session and dismisses the panel with Esc, so it only works on a live session. The recorded id is what -r uses afterwards, so capture it before exiting.

Restarting a session

Every session cc rc starts is given its conversation id up front (claude --session-id), and that id is stored on the terminal as user.agsmithClaudeSession. To restart a terminal whose Claude has exited, and get its conversation back:

agsmith iterm send-ctrl <terminal> c c --delay 600   # double Ctrl+C exits Claude
agsmith cc rc <path> -t <terminal> -r

Do not reach for claude --continue when a directory hosts more than one session: it resumes the most recently modified conversation for that directory, which is whichever session last wrote a message — not the one belonging to the terminal you ran it in. -r is unambiguous because the id is recorded per terminal.

-r also works when the terminal is gone entirely — the usual state of a session that shows as offline. It creates the tab and resumes in one step, identifying the conversation from the remote-control name, which is also its custom title:

agsmith cc rc ~/dev/ts-ird -t ts-ird -s ts-ird-0711 -r

-r resolves the conversation in this order: an id passed to --resume, the id recorded on the terminal, the resume line on the terminal's screen, the custom title matching --session, and finally the sole conversation for the directory. When none of those settle it, it lists the candidates so one can be passed to --resume <id> rather than guessing.

For a session agsmith did not start there is no recorded id, so -r reads the Resume this session with: claude --resume <value> line Claude prints on exit. Both exit paths print it, the id is stable across resumes, and -r records what it finds so later restarts do not depend on the line still being on screen.

That printed value is not always an id. A session carrying a custom title is echoed by nameclaude --resume "copybara-1" — and a name is a picker search term, not an id: handing it back to claude --resume opens an interactive list. -r instead maps the name to an id offline, by reading the {"type":"custom-title"} records in the transcripts under ~/.claude/projects/<cwd with / replaced by ->.

Names are reused, so a name can match several conversations — every restart under the same title adds one. -r lists the matches, uses the most recently modified, and tells you to pass --resume <id> to choose another:

Session name "copybara-2" matches 4 conversations; using the most recently modified:
  679c2f6b-d5ee-4b01-b8fe-d743f5760ee6  2026-08-10 07:42  <- using this
  5c3fa544-260a-496b-9ec1-8e13eb17ae3b  2026-08-04 13:31
  ...

Resuming a long conversation can prompt for confirmation — Claude warns when a session is large and offers to resume from a summary instead. -r cannot answer that for you, so check the terminal afterwards with agsmith iterm read <terminal>.

Terminals touched by cc rc, iterm new, and iterm split are tagged with the iTerm2 user variable user.agsmithTerminal, which -t and all iterm lookups match on first. Session names are rewritten by whatever runs in the tab — Claude Code retitles them to ✳ Claude Code and then to a summary of the conversation — so the tag is what makes a terminal reliably addressable. Untagged sessions, including any created by hand, still match on name.