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

@ztalkai/spec-flow-cli

v0.1.3

Published

SpecFlow supported command-line client

Downloads

446

Readme

SpecFlow CLI

@ztalkai/spec-flow-cli uses OAuth Device Authorization to create an independent seven-day Better Auth CLI Session. It supports the Node.js 24 runtime line; Node.js 22 and unverified future major versions are intentionally unsupported.

npm install --global @ztalkai/[email protected]
specflow --server https://specflow.example auth login
specflow --server https://specflow.example auth status
specflow --server https://specflow.example auth logout

After login, resource commands reuse the stored CLI Session and emit structured JSON on stdout. Existing-Project commands always require an explicit --project-id:

SPEC_FLOW_API_URL=https://specflow.example specflow project list
SPEC_FLOW_API_URL=https://specflow.example specflow project list --status Archived
SPEC_FLOW_API_URL=https://specflow.example specflow project create --name "Payments" --project-id PAY
SPEC_FLOW_API_URL=https://specflow.example specflow project create --name "Payments" --project-id PAY --gherkin-language zh-CN
SPEC_FLOW_API_URL=https://specflow.example specflow project archive --project-id <project-id> --expected-version '"1"'
SPEC_FLOW_API_URL=https://specflow.example specflow project restore --project-id <project-id> --expected-version '"2"'
SPEC_FLOW_API_URL=https://specflow.example specflow project directory --project-id <project-id>
SPEC_FLOW_API_URL=https://specflow.example specflow spec read --project-id <project-id> --spec-id <spec-id>
SPEC_FLOW_API_URL=https://specflow.example specflow spec current --project-id <project-id> --spec-id <spec-id>
SPEC_FLOW_API_URL=https://specflow.example specflow spec revision --project-id <project-id> --spec-id <spec-id> --revision-id <revision-id>
SPEC_FLOW_API_URL=https://specflow.example specflow spec changelog --project-id <project-id> --spec-id <spec-id>
SPEC_FLOW_API_URL=https://specflow.example specflow spec edit --project-id <project-id> --spec-id <spec-id> --gherkin-source "Feature: Payments"
SPEC_FLOW_API_URL=https://specflow.example specflow spec assign-scenario-ids --project-id <project-id> --spec-id <spec-id>
SPEC_FLOW_API_URL=https://specflow.example specflow spec publish --project-id <project-id> --spec-id <spec-id>
SPEC_FLOW_API_URL=https://specflow.example specflow glossary edit --project-id <project-id> --entries '[{"id":"payment","canonicalTerm":"Payment","definition":"A completed transfer.","usageNote":null}]'
SPEC_FLOW_API_URL=https://specflow.example specflow proposal create-spec --project-id <project-id> --spec-id <spec-id> --baseline-gherkin-source "Feature: Payments" --proposed-gherkin-source "Feature: Payments" --agent-revision <revision> --agent-model <model> --source-reference-ids '["source-id"]'
SPEC_FLOW_API_URL=https://specflow.example specflow proposal list --project-id <project-id> --target-kind Spec --spec-id <spec-id> --status Open
SPEC_FLOW_API_URL=https://specflow.example specflow proposal apply --project-id <project-id> --proposal-id <proposal-id> --change-ids '["hunk-1"]'
SPEC_FLOW_API_URL=https://specflow.example specflow proposal discard --project-id <project-id> --proposal-id <proposal-id>
SPEC_FLOW_API_URL=https://specflow.example specflow authoring-history --project-id <project-id>

Every official CLI service request sends the versioned @ztalkai/spec-flow-cli/<version> User-Agent. Diagnostics use stderr, and stable Skill Errors use documented reason and exit-status categories.

Machine contract

Resource-command success writes exactly one newline-terminated JSON object to stdout. stderr is newline-delimited JSON (NDJSON): zero or more diagnostics use { "diagnostic": { "level": "warning", "message": string } }. These command/result pairs are the supported schemas:

