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

@pan-enso/nctl

v0.4.2

Published

Headless Notion CLI for Markdown-driven page and database operations.

Downloads

1,051

Readme

nctl

A headless Notion CLI from Japan

npm version license TypeScript

nctl is a CLI for writing to Notion from CI/CD pipelines, cron jobs, and shell scripts — without OAuth, without a browser, without interactive login.

It uses only integration tokens, accepts Markdown files as input, and is safe to run headlessly at any time.

$ nctl page upsert --md report.md
✓ updated  abc123  sha256:e3b0c44...

Table of Contents


Install

# npm
npm install -g @pan-enso/nctl

# bun
bun add -g @pan-enso/nctl

Requires Bun at runtime.


Authentication

nctl resolves your Notion integration token in this order:

| Method | Example | |--------|---------| | Environment variable (default) | NOTION_API_KEY=ntn_xxx nctl ... | | Google Cloud Secret Manager | nctl --token-from gcloud:NOTION_TOKEN ... | | Any shell command | nctl --token-from "command:vault read secret/notion" ... |

[!TIP] For CI/cron environments, the gcloud: method keeps tokens out of environment files entirely.

[!WARNING] command: executes an arbitrary shell command on the host. Use it only with trusted commands and avoid embedding secrets in shell history or process lists.

Security note: The command: token source passes the provided string directly to bash -lc, enabling arbitrary shell execution. Only use trusted commands here.

# Simplest: env var
NOTION_API_KEY=ntn_xxx nctl db query DB_ID

# Google Cloud Secret Manager
nctl --token-from gcloud:NOTION_TOKEN page upsert --md report.md

# Custom command (any secret manager)
nctl --token-from "command:gcloud secrets versions access latest --secret=NOTION_TOKEN" \
  page upsert --md report.md

Commands

nctl page upsert

Create or update a Notion page from a Markdown file. Uses content hashing — if nothing changed, the page is not touched (no-op).

nctl page upsert --md report.md
nctl page upsert --md report.md --dry-run   # preview without writing

The Markdown file must have a YAML frontmatter block:

---
parent_page_id: PAGE_ID    # for pages under another page
# or:
database_id: DB_ID         # for database entries

title: Weekly Report
properties:
  Status: Done
  Tags:
    - engineering
    - weekly
---

# Heading

Body content goes here.

Output:

✓ created  abc123  sha256:e3b0c44...
✓ updated  abc123  sha256:e3b0c44...
- noop     abc123  sha256:e3b0c44...   ← content unchanged, skipped

nctl db query

Query a Notion database and output results in various formats.

# Human-readable table
nctl db query DB_ID --format table

# JSON Lines (for piping)
nctl db query DB_ID --format jsonl --limit 10

# Page IDs only
nctl db query DB_ID --format ids

# With filter (Notion filter JSON)
nctl db query DB_ID \
  --filter '{"property":"Status","status":{"equals":"Done"}}' \
  --format table

| Flag | Description | |------|-------------| | --format | table | jsonl | ids | json | | --filter | Notion filter object (JSON string) | | --limit | Max results (default: all) |


nctl block marker-replace

Replace a named section inside an existing Notion page — without touching anything outside the markers. Safe to run repeatedly (idempotent).

Step 1. Add markers to your Notion page (as code blocks or paragraph blocks containing):

<!-- nctl:marker:morning-patrol -->
... this section will be replaced ...
<!-- /nctl:marker:morning-patrol -->

Step 2. Run:

nctl block marker-replace PAGE_ID --marker morning-patrol --md update.md
nctl block marker-replace PAGE_ID --marker morning-patrol --md update.md --dry-run

nctl api

Low-level escape hatch for direct Notion REST API calls.

# GET
nctl api get /users/me

# POST with body
nctl api post /search --body '{"query":"Roadmap"}'

# PATCH
nctl api patch /blocks/BLOCK_ID \
  --body '{"paragraph":{"rich_text":[{"text":{"content":"Updated"}}]}}'

Real-world Examples

GitHub Actions: publish a daily report to Notion

name: Daily Report
on:
  schedule:
    - cron: "0 9 * * *"

jobs:
  report:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v1
      - run: npm install -g @pan-enso/nctl
      - run: nctl page upsert --md reports/daily.md
        env:
          NOTION_API_KEY: ${{ secrets.NOTION_API_KEY }}

cron + Google Cloud Secret Manager

# /etc/cron.d/notion-sync
0 6 * * * user nctl --token-from gcloud:NOTION_TOKEN page upsert --md /opt/reports/morning.md

Scripting with db query

# Get all Done items and process them
nctl db query "$DB_ID" --format jsonl --filter '{"property":"Status","status":{"equals":"Done"}}' \
  | jq -r '.id' \
  | while read id; do
      echo "Processing $id..."
    done

Markdown Format

nctl parses standard Markdown with YAML frontmatter.

Supported frontmatter fields:

| Field | Type | Description | |-------|------|-------------| | title | string | Page title (required) | | parent_page_id | string | Parent page UUID | | database_id | string | Target database UUID | | properties | object | Database properties (key: value) |

Supported block types:

| Markdown | Notion Block | |----------|-------------| | # H1 / ## H2 / ### H3 | Heading 1/2/3 | | Paragraph text | Paragraph | | > quote | Quote | | - item | Bulleted list item | | 1. item | Ordered list item (not supported) | | ```lang ``` | Code block | | --- | Divider |


Exit Codes

| Code | Name | Meaning | |------|------|---------| | 0 | success | OK | | 1 | api-error | Notion API returned an error | | 2 | auth-error | Token missing or invalid | | 3 | validation-error | Bad input (missing frontmatter, invalid args) | | 4 | not-found | Page or block not found |


Contributing

Issues and PRs are welcome at github.com/pan-enso/nctl.

Please open an issue before submitting large changes.


Author

Built by @AgentGymLeader · pan-enso

Inspired by @sakasegawa/ncli.


License

MIT