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

@tutorflow/cli

v0.3.1

Published

TutorFlow CLI - agent-friendly tool for creating AI-powered educational content (slides, videos, courses) and evaluating student responses. Supports --json output, env-var auth, and non-interactive use.

Readme

@tutorflow/cli

TutorFlow CLI for creating AI-powered educational content (slides, videos, courses) and evaluating student responses from the terminal or from AI agents. Wraps the TutorFlow Platform API (/v1/platform/*).

Installation

A. End users (recommended)

Install globally with any package manager:

npm  install -g @tutorflow/cli
pnpm add    -g @tutorflow/cli
yarn global add @tutorflow/cli

Then anywhere on the system:

tutorflow --version
tutorflow auth login
tutorflow agent register --name "My Agent Workspace" --slug my-agent-workspace --email [email protected]
tutorflow --json platform manifest
tutorflow slide create "My first deck"

One-shot use without installing:

npx @tutorflow/cli slide create "My first deck"

This is the recommended path for AI agents and CI environments — no install step, always pulls the latest published version.

B. Local development (this repo)

git clone https://github.com/tutorflow-io/cli.git
cd cli
pnpm install
pnpm build
pnpm link              # exposes the `tutorflow` binary globally

Verify:

tutorflow --version       # matches package.json
tutorflow --help

To unlink later: npm unlink -g @tutorflow/cli.

Run from source without building:

pnpm dev -- <command> [options]
# e.g. pnpm dev -- slide list --limit 5

Releasing

Releases are automated by GitHub Actions on pushes to master / main. Auth uses npm OIDC Trusted Publishing — no NPM_TOKEN secret in CI.

git checkout master
git pull

npm version patch --no-git-tag-version  # 0.3.0 → 0.3.1 (or minor / major)
git push origin master  # Release workflow publishes if that version is not on npm yet

The Release workflow:

  1. Reads package.json name and version
  2. Skips npm publish if that exact version already exists
  3. Runs pnpm typecheck + pnpm build + pnpm test
  4. Publishes new versions with npm publish --access public (auth via OIDC)
  5. Creates a GitHub Release only for v*.*.* tag pushes

For full setup (npm org, Trusted Publisher config) see RELEASING.md.

Pre-release / beta

npm version prerelease --preid=beta --no-git-tag-version  # 0.3.1 → 0.3.2-beta.0
git push origin master

Pre-release versions publish to a dist-tag derived from the prerelease id (0.3.1-beta.0 publishes with --tag beta).

⚠️ Caveats & gotchas

These tripped us up during initial setup. Read before changing release infrastructure.

Versioning

  • Don't hardcode the version in src/index.ts. It reads version from package.json via resolveJsonModule. Hardcoding will silently lie to users via --version after every npm version bump.
  • The prepublishOnly script in package.json rebuilds dist/ before publish. Don't skip it.

npm Trusted Publishing (OIDC)

  • npm CLI must be 11.5.1+ for OIDC auth. npm also requires a modern Node 22 runtime for trusted publishing, so the workflow uses Node 22.x and invokes pnpm dlx npm@latest ... for registry checks and publish. Do not replace that with the runner's bundled npm unless you verify its version supports OIDC trusted publishing.

  • The Trusted Publisher binding at npmjs.com/package/@tutorflow/cli/access hardcodes:

    • GitHub org: tutorflow-io
    • Repo: cli
    • Workflow filename: release.yml
    • Environment: (none)

    Renaming the repo, moving the workflow file, or adding an environment: to the publish job will break OIDC auth until the binding is updated.

  • Local manual publishes still need a token (set NPM_TOKEN in .env or run npm login). Use a Granular Access Token with "Bypass two-factor authentication (2FA)" checked, otherwise CI/scripts get EOTP errors.

Provenance

  • --provenance is disabled in release.yml. npm only accepts provenance attestations from public GitHub repos. Add --provenance back to the npm publish command when this repo goes public.

Node + tooling

  • Vitest 4.x requires Node 20+ (uses node:util.styleText). CI tests Node 20 and Node 22, while release publishing runs on Node 22 for npm OIDC. The runtime CLI itself still supports Node 18 (declared in package.json#engines).

Tarball hygiene

  • tsconfig.json excludes **/*.test.ts so test files don't end up in dist/ (and therefore not in the published tarball).
  • package.json#files is an allowlist (dist, README.md, LICENSE). Anything not listed is excluded.
  • Run npm pack --dry-run to inspect the tarball before publish.

Source maps and a private repo

The published tarball currently includes .js.map and .d.ts.map files which embed paths from the build environment. While the source itself isn't shipped, this leaks file structure. If that matters, set sourceMap: false and declarationMap: false in tsconfig.json before going public.

Development

Run with tsx (no build step):

pnpm dev -- <command> [options]
# e.g. pnpm dev -- slide list --limit 5

Build artifacts land in dist/ (entry: dist/index.js, declared in package.json#bin).

Non-interactive / agent usage

Every command supports the contract below so AI agents and CI scripts can use the CLI without a TTY.

Auth without prompts

# Option 1: env var (preferred for one-shot calls)
TUTORFLOW_API_KEY=tf_xxx tutorflow --json slide list

# Option 2: persist to ~/.tutorflow/config.json once
tutorflow auth login --api-key tf_xxx
tutorflow --json slide create "Photosynthesis for 5th graders" -n 12

TUTORFLOW_API_KEY always wins over the config file. tutorflow auth whoami --json reports source: "env" | "config" so an agent can verify which credential is active.

Machine-readable output

--json (or TUTORFLOW_JSON=1) makes every command emit a single JSON line on stdout. Errors emit JSON to stderr.

$ tutorflow --json auth whoami
{"authenticated":true,"apiKeyPreview":"tf_xxxxxxx...","baseUrl":"https://api.tutorflow.io","source":"env"}

$ tutorflow --json auth whoami    # without auth
# stdout: (empty)
# stderr: {"error":{"message":"Not authenticated","code":"EAUTH"}}
# exit:   1

List commands return raw arrays:

$ tutorflow --json slide list --limit 3
[{"id":"...","title":"...","status":"ready","createdAt":"..."},...]

Pipe into jq for downstream automation:

tutorflow --json slide list --status ready | jq -r '.[].id' | xargs -I{} tutorflow --json slide get {}

Exit codes

| Code | Meaning | | ----- | ----------------------------------------------------------------- | | 0 | Success | | 1 | Generic failure (auth, network, API error, etc.) | | 2 | Bad input (invalid flag value, missing required input in non-TTY) | | 130 | User cancelled an interactive prompt (Ctrl+C) |

The error JSON includes a stable code: EAUTH, EINVAL, ENOINTERACTIVE, ENETWORK, EHTTP_<status>, ENOPREVIEW, ENOTFOUND, ECANCELLED.

Required-input flags (no prompts)

Commands that have required input expose every prompt as a flag:

# self-register a platform workspace and save the returned API key
tutorflow --json agent register \
  --name "Agent Workspace" \
  --slug agent-workspace \
  --email [email protected] \
  --agent-identity "codex"

# slide / video / course create — accept positional [prompt]
tutorflow --json slide create "Photosynthesis" -n 12 -l en
tutorflow --json video create "Newton's laws" -s 6 -r 16:9 -d 90
tutorflow --json course create "Beginner Python" -l 10 -lang en

# evaluate — every input is a flag
tutorflow --json evaluate \
  --type exact \
  --question "What is 2+2?" \
  --student-answer "4" \
  --expected-answer "4"

# logout (skip confirmation)
tutorflow auth logout --force

If a required input is missing and stdin is not a TTY, the command exits 2 with ENOINTERACTIVE and an error message naming the flag(s) to use. It never hangs waiting for input.

Globals

| Flag | Env var | Effect | | --------------------------------- | -------------------------------- | ----------------------------------------------- | | --json | TUTORFLOW_JSON=1 | JSON output to stdout, JSON errors to stderr | | --debug | DEBUG=1 or TUTORFLOW_DEBUG=1 | Print stack traces on errors | | --api-key <k> (auth login only) | TUTORFLOW_API_KEY | API key | | (n/a) | TUTORFLOW_BASE_URL | Override API base URL (staging, self-hosted) |

MCP server

A native MCP server (so agents can call tools directly without shelling out) is on the roadmap. For now, the --json shell contract is the recommended integration path.

Authentication

Generate an API key in your TutorFlow workspace, then:

tutorflow auth login      # interactive password prompt for API key
tutorflow auth whoami     # prints masked API key + base URL
tutorflow auth logout     # confirms then deletes ~/.tutorflow/config.json

Credentials are persisted to ~/.tutorflow/config.json:

{
  "apiKey": "tf_xxxxxxxxxxxxxxxxxxxx",
  "baseUrl": "https://api.tutorflow.io",
  "workspace": "optional-workspace-slug"
}

The API key is sent as the Authorization: Bearer <apiKey> header on authenticated requests.

Config schema

interface IConfig {
  apiKey?: string;     // required for all non-auth/config commands
  baseUrl?: string;    // default: https://api.tutorflow.io
  workspace?: string;  // optional workspace slug
}

Configuration commands

tutorflow config get                      # print full config (apiKey is masked)
tutorflow config get <key>                # print one key as KEY=VALUE
tutorflow config set <key> <value>        # update one key

| Key | Type | Default | Notes | | ----------- | -------- | -------------------------- | -------------------------------------- | | apiKey | string | none | Set via auth login or config set | | baseUrl | string | https://api.tutorflow.io | Override for staging / self-hosted | | workspace | string | none | Reserved for future scoping |

config set rejects unknown keys.

Commands

All resource commands require authentication. Without an API key they print: ✗ Not authenticated. Run "tutorflow auth login" first.

tutorflow agent

Manage the agent-facing TutorFlow Platform account flow.

agent register

Public self-service registration. Creates a platform workspace through POST /v1/platform/agent/register, returns the one-time API key, and saves it to ~/.tutorflow/config.json by default so the next CLI command can run without a separate auth login.

| Option | Type | Default | Description | | ------------------------------ | -------- | ------- | -------------------------------------------- | | --name <name> | string | — | Workspace display name | | --slug <slug> | string | — | Unique lowercase slug | | --email <email> | string | none | Contact email for verification and billing | | --agent-identity <identity> | string | none | Optional agent identity string | | --no-save | boolean | false | Print the API key but do not persist it |

tutorflow --json agent register \
  --name "Agent Workspace" \
  --slug agent-workspace \
  --email [email protected] \
  --agent-identity "codex"

agent account

GET /v1/platform/agent/account. Shows workspace status, credit balance, email verification state, rate limits, and restrictedActions.

tutorflow --json agent account

agent billing status

GET /v1/platform/agent/billing/status. Shows payment-method state, credit balance, billing mode, email verification state, and restrictedActions.

tutorflow --json agent billing status

agent billing session

POST /v1/platform/agent/billing/session. Returns a TutorFlow-hosted checkout URL that an agent should forward to a human operator.

tutorflow --json agent billing session

agent verify-email resend

POST /v1/platform/agent/verify-email/resend. Resends the workspace email verification message using the active API key.

tutorflow --json agent verify-email resend

agent checkout info

GET /v1/platform/agent/checkout?token=<token>. Reads workspace info for a TutorFlow checkout token returned through the hosted billing-session flow.

tutorflow --json agent checkout info --token checkout_123

agent checkout create

POST /v1/platform/agent/checkout. Creates a Stripe Checkout URL from a TutorFlow checkout token and amount. This is normally called by the hosted checkout page; CLI support exists for complete automation and testing.

tutorflow --json agent checkout create --token checkout_123 --amount-usd 25

tutorflow platform

Inspect public discovery metadata and validate the active Platform API key.

| Subcommand | Endpoint | Auth | | ---------- | -------- | ---- | | manifest | GET /v1/platform/.well-known/agent.json | none | | pricing | GET /v1/platform/pricing-catalog | none | | health | GET /v1/platform/health | Platform API key |

tutorflow --json platform manifest
tutorflow --json platform pricing
tutorflow --json platform health

tutorflow slide

Create and manage slide decks.

slide create [prompt]

Creates a slide deck synchronously (mode: 'sync').

| Argument / Option | Type | Default | Description | | ----------------------- | --------------------- | ------- | ------------------------------------ | | [prompt] | string (positional) | — | Prompted interactively if omitted | | -n, --count <number> | string → int | 10 | Slide count (parsed via parseInt) | | -l, --language <lang> | string | en | ISO 639-1 language code | | -t, --theme <theme> | string | none | Theme identifier |

POST /v1/platform/slides body:

{
  "prompt": "...",
  "slideCount": 10,
  "language": "en",
  "theme": "...",
  "mode": "sync"
}

Output: id, status, previewUrl (if returned). After creation, prompts to open the preview URL in the browser.

tutorflow slide create "Photosynthesis for 5th graders" -n 12 -l en -t classic

slide list

| Option | Type | Default | Description | | ----------------------- | -------- | ------- | -------------------------- | | -l, --limit <number> | string | 10 | Max rows to fetch | | -s, --status <status> | string | none | Server-side status filter |

GET /v1/platform/slides?limit=<n>[&status=<s>]. Renders an ASCII table with ID (8-char prefix), Title, Status, Created.

slide open <id>

Fetches /v1/platform/slides/:id and opens previewUrl in the default browser via open.


tutorflow video

Create and manage AI-generated videos.

video create [prompt]

Creates a video synchronously (mode: 'sync').

| Argument / Option | Type | Default | Description | | -------------------------- | --------------------- | ------- | ---------------------------------------- | | [prompt] | string (positional) | — | Prompted interactively if omitted | | -s, --scenes <number> | string → int | 5 | Scene count | | -r, --ratio <ratio> | string | 16:9 | Aspect ratio (16:9, 9:16, 1:1, …) | | -d, --duration <seconds> | string → int | 60 | Target duration in seconds | | -l, --language <lang> | string | en | ISO 639-1 language code |

POST /v1/platform/videos body:

{
  "prompt": "...",
  "sceneCount": 5,
  "aspectRatio": "16:9",
  "targetDurationSeconds": 60,
  "language": "en",
  "mode": "sync"
}

Output: id, status, sceneCount, previewUrl (if returned). Prompts to open in browser.

tutorflow video create "Intro to Newton's laws" -s 6 -r 16:9 -d 90 -l en

video list

| Option | Type | Default | Description | | ----------------------- | -------- | ------- | -------------------------- | | -l, --limit <number> | string | 10 | Max rows | | -s, --status <status> | string | none | Server-side status filter |

GET /v1/platform/videos?limit=<n>[&status=<s>]. Table columns: ID, Title, Status, Scenes, Created.

video open <id>

Opens the video preview URL in the browser.


tutorflow course

Create and manage multi-lesson courses.

course create [prompt]

| Argument / Option | Type | Default | Description | | -------------------------- | --------------------- | ------- | --------------------------------- | | [prompt] | string (positional) | — | Prompted interactively if omitted | | -l, --lessons <number> | string → int | 8 | Lesson count | | -lang, --language <lang> | string | en | ISO 639-1 language code |

Note: --language uses the short flag -lang (not -L), because -l is already taken by --lessons.

POST /v1/platform/courses body:

{
  "prompt": "...",
  "lessonCount": 8,
  "language": "en",
  "mode": "sync"
}
tutorflow course create "Beginner Python in Korean" -l 10 -lang ko

course list

| Option | Type | Default | Description | | ---------------------- | -------- | ------- | ----------- | | -l, --limit <number> | string | 10 | Max rows |

GET /v1/platform/courses?limit=<n>. Table columns: ID, Title, Status, Created.

course open <id>

Opens the course preview URL in the browser.


tutorflow module

Create and manage standalone lesson modules.

module create [prompt]

POST /v1/platform/modules. Generates one classroom-compatible standalone lesson. --idempotency-key is sent as the Idempotency-Key header so agents can safely retry a create call.

| Argument / Option | Type | Default | Description | | ------------------------------ | --------------------- | -------- | -------------------------------- | | [prompt] | string (positional) | — | Lesson/module prompt | | --title <title> | string | none | Override generated title | | --description <description> | string | none | Override generated description | | --target <target> | string | none | Target learner audience | | --type <type> | string | lesson | Module type | | --locale <locale> | string | en | Locale code | | --language <language> | string | en | Language code | | --slug <slug> | string | none | Optional slug | | --private | boolean | false | Set isPublic: false | | --no-quiz | boolean | false | Set hasQuiz: false | | --idempotency-key <key> | string | none | Safe retry key |

tutorflow --json module create "Fractions for 5th grade" \
  --title "Fractions" \
  --target "5th grade" \
  --no-quiz \
  --idempotency-key lesson-001

module list

GET /v1/platform/modules?limit=<n>&offset=<n>[&search=<q>].

tutorflow --json module list --limit 20 --offset 0 --search fractions

module get <id> / module open <id>

Fetch a module by ID or open its publicUrl.


tutorflow test

Create and manage generated assessments.

test create [prompt]

POST /v1/platform/tests. Generates a test/assessment. --idempotency-key is sent as the Idempotency-Key header.

| Argument / Option | Type | Default | Description | | ------------------------- | --------------------- | ---------- | ------------------------------ | | [prompt] | string (positional) | — | Test generation prompt | | --title <title> | string | none | Override generated title | | --description <text> | string | none | Override generated description | | -l, --language <lang> | string | en | Language code | | --level <level> | string | none | Difficulty level | | --subject <subject> | string | none | Subject area | | -n, --items <number> | string -> int | none | Target item count | | --time-limit <minutes> | string -> int | none | Time limit in minutes | | --classroom-id <id> | string | none | Classroom ID | | --tier <tier> | string | standard | Pricing tier | | --mode <mode> | string | sync | sync or async | | --idempotency-key <key> | string | none | Safe retry key |

tutorflow --json test create "Algebra quiz" \
  --title "Algebra Basics" \
  --items 8 \
  --time-limit 30 \
  --idempotency-key test-001

test list

GET /v1/platform/tests?limit=<n>&offset=<n>[&status=<s>].

tutorflow --json test list --limit 20 --status READY

test get <id> / test open <id> / test edit-token <id>

Fetch a test, open its preview URL, or generate a fresh edit token.


tutorflow evaluate

Evaluates a single learner response through the Platform Evaluations API.

| Option | Type | Default | Allowed values / notes | | ------------------------- | -------- | ---------- | ---------------------- | | -t, --type <type> | string | (prompted) | exact, rubric_short_answer, open_ended. Aliases: rubric, open-ended | | -q, --question <text> | string | (prompted) | Question text | | -a, --student-answer <text> | string | (prompted) | Learner answer | | -e, --expected-answer <text> | string | none | Required for exact; sent as referenceAnswer | | --rubric-json <json> | string | none | Required for rubric_short_answer | | --max-score <number> | number | backend default | Maximum score | | -l, --language <lang> | string | en | Feedback language | | --idempotency-key <key> | string | none | Safe retry key |

Interactive flow:

  1. Type (if not supplied)
  2. Question
  3. Student answer
  4. Expected answer — required only when type is exact

POST /v1/platform/evaluations body:

{
  "evaluationType": "exact",
  "questionText": "What is 2+2?",
  "learnerAnswer": "4",
  "referenceAnswer": "4",
  "language": "en",
  "mode": "sync"
}

referenceAnswer is omitted when evaluationType is open_ended. rubric_short_answer requires --rubric-json, for example:

tutorflow --json evaluate \
  --type rubric_short_answer \
  --question "Explain the water cycle" \
  --student-answer "Water evaporates and condenses." \
  --rubric-json '{"criteria":[{"name":"Accuracy","description":"Scientific correctness","maxScore":10}]}'

tutorflow auth

| Subcommand | Description | | ---------- | ---------------------------------------------------- | | login | Interactive prompt for API key, writes to config | | logout | Prompts to confirm, then deletes config file | | whoami | Prints masked API key (first 10 chars) and baseUrl |

Exit behavior & errors

  • All commands wrap their action in try/catch. Failures print ✗ <message> in red and the raw error, but the process exit code is not set explicitly — it's whatever Node returns (typically 0). Don't rely on exit codes for scripting yet.
  • await prompt(...) from @clack/prompts returns a symbol when the user cancels (Ctrl+C); commands check for this and print Cancelled instead of throwing.
  • HTTP errors from the API are surfaced as Error: API Error: <status> <body-json>.

API client

Internal HTTP client lives in src/client.ts:

class ApiClient {
  static async create(): Promise<ApiClient>;        // loads from ~/.tutorflow/config.json
  request<T>(method: string, endpoint: string, body?: object): Promise<T>;
  setApiKey(apiKey: string): void;
  setBaseUrl(baseUrl: string): void;
}
  • Uses undici.fetch.
  • Headers always include Content-Type: application/json. Authenticated requests also include Authorization: Bearer <apiKey>.
  • Throws Error('API Error: <status> <body>') on non-2xx.

Project layout

.
├── package.json              # bin: { tutorflow: ./dist/index.js }
├── tsconfig.json
├── vitest.config.ts
├── .github/workflows/
│   ├── ci.yml                # typecheck + build + test on PRs and master
│   └── release.yml           # OIDC publish on master/main pushes when version is new
├── RELEASING.md              # full release procedure + Trusted Publisher setup
└── src/
    ├── index.ts              # commander entrypoint, registers all commands
    ├── client.ts             # undici-based ApiClient
    ├── client.test.ts
    ├── config.ts             # ~/.tutorflow/config.json read/write/update/delete
    ├── config.test.ts
    ├── types.ts              # API response interfaces (SlideResource, etc.)
    ├── commands/
    │   ├── agent.ts          # register / account / billing / email verification
    │   ├── auth.ts           # login / logout / whoami
    │   ├── config.ts         # get / set
    │   ├── course.ts         # create / list / open
    │   ├── evaluate.ts       # interactive evaluation
    │   ├── module.ts         # standalone lesson modules
    │   ├── platform.ts       # discovery / pricing / health
    │   ├── slide.ts          # create / list / open
    │   ├── test.ts           # generated assessments
    │   └── video.ts          # create / list / open
    └── utils/
        ├── output.ts         # formatTable, formatJson
        └── spinner.ts

npm scripts

| Script | Command | Purpose | | ---------------- | ----------------------- | --------------------------------------- | | dev | tsx src/index.ts | Run from source | | build | tsc --outDir dist | Emit JS + .d.ts to dist/ | | link | npm link | Symlink tutorflow globally | | typecheck | tsc --noEmit | Type-check without emitting | | test | vitest run | Run all tests once | | test:watch | vitest | Watch-mode tests | | test:coverage | vitest run --coverage | Generate coverage report | | prepublishOnly | rm -rf dist && build | Auto-runs before npm publish |

Stack