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

@pylink/cli

v0.1.3

Published

CLI for controlling a PyLink workspace from the shell. Designed for AI agents: --json output, stable exit codes, stdin piping.

Readme

@pylink/cli

Control a PyLink workspace from the shell. Designed for AI agents as much as humans: every command accepts --json for stable machine output, exit codes map to specific failure classes, and write can read content from stdin.

Install

npm install -g @pylink/cli

Requires Node ≥ 20.

Quickstart

# One-time login via OAuth 2.1 device flow. Stores tokens in
# ~/.pylink/config.json (mode 0600).
pylink auth login --server https://pylink-dev.up.railway.app

# Who am I?
pylink whoami

# List, read, write, delete files in the personal workspace.
pylink ls
pylink cat README.md
echo "draft" | pylink write notes/draft.md --stdin
pylink rm notes/draft.md

Commands

Auth

| Command | Description | |---|---| | pylink auth login [--server URL] [--force] | RFC 8628 device flow. Emits a verification URL + user code; approve in your browser. | | pylink auth status | Show logged-in user, server, token expiry. Exits 1 if not logged in. | | pylink auth logout | Revoke refresh token, delete local config. |

Inspection

| Command | Description | |---|---| | pylink whoami | Bootstrap context: user, workspace index, base URL. | | pylink users | List all users (for sharing). | | pylink shared-with-me | Files shared with you by others. |

Read / write

All accept --workspace <name> (default personal).

| Command | Description | |---|---| | pylink ls | List files in the workspace. | | pylink cat <path> | Read file contents; emits ETag alongside the body. | | pylink write <path> (--content <str> \| --stdin) | Create or update. Use --stdin to pipe large / pre-formatted content. | | pylink write <path> --content X --if-none-match '*' | Conditional create: fails with exit 3 if the file already exists. | | pylink write <path> --content X --if-match <etag> | Conditional update: fails with exit 3 if the file has changed since you read it. | | pylink rm <path> [--if-match <etag>] | Delete (optionally conditional). | | pylink edit <path> --replace <anchor> --with <text> | Anchor-based replace. Fails if the anchor doesn't occur exactly once. | | pylink edit <path> --insert-after <anchor> --text <text> | Insert immediately after the matched anchor. | | pylink edit <path> --insert-before <anchor> --text <text> | Insert immediately before the matched anchor. |

Sharing

| Command | Description | |---|---| | pylink shares <path> | List who has access to a file. | | pylink share <path> --user <id> --permission viewer\|editor | Grant access to a user. | | pylink unshare <path> --user <id> | Revoke a user's access. |

Bulk local sync (for human editor workflows)

| Command | Description | |---|---| | pylink pull [<dir>] [--workspace <name>] [--force] | Fetch the whole workspace to a local directory; writes a .pylink/state.json with SHA256 + ETag per file. | | pylink status [<dir>] | Show A/M/D changes vs the last pull. Exits 1 if dirty. | | pylink push [<dir>] [--dry-run] | Upload local changes with If-Match / If-None-Match guards. Aborts on any conflict. |

Pull / push honour .pylink/ignore (gitignore-style) so you can exclude drafts, secrets, etc.

Agent mode: --json

Pass --json on any command to flip both stdout and stderr into stable machine output:

  • stdout: the command's result serialised as pretty JSON (arrays for list commands, row objects for reads/writes, event objects for multi-step flows like auth login).
  • stderr: a single-object error envelope when the command fails — never any trailing help text — so your parser sees exactly one JSON object.
$ pylink --json whoami
{
  "user": { "id": 2, "first_name": "Haoran", "last_name": "Shu" },
  "workspace_index": { "personal": 2 },
  "base_url": "https://pylink-dev.up.railway.app"
}

$ pylink --json cat missing.md
{
  "error": {
    "code": "api_error",
    "message": "File not found",
    "status": 404
  }
}
$ echo $?
4

auth login --json emits two events on stdout, one per line:

{"event":"device_code","verification_uri":"…","verification_uri_complete":"…","user_code":"ABCD-EFGH","expires_in":600,"interval":5}
{"event":"authenticated","user":{…},"server":"…"}

Agents can surface the first line to the human, then wait for the second.

Exit codes

| Code | Meaning | |---|---| | 0 | Success | | 1 | Generic failure (network, unknown server error) | | 2 | Usage error (missing or conflicting flags) | | 3 | Precondition failed (412 — stale --if-match or --if-none-match conflict) | | 4 | Not found (404) | | 5 | Auth required (401 / 403) — likely pylink auth login needed |

pylink auth status also exits 1 when logged out so scripts can gate on it.

Environment variables

| Var | Effect | |---|---| | PYLINK_TOKEN | Bearer token used for every request. Bypasses ~/.pylink/config.json entirely — no login required. Mint one in the web UI under Apps & Integrations → API keys. | | PYLINK_SERVER | Base URL. When PYLINK_TOKEN is set, this is how the CLI knows where to send requests. Otherwise falls back to the stored config's server, then http://localhost:3000. | | PYLINK_CONFIG | Path to the config file. Defaults to ~/.pylink/config.json. Useful for CI or multi-account workflows. |

Using @pylink/cli from an AI agent

Agents can't click through the device-flow browser approval, so device flow is a non-starter for headless usage. Use an API key instead:

# 1. In the web UI: Apps & Integrations → API keys → Create.
#    Copy the "plk_..." token — it's shown only once.

# 2. Anywhere (CI, sandbox, container), inject it via env:
export PYLINK_TOKEN=plk_...
export PYLINK_SERVER=https://pylink-dev.up.railway.app

# 3. The CLI now works without any config file or login step:
pylink --json whoami
pylink --json ls
echo "draft" | pylink --json write notes/agent.md --stdin

Precedence: if both PYLINK_TOKEN and ~/.pylink/config.json exist, the env var wins. This means an agent session on a already-logged-in laptop will use the env token, not your OAuth session.

Security note: env vars are visible to other processes running as the same user (ps e, /proc/<pid>/environ). API keys are long-lived by default, so treat them like passwords: store in a secret manager, rotate via the UI (revoke + recreate) when a host is compromised. Management endpoints (POST/DELETE /api/me/api-keys) require a web session, so a leaked key can't self-replace — only a human with browser access can mint more.

Development

git clone https://github.com/MercuryCod/pylink
cd pylink/cli
npm install
npm run dev -- --help     # run via tsx, no build step
npm run build             # compile to dist/
npm test                  # vitest

Links

License

MIT