| Command | Success object | | --- | --- | | project list [--status Active\|Archived] | { projects: ProjectSummary[] } | | project create | { project: ProjectSummary } | | project archive | { project: ProjectSummary } | | project restore | { project: ProjectSummary } | | project context | { project: ProjectContext, publishedGlossary: PublishedGlossary \| null } | | project directory | { capabilities: Capability[], permissions: Permissions, specs: SpecSummary[] } | | spec search | { nextCursor: string \| null, results: SearchResult[], totalCount: number } | | spec read | { currentRevision: SpecRevision \| null, permissions: Permissions, spec: SpecDetails } | | spec current | { revision: SpecRevision, spec: SpecSummary } | | spec revision | { revision: SpecRevision } | | spec changelog | { entries: SpecChangelogEntry[] } | | spec create | { spec: SpecDetails } | | spec update | { spec: SpecDetails } | | spec draft | { spec: SpecDetails } | | spec edit | { draft: { draftSessionId: string, gherkinSource: string, specId: string } } | | spec assign-scenario-ids | { assignedScenarioIds: string[], draft: { draftSessionId: string, gherkinSource: string, specId: string } } | | spec publish | { revision: SpecRevision, spec: SpecSummary } | | spec state | { spec: SpecSummary } | | spec rollback | { revision: SpecRevision, spec: SpecSummary } | | spec delete | { deleted: { specId: string } } | | glossary read | { currentRevision: GlossaryRevision \| null, draftSession: GlossaryDraftSession \| null, permissions: Permissions } | | glossary changelog | { entries: GlossaryChangelogEntry[] } | | glossary revision | { revision: GlossaryRevision } | | glossary draft | { glossary: GlossaryDetails } | | glossary edit | { draft: { draftSessionId: string, entries: GlossaryEntry[] } } | | glossary publish | { revision: GlossaryRevision } | | glossary rollback | { revision: GlossaryRevision } | | proposal create-spec\|create-glossary | { proposal: DraftProposal } | | proposal list | { proposals: DraftProposal[] } | | proposal apply | { mergeOutcome: "Applied", proposal: DraftProposal } | | proposal discard | { proposal: DraftProposal } | | authoring-history | { operations: AuthoringOperation[] } | | capability create | { capability: Capability } | | capability update | { capability: Capability } | | capability delete | { deleted: { capabilityId: string } } | | member list | { members: ProjectMember[] } | | member directory | { candidates: DirectoryIdentity[] } | | member add | { member: ProjectMember } | | member change-role | { member: ProjectMember } | | member remove | { removed: { userId: string } } | | token issue | { credential: string, token: ProjectReadToken } | | token list | { tokens: ProjectReadToken[] } | | token revoke | { revoked: { tokenId: string } } | | review request | { review: ManualReviewRecord } | | review read | { review: ManualReviewRecord } | | finding list | { findings: ReviewFinding[], nextCursor: string \| null, permissions: FindingPermissions } | | finding read | { finding: ReviewFinding, permissions: FindingPermissions } | | finding create | { finding: ReviewFinding, permissions: FindingPermissions } | | finding confirm\|dismiss\|resolve | { finding: ReviewFinding, permissions: FindingPermissions } |

The named JSON shapes are:

