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

@infaris/calendar-cli

v0.9.0

Published

Command line interface for Calendar by Infaris: log in once on your device, read your agenda, manage work tasks, and fill the urenstaat from tasks, commits and PRs.

Readme

@infaris/calendar-cli

Command line interface for Calendar. Log in once on your device with a browser (or have an AI agent relay the sign-in through chat, see below), then read your agenda, find free time, create/move/delete events, manage work tasks (with image attachments), stage mail concepts and fill the urenstaat from your terminal (or from tools like Claude Code, Codex, OpenCode or Cursor).

Built for AI assistants: every read command supports --json, accepts relative dates ("today", "tomorrow", "+3d", "tomorrow 09:00"), and the agenda expands recurring events into concrete occurrences so an agent can answer "what's on my calendar?" without understanding RRULEs.

It also respects your Calendar settings: new events land on your default calendar and use your default duration, free uses your configured working hours, weekend visibility and week start. Override any of these with flags.

Install

npm install -g @infaris/calendar-cli

This installs infaris-calendar and infaris-crm (customer dossiers, same login).

Log in

infaris-calendar login

This opens your browser, signs you in through Infaris (OAuth with loopback PKCE, same flow as the Env CLI CLI), and stores tokens in ~/.infaris-calendar/credentials.json. Only Infaris admin accounts can use the CLI (Calendar is an internal tool), and your account must have signed in to the Calendar web app at least once.

Access tokens are short-lived and refreshed automatically via the stored refresh token; you only need to log in again when the refresh token expires (30 days of inactivity).

Signing in over SSH / a remote session (e.g. t3code)

The default flow opens a browser on the same machine that runs the CLI and waits for Infaris to redirect back to a local port - that only works when the CLI and the browser are on the same box. When you're on a remote/SSH session, run:

infaris-calendar login --manual

