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

@fulldivevr/workcord-cli

v1.3.0

Published

Agent-first CLI using persistent Hermes tenant chat, with advanced API diagnostics.

Readme

@fulldivevr/workcord-cli

TypeScript CLI for talking to WorkCord agents through persistent Hermes tenant chat and using the WorkCord API. One invocation = one user action. Designed for automated agents such as Claude Code: ask the user's Assistant for the outcome you need, let that tenant manage its own instructions, skills, and flavors, then inspect its reply. Route-level commands and advanced diagnostics remain available.

Lives in the backend repo (clients/workcord-cli) so the spec it generates from is always the spec this server serves.

Install

npm i -g @fulldivevr/workcord-cli
# or run without installing
npx @fulldivevr/workcord-cli --help

Configuration

| Setting | How | | --- | --- | | Base URL | WorkCord production by default; override with saved configuration, WORKCORD_API_URL, or --base-url <url> | | User token | Saved by auth login --save, or supplied by WORKCORD_TOKEN | | Signing secret | WORKCORD_JWT_SECRET env var only — never a flag; privileged, enables --as impersonation | | Internal admin key | WORKCORD_INTERNAL_API_KEY, internal commands only | | Acting user | --as <userId>, per invocation — only with WORKCORD_JWT_SECRET |

Base URL must be a bare origin (scheme + host, no path). The /api/v1 prefix is added by the CLI. Resolution order is --base-url, WORKCORD_API_URL, saved configuration, then the WorkCord production default.

Your deployment's origin comes from whoever operates it. A local server is http://localhost:3001 (WorkCord) or http://localhost:3000 (RoomCord / JesusCord).

workcord auth login --email [email protected] --save

workcord rooms list

Use another server for one command:

workcord --base-url https://your-server.example.com rooms list

Save another server and login for future commands:

workcord --base-url https://your-server.example.com auth login --email [email protected] --save

Reset the saved server to the built-in default by clearing baseUrl from ~/.workcord/config.json and logging in again.

Output is JSON on stdout, errors on stderr, non-zero exit on failure.

How auth works

Every request carries a bearer token. There are two ways to get one, and one extra header for internal admin routes.

1. A real user token (normal path). Log in once with --save, and every command runs as that user with exactly that user's permissions:

workcord auth login --email [email protected] --save
# → code arrives by email, CLI prompts for it, prints
#   { token, accessToken, refreshToken, user, isNewUser } on stdout
#   and confirms the login was saved locally

workcord rooms list

WORKCORD_TOKEN is sent as-is in Authorization: Bearer; the CLI never signs anything in this mode and --as is not needed. This is the least-privilege setup and the right one for agents.

Non-interactive (no TTY) auth login --email prints a sessionId and you finish with:

workcord auth verify-email --session-id <id> --code 123456

Email login returns accessToken; phone/social login returns token. The CLI emits both fields on either path so callers can read one consistently — either value works as WORKCORD_TOKEN.

Email auth is domain-restricted. WorkCord prod and VIA both allow fulldive.com, via.com, via.com.tw, viatech.com.cn, *.tvbs.com, tvbs.*.com, tvbs.com.*, and *.tvbs.com.tw (EMAIL_AUTH_ALLOWED_DOMAINS in .sc/stacks/wize-rooms-api/client.yaml). Anything else gets DOMAIN_NOT_ALLOWED.

auth login --phone is for local and RoomCord only. Deployed WorkCord does not set AUTH_PHONE_OTP_DEV_BYPASS, so POST /auth/send-otp returns 503 OTP_NOT_AVAILABLE — the CLI turns that into an explicit "use --email" error.

Tokens expire. On 401, log in again with --save, or replace WORKCORD_TOKEN.

2. Local minting / impersonation (privileged, trusted CI only). With WORKCORD_JWT_SECRET set, the global --as <userId> flag mints an HS256 JWT locally (payload {userId, id}, 60-day expiry) instead of logging in. WORKCORD_JWT_SECRET must equal the server's JWT_SECRET env var — the names deliberately differ, and this is the most common setup mistake.