type ProjectSummary = {
  effectiveCapabilities: {
    canAdministerProject: boolean;
    canEditProject: boolean;
    canReadProject: true;
  };
  gherkinLanguage: string;
  id: string;
  name: string;
  role: "Admin" | "Editor" | "Viewer";
  status: "Active" | "Archived";
  version: string; // opaque ETag
};
type ProjectContext = {
  gherkinLanguage: string;
  id: string;
  name: string;
  status: "Active" | "Archived";
  version: string; // opaque ETag
};
type GlossaryEntry = {
  canonicalTerm: string;
  definition: string;
  id: string;
  usageNote: string | null;
};
type PublishedGlossary = {
  entries: GlossaryEntry[];
  id: string;
  publishedAt: string; // ISO 8601 date-time
};
type Capability = {
  id: string;
  name: string;
  parentId: string | null;
  version: string; // opaque ETag, for example: "\"1\""
};
type ProjectMember = {
  email: string | null;
  issuer: string;
  name: string;
  role: "Admin" | "Editor" | "Viewer";
  subject: string;
  userId: string;
  version: string; // opaque ETag
};
type DirectoryIdentity = {
  email: string | null;
  issuer: string;
  name: string;
  subject: string;
  username: string;
};
type Permissions = { canEdit: boolean };
type FindingPermissions = { canWrite: boolean };
type SourceReference = {
  externalRevision?: string;
  id: string;
  section?: string;
  title: string;
  url: string;
};
type SpecSummary = {
  capabilityId: string;
  featureName: string;
  id: string;
  sourceReferences: SourceReference[];
  state: "Draft" | "Ready" | "Unavailable";
  unavailabilityReason: string | null;
  version: string; // opaque ETag
};
type SpecDetails = SpecSummary & {
  draftSession: {
    documentName: string;
    id: string;
    textName: "gherkin-source";
  } | null;
};
type SpecRevision = {
  gherkinSource: string;
  id: string;
  publishedAt: string; // ISO 8601 date-time
};
type CapabilityPathEntry = { id: string; name: string };
type SearchResult = {
  capabilityPath: CapabilityPathEntry[];
  spec: SpecSummary;
};
type SourceReferenceInput = Omit<SourceReference, "id">;
type SpecChangelogEntry = {
  acceptedAt: string; // ISO 8601 date-time
  action:
    | "Published"
    | "RolledBack"
    | "StateChanged"
    | "MetadataChanged"
    | "FindingCreated"
    | "FindingConfirmed"
    | "FindingDismissed"
    | "FindingResolved"
    | "FindingStale";
  actorSessionId: string;
  actorUserId: string;
  changes: {
    content: { from: string | null; to: string } | null;
    finding: {
      findingId: string;
      status: "Confirmed" | "Dismissed" | "Proposed" | "Resolved" | "Stale";
    } | null;
    metadata: {
      capabilityId?: { from: string; to: string };
      sourceReferences?: {
        from: SourceReferenceInput[];
        to: SourceReferenceInput[];
      };
    } | null;
    state: {
      from: "Draft" | "Ready" | "Unavailable";
      fromUnavailabilityReason: string | null;
      to: "Draft" | "Ready" | "Unavailable";
      toUnavailabilityReason: string | null;
    } | null;
  };
  id: string;
  previousRevisionId: string | null;
  revisionId: string | null;
};
type GlossaryRevision = {
  entries: GlossaryEntry[];
  id: string;
  publishedAt: string; // ISO 8601 date-time
};
type GlossaryDraftSession = {
  documentName: string;
  entriesName: "entries";
  id: string;
};
type GlossaryDetails = {
  currentRevision: GlossaryRevision | null;
  draftSession: GlossaryDraftSession;
};
type GlossaryChangelogEntry = {
  acceptedAt: string; // ISO 8601 date-time
  action: "Published" | "RolledBack";
  actorSessionId: string;
  actorUserId: string;
  changes: {
    added: GlossaryEntry[];
    removed: GlossaryEntry[];
    updated: { from: GlossaryEntry; to: GlossaryEntry }[];
  };
  id: string;
  previousRevisionId: string | null;
  revisionId: string;
};
type ProjectReadToken = {
  createdAt: string; // ISO 8601 date-time
  expiresAt: string | null; // ISO 8601 date-time
  id: string;
  issuer: { id: string; name: string };
  lastUsedAt: string | null; // ISO 8601 date-time
  name: string;
  revokedAt: string | null; // ISO 8601 date-time
};
type FindingType =
  | "Contradiction"
  | "Conflict"
  | "Duplicate"
  | "Ambiguity"
  | "Terminology"
  | "Unverifiable";
type ManualReviewRecord = {
  agentRevision: string;
  completedAt: string | null; // ISO 8601 date-time
  createdAt: string; // ISO 8601 date-time
  draft: { contentHash: string; draftSessionId: string; specId: string };
  enabledFindingTypes: FindingType[];
  failureCategory:
    | "InvalidOutput"
    | "ProjectInactive"
    | "TerminalExecutionFailure"
    | "Timeout"
    | null;
  findingIds: string[];
  glossaryRevisionId: string | null;
  id: string;
  knownFindingIds: string[];
  mastraExecutionVersion: string;
  model: string | null;
  projectId: string;
  requestedBy: { sessionId: string; userId: string };
  reviewContractVersion: string;
  startedAt: string | null; // ISO 8601 date-time
  status:
    | "Queued"
    | "Running"
    | "ProposedFindings"
    | "Superseded"
    | "Cancelled"
    | "Failed";
  trigger: "ManualDraft";
  updatedAt: string; // ISO 8601 date-time
};
type FindingReference = {
  kind: "Feature" | "Rule" | "Background" | "Scenario" | "Examples" | "Step";
  text: string;
};
type FindingTarget = {
  binding:
    | { kind: "Revision"; revisionId: string }
    | {
        contentHash: string;
        draftSessionId: string;
        kind: "WorkingDraftSnapshot";
      };
  references: FindingReference[];
  specId: string;
};
type FindingEvidence =
  | { incompatibleClaims: [string, string]; kind: "Contradiction" }
  | { incompatibleRequirements: [string, string]; kind: "Conflict" }
  | { duplicatedBehavior: string; kind: "Duplicate" }
  | {
      interpretations: [
        { behavior: string; meaning: string },
        { behavior: string; meaning: string },
      ];
      kind: "Ambiguity";
    }
  | {
      glossaryEntryIds: string[];
      issue: "MissingDefinition" | "GlossaryConflict" | "MultipleMeanings";
      kind: "Terminology";
      term: string;
    }
  | { kind: "Unverifiable"; missingCriterion: string };
