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

@glorioustephan/todoist-autolabel

v1.1.0

Published

Automatically classify and label Todoist Inbox tasks with Claude AI using Structured Outputs.

Readme

@glorioustephan/todoist-autolabel

CI npm version Docs License: MIT

📖 Full documentation: https://glorioustephan.github.io/todoist-autolabel-service/

Automatically classify and label Todoist Inbox tasks with Claude AI using Structured Outputs — guaranteed-valid label assignments, no retry parsing, no malformed JSON.

This package ships both a turn-key CLI you can run as a long-lived daemon, and a library you can embed in your own Node process.

npx @glorioustephan/todoist-autolabel

Features

  • Inbox-only classification — leaves your organized projects alone, attacks the bottom of the funnel.
  • Claude Structured Outputs — labels are constrained to your taxonomy at decode time, so the model can never invent a label or return malformed JSON.
  • Cursor-paginated sync — talks to the new Todoist API v1 (the v9 REST API was deprecated in February 2026).
  • Stateful retries — a local SQLite DB tracks attempts so transient failures don't get stuck or duplicated.
  • CLI + librarynpx-runnable daemon, or import the pieces you need.
  • Cheap — defaults to Claude Haiku 4.5, ~$1 / $5 per 1M input/output tokens.

Architecture

graph TB
    subgraph Service["Autolabel Service"]
        SyncLoop["Sync Loop"]
        Claude["Claude AI<br/>Classifier"]
        Todoist["Todoist API v1"]

        SyncLoop -->|Classify new tasks| Claude
        Claude -->|Apply labels| Todoist

        subgraph DB["SQLite (local)"]
            Tasks[(tasks)]
            SyncState[(sync_state)]
            ErrorLogs[(error_logs)]
        end

        SyncLoop -.->|Read/Write| DB
        Claude -.->|Log errors| DB
        Todoist -.->|Update state| DB
    end

    style Service fill:#ffffff,stroke:#DC4C3E,stroke-width:2px,color:#202020
    style DB fill:#ffffff,stroke:#202020,stroke-width:1px
    style SyncLoop fill:#DC4C3E,stroke:#A02E20,color:#ffffff
    style Claude fill:#ffffff,stroke:#DC4C3E,stroke-width:2px,color:#202020
    style Todoist fill:#DC4C3E,stroke:#A02E20,color:#ffffff

Quick start (CLI)

1. Install

# As a one-off:
npx @glorioustephan/todoist-autolabel

# Or pinned in a project:
pnpm add @glorioustephan/todoist-autolabel

2. Scaffold .env and labels.json

npx @glorioustephan/todoist-autolabel init