export WORKCORD_JWT_SECRET=<the server's JWT_SECRET>
workcord --as <userId> users get-me

Same secret on both sides means the CLI can act as any user, with no password and no server-side check. Treat it as privileged signing material: keep it out of source control, package metadata, logs, screenshots and public examples, and run this CLI only from trusted machines and CI jobs. Prefer WORKCORD_TOKEN unless you actually need to switch identities.

Resolution order, in one place (resolveAuth in src/auth.ts):

| Situation | Result | | --- | --- | | --as <userId> passed, WORKCORD_JWT_SECRET set | mint and impersonate that user | | --as <userId> passed, no secret | error — set the secret or drop --as | | no --as, WORKCORD_TOKEN set | send that token verbatim | | no --as, only WORKCORD_JWT_SECRET set | error — --as <userId> required | | neither set | error naming both options |

--as wins over WORKCORD_TOKEN when both are available: passing it is an explicit statement of impersonation intent.

3. Internal admin routes. WORKCORD_INTERNAL_API_KEY is sent as the x-internal-key header; the server compares it to its own INTERNAL_API_KEY. Required for /internal/* and the jesuscord admin routes. Treat it like the signing secret.

Usage

workcord [--as <userId>] <namespace> <command> [flags]

--as is only needed when minting with WORKCORD_JWT_SECRET; with WORKCORD_TOKEN set, omit it everywhere.

Agent-first customization

To change agent behavior, create or update a skill, or maintain a flavor, talk to the owning agent through Hermes tenant chat. Do not edit prompts or tenant files directly. With no --agent, the user's non-deletable Assistant is the target and becomes the default owner of anything it creates:

workcord agent chat new --message "Create a skill that prepares a concise weekly sales report and enable it."
workcord agent chat new --message "Update my default flavor instructions to require sources in research reports."

Use the returned chatId for follow-up, verification, or rollback:

workcord agent chat send --chat "$CHAT_ID" --message "Verify the change, list the versions you created, and explain how to roll it back."

Both commands wait up to two minutes by default and return the durable Hermes message document. Use --async for a queued response, then continue with its messageId:

workcord agent chat wait --message-id "$MESSAGE_ID" --timeout 10m
workcord agent chat history --chat "$CHAT_ID"

If a local wait times out, the response has timedOut: true; the remote agent continues working and can be checked with chat wait.

To make another owned agent the explicit owner and conversation target:

workcord agent chat new --agent "$AGENT_ID" --message "Create and maintain a finance-specific flavor for yourself."

The Assistant or selected agent works within its tenant's Hub permissions. It can safely validate changes and report what it activated. agent chat uses Hermes' native durable hub_chat transport; it is not raw A2A wire traffic. A2A remains available for external agent peers. Direct hermes commands are advanced diagnostics and recovery tools, not the normal customization workflow.

  • workcord --help lists namespaces.
  • workcord <namespace> --help lists that namespace's commands.
  • workcord <namespace> <command> --help shows the command's flags.

Two layers coexist under the same namespaces.

Auto-generated (one per server route, namespaced by controller):

workcord <namespace> <command> [--<path-param> <value>] [--body '<json>'] [--query '<json>']

Hand-written semantic (small set, rich typed flags):

workcord auth login --email [email protected]
workcord --as USER messages send-text --room R --text "hello"

Semantic aliases cover common agent/admin actions with consistent flags: paginated listing/searching public users, internal authenticated/anonymous user filters, acting-user room lists, discoverable app-wide room lists, room create/update/delete, join/leave/read, list members, remove members, list/approve/deny join requests, rotate join codes, get the acting user's membership, and merge recent messages visible to a user across rooms. The generated route-level commands remain available for every endpoint.

users list --account-type ... and users list --authenticated use the internal API and require WORKCORD_INTERNAL_API_KEY. Without those flags, users list uses public search and only returns public profile fields.

Server auto-extracts mention tokens from visible @Name text. Prefer messages send-text --mention-user USER_ID so the CLI resolves the room member's display name and avoids exposing raw user ids. auth login never takes --as — it is how you obtain a WORKCORD_TOKEN in the first place.

Error handling

Every error is a single JSON line on stderr, with a non-zero exit code:

{"error":"GET /api/v1/rooms/X -> 404","status":404,"body":{"error":{"code":"NOT_FOUND","message":"Room not found"}}}

Agents can branch on .status (401 → re-login, 404 → resource gone, 5xx → already retried, give up) and .body.error.code for app-specific codes.

Examples

workcord --as USER rooms get-room --room-id R
workcord --as USER rooms create-room --body '{"name":"hello","isPublic":true}'
workcord --as USER messages list --room-id R --query '{"cursor":"2026-04-28T00:00:00Z","limit":50}'

workcord --as USER users list --q alex --limit 10
workcord --as ADMIN users list --account-type anonymous --limit 20
workcord --as ADMIN users set-plan --user USER_ID --tier pro --platform internal
workcord --as USER rooms discover --search "ai" --tags ai,tools --limit 20
workcord --as USER rooms create-with-message --name "ops" --public --tags ai,news --text "First post."
workcord --as USER rooms members --room R
workcord --as USER rooms upload-icon-file --room R --file ./icon.png
workcord --as USER agent chat new --message "Create a room helper that can research the web."
workcord --as USER room-agents install-hosted --room R --agent system-wizeup
workcord --as USER messages react --room R --message M --emoji 👍
workcord --as USER dm send-text --to USER_B --text "hi"

Common agent patterns

Send a message with a mention + image:

URL=$(workcord --as $U rooms upload-image-file --room $R --file ./img.png | jq -r .url)
workcord --as $U messages send-text --room $R --text "look @user_abc" --attachments "$URL"

Poll for new messages since last check:

SINCE=2026-04-28T10:00:00Z
workcord --as $U messages list --room-id $R --query "{\"cursor\":\"$SINCE\",\"limit\":50}"

Receive latest messages visible to a user across rooms:

workcord --as $U messages latest --limit 50 --per-room-limit 10
workcord --as $U messages latest --since "$SINCE" --inbox-category aiRooms --limit 100

This composite command lists the acting user's rooms, fetches recent messages from each room, merges them by sentAt descending, and includes room/roomName context on each message.

Switch user identity per call:

Saved login is for one interactive user. Agents can use WORKCORD_TOKEN to choose the token owner per invocation; with WORKCORD_JWT_SECRET, one process can act as 100 users by changing --as <userId> per invocation.

Onboard a fresh agent identity:

# deployed WorkCord / VIA
USER=$(workcord auth verify-email --session-id $SID --code $CODE | jq -r .user.id)

# local / RoomCord dev, where AUTH_PHONE_OTP_DEV_BYPASS=true
USER=$(workcord auth login --phone "+14155550$RANDOM" | jq -r .user.id)

Locally any phone with OTP 111111 works. On deployed WorkCord phone OTP is disabled — start with workcord auth login --email <address>.

React to a message + reply with mention:

workcord --as $U messages react --room $R --message $M --emoji 👍
workcord --as $U messages send-text --room $R --text "agreed @user_abc" --reply-to $M

DM a user once-and-done:

workcord --as $U dm send-text --to $OTHER --text "hey"
# → composite: opens (or finds) the DM room, sends the message, returns both

Advanced Hermes diagnostics and recovery (hermes namespace)

WorkCord provisions a Hermes Hub tenant for each room-agent, Hermes custom agent, and room-scoped marketplace flavor install. That tenant is where the agent actually runs: its chats, runs, files, skills, flavor pin, A2A peers and eval runs all live on the Hub, not in WorkCord. Normal changes should be requested with workcord agent chat; the hermes namespace exists for direct inspection, diagnostics, and recovery.

This is the one namespace that talks to a second origin. Every other command goes to the WorkCord API; hermes uses the WorkCord API only to reveal the tenant key, then calls the Hub's /v1/tenant-api/* routes with it.

Target selection

Every hermes command takes exactly one of:

| Flag | Meaning | | --- | --- | | --agent <id> | a custom agent id, used verbatim | | --room-agent <id> | a room agent id, resolved as workcord-room-agent:<id> | | --marketplace-install <id> | an isolated marketplace flavor install id or its room agent id |

Passing more than one, or none, is an error. Marketplace install access follows current room ownership; transferring room ownership revokes the former creator's ability to reveal the tenant credential.

Credentials

The tenant key is resolved in one place (resolveHermesKey in src/auth.ts) and is never accepted as a flag:

| Env var | Meaning | | --- | --- | | WORKCORD_HERMES_KEY | tenant key, used verbatim; requires WORKCORD_HERMES_HUB_URL | | WORKCORD_HERMES_HUB_URL | Hub origin (bare origin, no path); requires WORKCORD_HERMES_KEY | | WORKCORD_HERMES_TENANT_ID | tenant id for the tenant-scoped routes, alongside the two above |

Setting only one of WORKCORD_HERMES_KEY / WORKCORD_HERMES_HUB_URL is an error — the CLI will not half-guess a hub.

With none of them set, the CLI calls POST /api/v1/agents/{id}/hermes-cli-key/reveal on WorkCord (as the acting user, so ownership is enforced server-side) and gets back the key, tenant id and hub origin.

Revealed credentials are cached per agent ref in ~/.workcord/config.json under hermes, alongside the saved login. The file is written 0600 inside a 0700 directory. Two flags control it:

| Flag | Effect | | --- | --- | | --refresh | ignore the cached entry, re-reveal, rewrite the cache | | --no-cache | neither read nor write the cache for this invocation |

Env credentials always win over the cache. hermes key prints the resolved credentials on stdout — that is its whole job; no other command emits key material.

Commands

| Group | What | | --- | --- | | hermes key / whoami / runtime | reveal credentials, show the tenant, show its runtime | | hermes agent status\|start\|stop\|restart\|card | tenant agent lifecycle | | hermes chat list\|create\|get\|messages\|send\|message\|stream\|stop\|approve | chats and chat messages (stream is SSE) | | hermes run create\|get\|stream\|stop\|approve | runs | | hermes files ls\|cat\|download | tenant-visible files | | hermes share groups\|ls\|read\|stat\|grep\|find\|write\|mkdir\|move\|rm | share-group filesystem | | hermes skills list\|enable\|disable\|files\|cat | skills installed on this tenant | | hermes flavor current\|catalog\|get\|set\|subscribe\|checkout\|publish\|rollout | flavor marketplace and this tenant's pin | | hermes skill catalog\|get\|checkout\|publish | skill marketplace | | hermes eval suites\|run\|list\|get\|cancel | flavor eval suites and runs | | hermes peers list\|get\|call\|find | A2A peers | | hermes conversations list\|get\|messages | A2A conversations | | hermes sessions list, hermes artifacts | agent sessions and artifacts |

workcord hermes whoami --room-agent $RA
workcord hermes whoami --marketplace-install $MARKETPLACE_INSTALL
workcord hermes chat send --room-agent $RA --chat $CHAT --text "status?"
workcord hermes flavor current --agent $AGENT
workcord hermes peers find --agent $AGENT --query "translation" --limit 5

Version flags are named --flavor-version and --skill-version, not --version: commander's program-level .version() matches --version anywhere in argv, prints the CLI version and exits before the subcommand runs.

Direct flavor and skill recovery

For normal work, ask the Assistant or another owned agent to create, update, validate, or activate its own flavor and skills. Hermes Hub supports tenant-owned skill and flavor namespaces; ownership defaults to whichever agent tenant performs the creation.

The direct flavor publish, flavor rollout, and skill publish commands are kept for advanced recovery and authorized publisher workflows. They operate as the selected tenant and can only mutate namespaces that tenant owns.

The CLI does not pack or untar archives. The authoring loop is:

workcord hermes flavor checkout --agent $AGENT --flavor-version 1.4.0 --out flavor.tgz
# unpack, edit, and repack with hermes-hub's own tooling
workcord hermes flavor publish --agent $AGENT --archive flavor.tgz

--archive must already be a gzipped package built beforehand; anything over 32 MiB is rejected locally before upload.

flavor set warns (and refuses without --force) on a downgrade or a deprecated target. flavor rollout defaults to the policy-respecting automatic rollout; --all-matching re-pins every tenant on the Hub pinned to that flavor id regardless of their own update policy, and therefore requires --force.

Development

cd clients/workcord-cli
npm install
npm run build
npx tsx src/cli.ts --as <userId> users get-me

Refreshing the spec when the server changes

npm run sync-spec builds the backend from the repo root (npm run dump-spec) and copies the resulting cli/openapi.json into this package. No GitHub token and no network — the spec comes from the source tree you are sitting in.

npm run regen      # sync-spec + generate
npm run typecheck  # confirm clean

Generated files live in src/generated/ and are committed, so the CLI works without running regen if you don't need the latest server changes.

Layout

src/
├── cli.ts                  commander entry, global --as flag
├── auth.ts                 auth resolution: WORKCORD_TOKEN vs minted --as JWT,
│                           plus Hermes Hub key resolution
├── http.ts                 shared fetch primitives (retry, errors, query)
├── client.ts               WorkCord fetch wrapper (JSON + multipart)
├── hermes-client.ts        Hermes Hub fetch wrapper (JSON, upload, SSE, binary)
├── hermes-guards.ts        rollout/ownership/archive guards, version compare
├── commands/               hand-written semantic commands
└── generated/              auto-generated from openapi.json
    ├── index.ts
    └── commands/<tag>.ts   one file per controller namespace

scripts/
├── sync-spec.ts            backend build → openapi.json
└── generate-commands.ts    spec → commander commands

tests/
├── auth.test.ts            node --test suite for auth resolution
└── smoke.sh                end-to-end test, needs a live server

Adding a hand-written semantic command

When an auto-generated command has a use pattern that would benefit from rich flags (e.g. typed fields instead of an opaque --body):

  1. Create src/commands/<area>.ts.
  2. Use getOrCreateGroup(program, "<area>", "...") to extend the auto-generated namespace, then .command("<verb>") for the new subcommand.
  3. Register in src/cli.ts.
  4. Add an assertion in tests/smoke.sh.

Testing

npm test           # hermetic, no network — auth resolution rules
npm run smoke      # end-to-end, needs a live server

Hermetic end-to-end suite (creates two fresh users per run via OTP, exercises multi-user flows, file upload, custom agents, error paths). Requires a running server (WORKCORD_API_URL) and a matching WORKCORD_JWT_SECRET, so it is not part of any automated gate.

Release

Publishing is a manual maintainer action — there is no auto-publish CI.

  1. npm run regen — refresh the spec and generated commands; commit the diff.
  2. npm run typecheck
  3. npm test then npm run build
  4. npm pack --dry-run — confirm the file list is exactly dist/**/*.js, README.md, LICENSE, package.json. No sources, no openapi.json, no .npmrc.
  5. Smoke: node dist/cli.js --help and node dist/cli.js --version.
  6. Bump version in package.json, then npm publish.

prepack re-runs typecheck + test + build, so steps 2–3 also gate the publish.

License

Proprietary — see LICENSE. Published to npm for installation by authorized Fulldive personnel and systems only; no reuse rights are granted.