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

@opendocs.cc/cli

v0.1.19

Published

CLI for publishing Markdown to OpenDocs

Readme

opendocs

CLI for publishing Markdown to OpenDocs. Works great with AI agents like OpenClaw, Claude Code, Cursor, Copilot, and others.

Install

npm install -g @opendocs.cc/cli

Requires Node.js 20 or newer.

Or run directly with npx:

npx @opendocs.cc/cli <command>

Quick start

# 1. Authenticate in your browser
opendocs login --browser

# 2. Publish a Markdown file
opendocs publish README.md

# 3. List your documents
opendocs list

For headless scripts and agents, create an API key from your OpenDocs dashboard and use opendocs login --key od_live_xxxxx.

For AI agents

Every command accepts --json for machine-readable output — stdout becomes a single parseable JSON object, errors are written as JSON to stderr, and human-readable spinners are suppressed. This is designed to let coding agents (Claude Code, Cursor, Copilot, OpenClaw, etc.) drive the CLI deterministically.

Three agent-friendly features worth knowing:

  • Vault-friendly batch publishing. Pass files or folders to opendocs publish and the CLI uses the batch endpoint — one authenticated request handles up to 25 documents at a time, and larger invocations are auto-chunked. Never loop opendocs publish per file in a shell.
  • Publish + export in one step. opendocs publish doc.md --export pdf --json publishes the doc and writes a branded PDF next to the source — one round trip, one command. Great for "turn these notes into a report" workflows.
  • Bundled agent guide. Run opendocs agent for a concise reference, opendocs agent --cli for CLI-only details, opendocs agent --api for API-only details, or opendocs agent --full for the full bundled SKILL.md.

See the error codes table in Troubleshooting for structured recovery hints.

opendocs agent

Print static, package-bundled instructions for AI agents. This command does not require login and does not fetch remote content.

opendocs agent          # concise, high-signal reference
opendocs agent --full   # full bundled SKILL.md
opendocs agent --api    # API-only reference
opendocs agent --cli    # CLI-only reference

With --json, the guide is wrapped as { "variant", "version", "content" }.

Commands

opendocs login

Authenticate with OpenDocs. The resulting API key is stored locally at ~/.config/opendocs/config.json.

opendocs login --browser
opendocs login
opendocs login --key od_live_abc123

opendocs login --browser opens OpenDocs in your browser. If you are already signed in, you only need to confirm the visible account and workspace before the CLI creates a new API key. Use the API-key prompt or --key for headless scripts.

The interactive prompt hides the key while you type. For scripts, prefer OPENDOCS_API_KEY over --key so the key is less likely to land in shell history.

opendocs logout

Clear stored credentials.

opendocs logout

opendocs whoami

Show current authentication status, username, workspace, active project, and API URL.

opendocs whoami

The active project is where publish, list, and slug-based post commands act by default.

opendocs project

List, create, switch, rename, and delete projects in the active workspace. Projects are useful when an agent publishes a batch of related documents and you want that set isolated from the Default project.

opendocs project list
opendocs project current
opendocs project create "Launch docs" --slug launch-docs --use
opendocs project use launch-docs
opendocs project rename launch-docs --name "Launch documentation"
opendocs project delete launch-docs --yes

Default project documents use URLs like opendocs.cc/<workspace>/<slug>. Non-default project documents use opendocs.cc/<workspace>/projects/<project>/<slug>.

opendocs publish <files...>

Publish one or more Markdown files to OpenDocs. Pass multiple files, globs, or folders and the CLI uses the batch endpoint — one authenticated request handles up to 25 documents at a time, and larger invocations are auto-chunked.

# Single file
opendocs publish guide.md
opendocs publish guide.md --title "Getting Started" --slug getting-started
opendocs publish guide.md --visibility public --confirm-public
opendocs publish guide.md --tags "tutorial,getting-started"
opendocs publish guide.md --project launch-docs