This drops a starter .env and labels.json into the current directory (won't clobber existing files — pass --force to overwrite). Skip this step if you'd rather hand-roll them as below.

2b. Or create them manually

.env:

TODOIST_API_TOKEN=your_todoist_api_token
ANTHROPIC_API_KEY=your_anthropic_api_key

# All optional:
ANTHROPIC_MODEL=claude-haiku-4-5-20251001
MAX_LABELS_PER_TASK=5
POLL_INTERVAL_MS=300000
LOG_LEVEL=info

labels.json (copy labels.example.json as a starting point):

{
  "labels": [
    { "name": "urgent",   "color": "red" },
    { "name": "waiting",  "color": "grey" },
    { "name": "errands",  "color": "red" }
  ]
}

Every label here must already exist in Todoist with the same name. The example file uses Todoist's red / berry_red / grey / charcoal named colors to match the Todoist brand palette.

3. Run

npx @glorioustephan/todoist-autolabel

The CLI runs forever, polling Todoist every POLL_INTERVAL_MS. Stop it with Ctrl+C.

CLI reference

todoist-autolabel [options]
todoist-autolabel init [--force]

Options
  -l, --labels <path>   Path to your labels file (default: ./labels.json)
  -h, --help            Show help and exit
  -v, --version         Print version and exit

You can point at any labels file you like:

npx @glorioustephan/todoist-autolabel --labels ./taxonomies/work.json
# or
LABELS_PATH=./taxonomies/work.json npx @glorioustephan/todoist-autolabel

Quick start (library)

import {
  loadConfig,
  initDatabase,
  initTodoistApi,
  initClassifier,
  initSyncManager,
  getSyncManager,
} from '@glorioustephan/todoist-autolabel';

const config = loadConfig();           // pulls from process.env + CWD .env
initDatabase(config);
await initTodoistApi(config);

const classifier = await initClassifier(config);
if (!classifier.success) throw new Error(classifier.error);

initSyncManager(config);
await getSyncManager().sync();         // run one cycle

Every function returns either a value or a Result<T, E> — there's no surprise throwing.

Configuration

All configuration is environment-driven. The CLI looks for a .env file in the current working directory (process.cwd()), not the install location.

| Variable | Default | Description | | --------------------- | ------------------------------------------ | ---------------------------------------------------------- | | TODOIST_API_TOKEN | required | Todoist API token (Settings → Integrations → Developer) | | ANTHROPIC_API_KEY | required | Anthropic API key | | ANTHROPIC_MODEL | claude-haiku-4-5-20251001 | Any Claude model that supports Structured Outputs | | MAX_LABELS_PER_TASK | 5 | Hard cap on labels applied per task | | POLL_INTERVAL_MS | 15000 | Polling interval in ms (raise for less Todoist API churn) | | MAX_ERROR_LOGS | 1000 | FIFO cap on the local error log | | DB_PATH | <cwd>/data/todoist.db | SQLite database location | | LABELS_PATH | <cwd>/labels.json | Path to your label taxonomy | | LOG_LEVEL | info | debug | info | warn | error | | BACKFILL_ON_START | true | On boot, retry every still-unlabelled Inbox task that previously failed | | BACKFILL_INTERVAL_MS| 86400000 (24h) | Periodic sweep cadence for the same retry. 0 disables. | | BACKFILL_COOLDOWN_MS| 3600000 (1h) | Minimum gap between successive retries of the same task |

Supported Claude models

Structured Outputs is supported on Claude Haiku 4.5+, Sonnet 4.5+, and Opus 4+. (Anthropic docs)

| Model | Speed | Cost (in/out per 1M tok) | Best for | | -------------------------------- | ------- | ------------------------ | ------------------------------ | | claude-haiku-4-5-20251001 | Fastest | ~$1 / ~$5 | Recommended default | | claude-sonnet-4-5-20250929 | Fast | ~$3 / ~$15 | Higher-accuracy classification | | claude-opus-4-20250514 | Slower | ~$15 / ~$75 | Large or ambiguous taxonomies |

How it works

  1. Poll — Every POLL_INTERVAL_MS, fetch the Inbox via the Todoist API v1.
  2. Filter — Skip completed tasks and tasks that already have any labels.
  3. Classify — Send (content, description, available_labels[]) to Claude with a JSON-schema-constrained output_format. The model can only emit names from your taxonomy.
  4. Apply — Patch labels back to Todoist.
  5. Track — Record the attempt in SQLite (success, retry, or permanent fail after 3 tries).

The classifier uses Claude's Structured Outputs beta header, so JSON parsing is guaranteed — no schema-violation retries needed.

Retries and backfill

After 3 failed classification attempts a task is marked failed in the local DB and the regular sync loop stops re-trying it — otherwise a malformed or temporarily-broken task would burn classifier calls forever. Two safety valves bring those tasks back without manual intervention:

  • On boot (BACKFILL_ON_START=true, default) — Every previously-failed task that's still sitting unlabelled in the Inbox is reset to pending, so the very next sync cycle gives it another chance. If the API was down for a week, the next service restart will catch your week-old tasks up.
  • On a slow periodic sweep (BACKFILL_INTERVAL_MS=86400000, default 24h) — Same reset, run again on a separate timer while the service is running.

BACKFILL_COOLDOWN_MS (default 1h) caps how often a single task can be retried — a genuinely-unclassifiable task settles into ~1 attempt per cooldown interval rather than spinning. To opt out of the periodic sweep entirely set BACKFILL_INTERVAL_MS=0; to opt out of startup backfill set BACKFILL_ON_START=false.

If a task is truly unclassifiable and you want to stop attempts, the pragmatic move is to either delete it from Todoist or label it manually — both make the sync loop skip it on the next pass.

Project layout (for embedders)

src/
├── service.ts        # CLI entry point (#!/usr/bin/env node)
├── index.ts          # Library public surface (exports below)
├── config.ts         # Env-driven configuration
├── database.ts       # SQLite persistence
├── todoist-api.ts    # @doist/todoist-sdk wrapper
├── classifier.ts     # Claude classifier (Structured Outputs)
├── sync.ts           # Sync orchestration
├── logger.ts         # Levelled logger (chalk)
└── types.ts          # Branded IDs, Result<T,E>, domain types

Public exports: loadConfig, getConfig, resetConfig, createLogger, getLogger, initDatabase, getDatabase, closeDatabase, TodoistApiManager, initTodoistApi, getTodoistApi, resetTodoistApi, initClassifier, getClassifier, resetClassifier, SyncManager, initSyncManager, getSyncManager, resetSyncManager, plus all domain types from ./types.js.

Running as a daemon (optional)

The CLI is just a long-running Node process — pair it with whatever process manager you already use (systemd, launchd, pm2, Docker). The repo includes a sample ecosystem.config.cjs for PM2; see docs/deployment.md for details. The PM2 config is not published to npm.

Contributing

See CONTRIBUTING.md. TL;DR:

  • Conventional Commits (pnpm run commit opens a Commitizen prompt).
  • pnpm typecheck && pnpm test before opening a PR.
  • Releases are automated via release-please.

License

MIT © glorioustephan