type ReviewFinding = {
  affectedScenarios: string[];
  author: { sessionId: string; userId: string };
  conclusion: string;
  createdAt: string; // ISO 8601 date-time
  evidence: FindingEvidence;
  explanation: string;
  glossaryReferences: {
    entry: GlossaryEntry;
    entryId: string;
    revisionId: string;
  }[];
  id: string;
  projectId: string;
  reviewRecordId: string | null;
  status: "Proposed" | "Confirmed" | "Dismissed" | "Resolved" | "Stale";
  targets: FindingTarget[];
  type: FindingType;
  updatedAt: string; // ISO 8601 date-time
  version: string; // opaque ETag
};

A failed resource command writes nothing to stdout. After any diagnostic lines, the final stderr line is exactly { "error": { "message": string, "reason": SkillErrorReason, "retryAfter"?: string, "retryable": boolean, "diagnostics"?: SkillErrorDiagnostic[] } } where type SkillErrorDiagnostic = { column?: number; line?: number; message: string; reason: string }. diagnostics is present whenever the server reports positioned source problems: Spec Publish and Rollback rejections from the Gherkin Publish Gate carry one entry per feature-count, language-mismatch, parse-error, scenario-required, or scenario-id-* reason, spec assign-scenario-ids carries the same shape when the Working Draft source is invalid, and other Invalid Operation rejections may carry it too. SkillErrorReason is Authentication Failed, Project Not Accessible, Permission Denied, Resource Not Found, Resource Changed, Invalid Operation, or Temporary Failure. Exit status 2 covers authentication, validation, inaccessible/not-found resources, conflicts, and invalid operations; 3 is permission denial; 4 is a Temporary Failure, whose retryable field determines whether automatic retry is safe. Exit status 0 is success. Exit status 1 is reserved for startup/runtime failure before a resource-command contract can run.

HTTP 429 is a retryable Temporary Failure. When the server supplies Retry-After, the CLI copies its exact value to retryAfter and does not retry the request automatically. Generated --help is a separate controlled success path: top-level help includes authentication and every resource group, nested help includes the selected group's operations and options, and all help writes to stdout, exits zero, and does not resolve credentials or call the service.

ProjectSummary.effectiveCapabilities incorporates lifecycle status. Archived Projects always report canEditProject: false, including for Admins and Editors; an Archived Admin still reports canAdministerProject: true for Restore and access reduction. member add is create-only: an existing issuer-and-subject Membership is an Invalid Operation, and its Role can change only through member change-role with the version returned by member list. Archived Projects reject Member addition and Role expansion while still permitting versioned Role reduction and removal.

Every non-idempotent create, Draft-session, Publish, Rollback, Membership mutation, token issuance, Finding disposition, Manual Review request, and Project lifecycle command performs at most one fetch-transport retry. It clones the request and reuses the same Idempotency-Key; an HTTP response, including a response whose body cannot be decoded, is never dispatched again. Archive and Restore require the opaque Project version returned by discovery or context; Membership role changes and removals require the version returned by member list. If neither transport attempt yields a usable response, the final Temporary Failure has retryable: false and directs the caller to inspect the resource or Changelog before deciding whether another logical mutation is appropriate.

Token issuance returns the plaintext credential only for the first committed response. If a same-key retry confirms the token with credential: null, the CLI exits nonzero, prints no success payload, and directs the caller to list or revoke the token before deliberately issuing another.

Source Reference URLs accept only HTTP or HTTPS and must not contain URL userinfo (username or password). Keep external credentials in the Agent's own authorized source-reading tool; never embed them in SpecFlow metadata.

The repository-owned official Skill is skills/specflow/SKILL.md. It pins this exact CLI release, requires explicit User confirmation immediately before Spec or Glossary Publish and Rollback, and does not send proof of that confirmation to the CLI or API.

The CLI stores its Session credential in macOS Keychain, Windows Credential Manager, or Linux Secret Service. It does not print or persist the credential in environment variables. If the system store is unavailable on a POSIX platform, local-file storage is used only after --allow-file-fallback is explicitly selected; the CLI warns and enforces directory mode 0700 and file mode 0600.