# Multiple files (auto-batched, up to 500 per invocation)
opendocs publish doc1.md doc2.md doc3.md
opendocs publish docs/*.md --tags "release-2026-q2"

# Folder / Obsidian vault upload (recursive; skips .obsidian, .trash, .git, .hg, node_modules)
opendocs publish ./vault --tags "obsidian-import"

# Publish and export in one command — PDFs land next to each source
opendocs publish docs/*.md --export pdf
opendocs publish guide.md --export docx

| Flag | Description | | ------------------ | ------------------------------------------------------------------- | | --title <title> | Override document title (single-file only) | | --slug <slug> | Custom URL slug (single-file only) | | --visibility <v> | private, workspace (default), or public | | --tags <tags> | Comma-separated tags (applied to every file when publishing multiple) | | --project <slugOrId> | Publish to a specific project instead of the active project | | --confirm-public | Required when publishing as public | | --export <fmt> | After publishing, export each doc to docx or pdf next to source |

Limits: 1 MB markdown per document, 500 files per invocation (auto-batched in groups of 25). Partial success is reported per-file — if one document's slug is taken, the others still publish.

Local images: Markdown and HTML image references that point to files next to the source document are uploaded automatically before publish. Supported formats are JPEG, PNG, WebP, GIF, and SVG, with a 10 MB cap per image and 30 local images per document. The CLI will not read images outside the directory containing the Markdown file.

--export behaviour: PDFs/DOCX files are written next to each source (e.g. docs/intro.mddocs/intro.pdf). Publish and export phases are independent — if publish succeeds and export fails, posts stay up and the CLI prints a opendocs export <postId> --format <fmt> retry hint.

Referencing a post: ID or slug

Every command that takes a <postId> also accepts the post's slug — the human-readable path segment from the URL. Slugs are unique within the active project. Use whichever is handier:

opendocs get my-guide              # slug — what you'd naturally type
opendocs get abc123                # UUID from `opendocs list --json`

This applies to get, pull, visibility, unpublish, delete, export, and the --post-id flag on update. If the same slug exists in another project, switch with opendocs project use <slug> or pass the UUID.

opendocs update <file>

Update an existing document with new content.

opendocs update guide.md --post-id my-guide
opendocs update guide.md --post-id abc123 --visibility public --confirm-public

| Flag | Description | | ---------------------- | ---------------------------------------------------- | | --post-id <id\|slug> | (required) Post ID (UUID) or slug | | --title <title> | Override document title | | --visibility <v> | Change visibility (private, workspace, public); omit to keep current visibility | | --tags <tags> | Replace tags (comma-separated); omit to keep current tags | | --confirm-public | Required when changing to public |

Like publish, update uploads supported local image references from the Markdown file's directory before sending the new version.

opendocs visibility <id|slug> <level>

Change a document's visibility without uploading new Markdown or creating a new version.

opendocs visibility my-guide private
opendocs visibility my-guide workspace
opendocs visibility my-guide public --confirm-public

| Level | Description | | ----------- | ------------------------------------------------------------- | | private | Only you can read the document | | workspace | Workspace members can read it (default for new documents) | | public | Anyone with the link can read it; requires --confirm-public |

opendocs unpublish <id|slug>

Unpublish a document (keeps the content, removes public access).

opendocs unpublish my-guide

opendocs delete <id|slug>

Permanently delete a document and free its slug. This cannot be undone.

opendocs delete my-guide --yes

Use --yes (or -y) in scripts, CI, and agent workflows. Without it, the CLI will ask a human to re-type the ID or slug in an interactive terminal, and will refuse to delete in non-interactive contexts.

opendocs list (alias: opendocs ls)

List your documents with pagination. opendocs ls is a muscle-memory alias for anyone used to ls.

opendocs list
opendocs ls                              # same thing
opendocs list --page 2 --limit 50
opendocs list --tag tutorial
opendocs list --all
opendocs list --all-projects

| Flag | Description | | ------------- | ----------------------------------------- | | --page <n> | Page number (default: 1) | | --limit <n> | Results per page, max 100 (default: 20) | | --tag <tag> | Filter by tag | | --all | Show documents across every workspace | | --all-projects | Show every project in the active workspace |

--page and --limit must be positive integers; --limit cannot exceed 100.

opendocs get <id|slug>

Get details of a specific document.

opendocs get my-guide

opendocs pull

Download raw Markdown for one document, a project, or the active workspace.

opendocs pull my-guide                    # Saves to <slug>.md
opendocs pull my-guide -o current.md      # Custom output path
opendocs pull my-guide --stdout           # Print to stdout
opendocs pull --project                   # Pull every doc in the active project
opendocs pull --project launch            # Pull every doc in a named project
opendocs pull --workspace                 # Pull every project in the active workspace
opendocs pull --tag release -o ./docs     # Pull matching docs in the active project
opendocs pull --workspace --tag release   # Pull matching docs across all projects

For single-document pulls, -o is a file path. For project/workspace pulls, -o is an output directory. Project pulls write files directly into that directory. Workspace pulls write Default project docs at the root and non-default projects under projects/<project-slug>/.

Bulk pulls refuse to overwrite existing files unless you pass --overwrite. Use --dry-run to preview the files that would be written.

| Flag | Description | | --------------------- | ------------------------------------------------ | | -o, --output <path> | Output file path, or output directory for bulk | | --stdout | Print one document to stdout instead of saving | | --project [ref] | Pull active project, or a project slug/id | | --workspace | Pull every project in the active workspace | | --tag <tag> | Filter bulk pulls by tag | | --overwrite | Replace existing files during bulk pulls | | --dry-run | Preview a bulk pull without writing files |

opendocs export <idsOrSlugs...>

Export one or more published documents as formatted .docx or .pdf files. Pass multiple IDs or slugs to export a batch (delivered as a zip and unpacked locally).

# Single
opendocs export my-guide --format pdf                    # Saves to my-guide.pdf
opendocs export my-guide --format docx                   # Saves to my-guide.docx
opendocs export my-guide --format pdf -o report.pdf      # Custom output path
opendocs export my-guide --format pdf --landscape        # Landscape A4
opendocs export my-guide --format pdf --accent-color "#B91C1C"
opendocs export my-guide --format pdf --stdout | lp      # Pipe straight to lp

# Batch (up to 10 docs per invocation — mix IDs and slugs freely)
opendocs export intro guide api-spec --format pdf        # Unpacks to cwd
opendocs export abc123 def456 --format pdf -o ./exports  # Unpacks to ./exports/

| Flag | Description | | ----------------------- | ------------------------------------------------------------ | | -f, --format <format> | docx (default) or pdf | | -o, --output <path> | File path (single doc) or directory (multiple docs) | | --stdout | Write binary to stdout (single doc only) | | --landscape | Render in landscape A4 | | --accent-color <hex> | Brand accent hex (e.g. #B91C1C) — derives the full palette |

Batch mode: pass up to 10 IDs or slugs in one call. The server runs each conversion sequentially and returns a single zip containing all outputs plus a manifest.json with per-doc results. The CLI unpacks into the target directory (cwd by default) and prints one success/failure line per doc. Partial success is supported — if one doc fails (e.g. converter error), the rest still land.

Output filenames returned by the API are treated as basenames only; unsafe filenames containing path separators are rejected instead of being written outside the requested output directory.

opendocs workspace

List and switch between workspaces you belong to. The CLI stores one active workspace and one active project locally; publish, update, list, get, pull, visibility, unpublish, delete, and export use that active workspace/project for slug lookups.

opendocs workspace list
opendocs workspace ls
opendocs workspace use acme-docs
opendocs workspace switch acme-docs

Switching workspaces only changes local CLI config. It also resets the active project to that workspace's Default project. It does not change the active workspace in the web dashboard.

Global flags

These flags work with any command:

| Flag | Description | | ----------------- | ---------------------------------------------------- | | --json | Output results as JSON (for scripting and AI agents) | | --api-url <url> | Override the API base URL | | --version | Show version number | | --help | Show help |

JSON mode

Pass --json to get machine-readable output. Human-friendly spinners and formatting are suppressed, and results are printed as JSON to stdout.

opendocs list --json
opendocs get abc123 --json
opendocs publish doc.md --json

This is designed for AI coding agents and scripts that need to parse the output.

Environment variables

| Variable | Description | | ------------------ | ---------------------------------------------------------- | | OPENDOCS_API_KEY | API key (alternative to opendocs login) | | OPENDOCS_API_URL | Override API base URL (default: https://api.opendocs.cc) | | OPENDOCS_WEB_URL | Override web URL for opendocs login --browser |

Configuration

Credentials are stored at ~/.config/opendocs/config.json with 0600 permissions (readable only by you). The containing config directory is kept at 0700. The config file is created by opendocs login and removed by opendocs logout.

Troubleshooting

When a command fails, the CLI exits non-zero and prints a structured error. In --json mode the error object is written to stderr (not stdout) so scripts can parse it cleanly:

{ "error": "slug_taken", "message": "That slug is already in use." }

Common error codes:

| Error | What it means | How to recover | | ----------------------- | ------------------------------------------------------------------- | --------------------------------------------------------------------------- | | auth_expired (401) | API key revoked or invalid. Config is auto-cleared. | Run opendocs login with a new key. | | onboarding_incomplete | Account exists but username/workspace aren't set. | Finish onboarding at opendocs.cc. | | no_workspace | Local config is missing workspaceId (rare — config corruption). | Run opendocs login to refresh it. | | slug_taken (409) | Slug already used within the workspace. | Re-run without --slug (auto-generates) or pick a different one. | | reserved_slug | Slug collides with an OpenDocs route word such as tag or projects. | Pick another slug, or omit --slug and let OpenDocs suffix it. | | project_not_empty | Tried to delete a project that still contains documents. | Delete or move the documents first. | | doc_too_large | File exceeds the 1 MB markdown cap. | Split the document — this is a hard server-side limit. | | too_many_files | >500 files passed in one publish invocation. | Split into multiple runs. | | too_many_posts | >10 postIds passed to export. | Export in groups of 10. | | invalid_format | --format / --export wasn't pdf or docx. | Fix the flag value. | | invalid_page | --page was not a positive integer. | Use --page 1 or another positive integer. | | invalid_limit | --limit was not a positive integer between 1 and 100. | Pick a limit from 1 to 100. | | conflicting_options | Flags don't combine (e.g. --title with multiple files). | Drop the conflicting flag. | | confirm_public_required | Public visibility was requested without --confirm-public. | Re-run with --confirm-public only after explicit user consent. | | confirm_required | A destructive command needs confirmation in a non-interactive context. | Re-run with --yes if deletion is intentional. | | document_limit | Publish would exceed the Free plan's document cap. | Unpublish old docs, publish fewer files, or upgrade. | | image_upload_failed | A local image referenced by Markdown could not be uploaded. | Fix or remove the image reference and retry. | | not_a_member | Tried to switch to a workspace the account cannot access. | Run opendocs workspace list and choose one of those slugs. | | not_found (404) | postId doesn't exist or isn't yours. | Double-check with opendocs list. | | export_failed (502) | Converter service transient failure. | Retry. If this happened during publish --export, the post is still up — re-run just the export with opendocs export <postId> --format <fmt>. |

If something else goes wrong, re-run with --json and inspect stderr — most error paths include both a machine-readable error code and a human-readable message.

Development

Clone and install:

git clone https://github.com/opendocs-cc/opendocs-cli.git
cd opendocs-cli
npm install

Useful scripts:

npm run dev         # Run the CLI directly from src/ via tsx
npm run build       # Bundle to dist/ with tsup
npm test            # Run the vitest suite
npm run typecheck   # tsc --noEmit

Tests spawn the CLI as a real subprocess against an in-process mock API — no network and no global config is touched (config lives under a throwaway XDG_CONFIG_HOME). See test/helpers/ for the harness.

License

MIT