(an SSH session is auto-detected via $SSH_CONNECTION/$SSH_TTY, so most of the time you don't even need the flag). Instead of a local callback, it prints a URL to open on any device - your Windows laptop, phone, whatever has a browser. After you sign in there, Infaris shows your code on a page with a Copy button. Paste it back into the terminal.

Signing in through an AI agent (Claude Code, Codex, OpenCode, Cursor)

login --manual needs one process to stay open waiting for your paste, which doesn't work when an agent is relaying the URL and code through a chat instead of a live terminal. For that, the sign-in is split into two separate commands:

infaris-calendar login link          # 1. prints a URL, then exits immediately
infaris-calendar login code <value>  # 2. run later, once you have the code
printf '%s' "$CODE" | infaris-calendar login code -   # same, keeps the code out of argv/history
infaris-calendar login status --json # signed-in account + pending link, never tokens

Re-running login link prints the same URL while the pending sign-in still has more than 5 minutes left, so an agent retry never invalidates the link you are completing; login link --new forces a fresh one. Token refreshes are locked across processes, so parallel agents sharing one home don't rotate the same refresh token twice (Infaris revokes the whole token family on reuse). Without a terminal, login --manual fails immediately with the login link hint instead of hanging.

login link stores the pending PKCE state under ~/.infaris-calendar/ (expires after 15 minutes) and exits, so an agent can post the printed URL in the chat; you open it on your own device, sign in, and Infaris shows your code with a Copy button. Paste that code back to the agent, which runs login code <value> to finish. A wrong or expired paste doesn't invalidate the pending sign-in - rerun login code with the right value without starting over, as long as you're still inside the 15-minute window.

To point at a non-production instance:

infaris-calendar login --url http://localhost:3000
# or set it for the whole session:
export INFARIS_CALENDAR_URL=http://localhost:3000
# the OAuth issuer can be overridden too:
export INFARIS_CALENDAR_ISSUER=https://infaris.com

Read your agenda

infaris-calendar agenda                       # today
infaris-calendar agenda --tomorrow
infaris-calendar agenda --week                # current week (Mon-Sun)
infaris-calendar agenda --month
infaris-calendar agenda --days 7              # next 7 days
infaris-calendar agenda --from 2026-07-01 --to 2026-07-07
infaris-calendar agenda --calendar <id>      # one calendar only
infaris-calendar agenda --week --json        # full details for an AI assistant

Calendars you've turned off (hidden) in Calendar are excluded, just like in the web app. Pass --include-hidden to show them, or --calendar <id> to target one specific calendar regardless of its visibility.

Recurring events are expanded into individual occurrences within the range by default (pass --no-expand to get the raw master events with their RRULE). --json includes every detail: description, location, attendees, status, timezone, colour, the calendar id and name, and recurringEventId/isOccurrence for expanded occurrences.

list events is an alias for agenda.

Agenda options:

| Flag | Description | | --- | --- | | --from <date> | Range start (ISO, or today/tomorrow/+3d) | | --to <date> | Range end (inclusive) | | --days <n> | Number of days from --from or today | | --today / --tomorrow | Single day (today is the default) | | --week / --month | Current week (Mon-Sun) / calendar month | | --calendar <id> | Only events from this calendar id | | --search <text> | Filter by title, description or location | | --include-hidden | Also include calendars you turned off | | --no-expand | Do not expand recurring events | | --json | Output as JSON |

List your calendars

Your personal calendar, location/shared calendars and any connected Google/ iCloud/Microsoft/CalDAV accounts:

infaris-calendar calendars
infaris-calendar calendars --json
infaris-calendar calendars --set-default "Arjan"

Use the printed calendar id with agenda --calendar and free --calendar. create event --calendar, update event --calendar and --set-default accept either an exact calendar name or an id and only select a writable calendar.

Create a calendar event

infaris-calendar create event --title "Lunch with client" \
  --start "tomorrow 12:00" --duration 60 --location "Café Central, Amsterdam"

infaris-calendar create event -t "Sprint review" \
  --start 2026-07-01T14:00 --end 2026-07-01T15:00 --calendar <id>

infaris-calendar create event -t "Day off" --start 2026-07-04 --all-day

infaris-calendar create event -t "Conference" \
  --start 2026-10-19 --end 2026-10-20 --all-day \
  --calendar "Arjan" --attendee Michan Noah

New timed events that overlap something already on your calendar print a non-blocking warning (turn it off with --no-conflict-check).

| Flag | Description | Default | | --- | --- | --- | | -t, --title | Event title (required) | — | | -s, --start | Start (ISO or relative, required) | — | | -e, --end | End (ISO or relative). With --all-day, the date is inclusive | start + duration | | --duration <minutes> | Used when --end is omitted | your settings (60) | | -a, --attendee <people...> | Invite team members by first name, full name or email | none | | --all-day | All-day event | — | | -l, --location | Location | — | | -d, --description / --description-file | Description (file - = stdin) | — | | --calendar <id> | Target calendar | your default calendar | | --no-conflict-check | Skip the overlap warning | — | | --json | Output the created event as JSON | — |

Your next event

infaris-calendar next                 # next 3 upcoming events
infaris-calendar next --limit 1       # just the next one
infaris-calendar next --days 7 --json

Find free time

Find open slots within working hours, in your local timezone. All-day events (e.g. holidays) count as busy by default. Weekends are skipped unless you ask for them.

infaris-calendar free --tomorrow                       # gaps tomorrow
infaris-calendar free --week --min 60                  # 1h+ slots this week
infaris-calendar free --days 5 --work-start 08:00 --work-end 18:00
infaris-calendar free --week --json                    # for an AI to pick a slot

| Flag | Description | Default | | --- | --- | --- | | range flags | Same as agenda (--today/--week/--days/--from...) | today | | --work-start <HH:mm> / --work-end <HH:mm> | Working hours | your settings (09:00 / 17:00) | | --min <minutes> | Minimum slot length to report | 30 | | --include-weekends | Include Saturday and Sunday | your settings | | --ignore-all-day | Don't treat all-day events as busy | off | | --calendar <id> | Only consider this calendar as busy | all | | --json | Output slots as JSON | — |

Show, reschedule or delete an event

Get the event id from agenda --json (or next --json), then:

infaris-calendar show event <id>                       # full details
infaris-calendar show event <id> --json

infaris-calendar update event --id <id> --start "tomorrow 15:00" --end "tomorrow 16:00"
infaris-calendar update event --id <id> --shift 15     # move 15 min later (-15 = earlier)
infaris-calendar update event --id <id> --title "New title" --location "Room 2"
infaris-calendar update event --id <id> --attendee Michan Noah
infaris-calendar update event --id <id> --clear-attendees

infaris-calendar delete event --id <id>

For recurring events the change applies to the whole series (the CLI tells you when that's the case).

| update event flag | Description | | --- | --- | | --id <id> | Event id (required) | | -t, --title / -l, --location / -d, --description | Edit fields | | -s, --start / -e, --end | New start/end (ISO or relative) | | --shift <minutes> | Move the whole event by N minutes (+later, -earlier) | | --calendar <id> | Move to another calendar | | -a, --attendee <people...> | Replace attendees by first name, full name or email | | --clear-attendees | Remove all attendees | | --json | Output the updated event as JSON |

Create a task

infaris-calendar create task \
  --title "Agent: collect disk I/O metrics" \
  --product pulseguard \
  --priority medium \
  --type development \
  --category feature_request \
  --description "Add per-disk read/write throughput and IOPS to the agent."

Long descriptions can be read from a file (or stdin with -):

infaris-calendar create task --title "..." --product pulseguard --description-file ./task.md
cat task.md | infaris-calendar create task --title "..." --description-file -

Options:

| Flag | Description | Default | | --- | --- | --- | | -t, --title | Task title (required) | — | | -p, --product | general, pulseguard, pulsefiles, pulseqr, pulsecalendar, pulsesupport, pulsemail, ipulse | pulseguard | | -d, --description | Markdown description | — | | --description-file | Read description from a file (- = stdin) | — | | --priority | high, medium, low | medium | | --status | not_started, in_progress, done | not_started | | --type | one or more of general, development, marketing | development | | --category | one or more of bug, refinement, feature_request | — | | --relation-number | linked customer relation number | — | | --due | due date (ISO) | — | | --image <paths...> | attach image(s) or a PDF (max 10 MB each) | — | | --json | print the created task as JSON | — |

Attach a file to a task

Add an image or PDF to a task after the fact (creating a task already accepts --image directly):

infaris-calendar attach task --id 427 ./screenshot.png
infaris-calendar attach task --id 427 ./screenshot.png ./logs.pdf

10 MB per file, same limit as the web app's attachment upload.

Stage a mail draft

Prepares an editable concept in your own webmail; it never sends anything. Open the printed URL to review and send it from /dashboard/mail.

infaris-calendar mail draft --to [email protected] --subject "Offerte" \
  --body "<p>Hoi,</p><p>Zie bijlage.</p>" --attach ./offerte.pdf

infaris-calendar mail draft --to [email protected] --subject "Screenshot" \
  --body-file ./mail.html --from [email protected] --json

| Flag | Description | | --- | --- | | --to <email> | Recipient (required) | | --subject <text> | Subject | | --body <html> / --body-file <path> | HTML body (file - = stdin) | | --from <email> | Preferred sender mailbox address | | --attach <path> | Image or PDF to attach (max 10 MB) | | --json | Output the draft id/url as JSON |

List tasks

infaris-calendar list tasks
infaris-calendar list tasks --status in_progress
infaris-calendar list tasks --search agent --include-done
infaris-calendar list tasks --assignee me --json
infaris-calendar list tasks --json

Urenstaat

Fill https://calendar.infaris.com/dashboard/hours from Calendar tasks, git commits (IF-687 / #687 in the subject; legacy IP-687 also works) and GitHub PRs for that day. Needs infaris-calendar login, a git checkout, and optionally gh auth login.

infaris-calendar hours                         # today
infaris-calendar hours --week
infaris-calendar hours fill                    # draft from tasks + commits + PRs
infaris-calendar hours fill --date yesterday --json
infaris-calendar hours fill --date 2026-09-02 --write
infaris-calendar hours fill --date yesterday --write --replace   # overwrite
infaris-calendar hours log --date today --hours 8 --notes "#687 uren-skill"
infaris-calendar hours delete --date today

hours fill writes nothing until you pass --write. If that day already has a row, it refuses unless you also pass --replace. Hours default to your weekday default (8u on a normal weekday). Notes are one summarised line: Guard / Calendar / Env / reviews / meetings, with task keys in parentheses.

Other commands

infaris-calendar whoami    # show the logged-in account
infaris-calendar logout    # remove the stored token

How auth works

The CLI is a public OAuth client (pulsecalendar-cli, a server-side registration) on the Infaris identity provider, using authorization code + PKCE with a loopback redirect (same pattern as gh auth login and the Env CLI CLI):

  1. The CLI starts a one-shot HTTP server on 127.0.0.1:47655.
  2. It opens https://infaris.com/api/oauth/authorize in your browser with a PKCE challenge.
  3. Infaris signs you in and redirects back to http://127.0.0.1:47655/callback with an authorization code.
  4. The CLI exchanges the code (plus PKCE verifier) for access and refresh tokens and stores them locally.

The Calendar API verifies these RS256 tokens against the Infaris JWKS and maps your Infaris account to your Calendar user by e-mail. New tasks are assigned to you by default, with priority medium, type development, status not_started and the current sprint, so AI agents can create a task with just a --title.

Develop

npm install
npm run dev -- create task --title "test" --product pulseguard   # run from source via tsx
npm run build                                                    # emit dist/

Customer dossiers and complete ticket context (0.8.0)

This package now also installs infaris-crm, using the same login as infaris-calendar. Read the customer support workflow for documentation ownership, daily work, handoff and rollout.

infaris-crm list --search "Customer" --json
infaris-crm create --name "Customer" --website https://example.com --json
infaris-crm show RELATION --include-done --json
infaris-crm contact RELATION --name "Person" --email [email protected]
infaris-crm note RELATION --method PHONE --outcome SPOKEN --body-file call.md
infaris-crm ticket RELATION --title "Slow website" --description-file problem.md --image screenshot.png --json
infaris-calendar list tasks --relation-number RELATION --json
infaris-calendar show task 427 --customer-context --download-dir /tmp/ticket-427 --json
infaris-calendar comment 427 --body-file investigation.md --json
infaris-calendar update task --id 427 --assignee me --status in_progress
infaris-calendar update task --id 427 --clear-assignees
infaris-calendar link-pr 427 https://github.com/InfarisHQ/monorepo/pull/123 --json

Task creation also accepts --assignee <people...> by team name, email, id or me. show task includes description, comments, subtasks, PRs and attachment URLs. --customer-context adds the linked CRM dossier. --download-dir downloads attachments without overwriting existing files. Open images with your agent's image viewer; do not infer their contents from filenames.

CRM commands return JSON by default. list supports --limit and --offset. Customer history defaults to the latest 50 entries; entriesPagination reports the total and next offset. Read older entries with show --entries-offset <n> and --entries-limit <n> (max 200). contact --id replaces that contact's details, so include fields you want to keep. note appends; it never overwrites the history. create creates a prospect without billing or a Guard license. Check returned warning values before retrying. Ticket creation reports its task number if a later upload fails; retry with attach task, not a second ticket.

Use infaris-crm login, or login link / login code <value> for remote sessions. The logout affects both CLI tools. INFARIS_CALENDAR_CONFIG_DIR can select an isolated login directory for a separate profile or automation session. Override the CRM server with INFARIS_CRM_URL; a non-production Calendar login requires this explicitly. Support must deploy the /api/crm/* routes before CRM commands are available.