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

@spora-ai/components

v0.1.1

Published

Shared Vue 3 component primitives (Avatar, Icon, palettes, composables) for Spora frontends.

Readme

@spora-ai/components

Shared Vue 3 primitives for the Spora ecosystem — Avatar, Icon, the agent palette, archetype iconography, and two composables that the host and each plugin frontend were otherwise reimplementing.

Single source of truth for anything that has to look identical across the admin app and every plugin. ESM, vue@^3.5 as a peer dependency, no runtime dependencies.

Install

npm install @spora-ai/components

Usage

<script setup lang="ts">
import '@spora-ai/components/styles' // required — see note below
import { AgentAvatar, STATUS_PALETTE } from '@spora-ai/components/avatar'
import { Icon } from '@spora-ai/components/icons'
</script>

<template>
  <AgentAvatar :agent="agent" size="md" />
  <Icon name="refresh" />
</template>

The stylesheet import is required. Components ship their own scoped CSS rather than depending on your Tailwind utilities, so they are styled even if you do not use Tailwind at all. Import it once, at your app entry:

// main.ts
import '@spora-ai/components/styles'

Your utility classes win

Those component defaults are wrapped in @layer components, so your own classes override them. On Tailwind v4 this is automatic: its @import "tailwindcss" puts utilities in a layer that outranks components. On Tailwind v3, or on no Tailwind at all, your unlayered classes outrank the layer anyway.

<!-- renders at 3rem, not the 1rem default -->
<Icon name="refresh" class="h-12 w-12" />

This works because of the cascade's layer rule: unlayered styles beat layered styles regardless of specificity. Before these defaults were layered, .spora-icon { width: 1rem } outranked a consumer's h-12 purely by being unlayered, and every <Icon class="h-…"> silently collapsed to 1rem. A consumer that passes no sizing class still gets the package default.

Subpaths

| Import | Exports | |---|---| | @spora-ai/components/avatar | Avatar, AgentAvatar, GroupAvatar, ArchetypeIcon, STATUS_PALETTE, statusDisplay | | @spora-ai/components/icons | Icon | | @spora-ai/components/composables | useInitials, useRelativeTime, formatRelativeTime | | @spora-ai/components/lib | PALETTES, paletteFor, archetypeElements, safeHex | | @spora-ai/components/types | ProfilePicture | | @spora-ai/components/styles | the stylesheet |

API

<Avatar>

Renders a profile picture: uploaded image, archetype SVG, or an initials fallback. Initials are derived from name — there is no initials prop.

{
  name?: string | null          // initials fallback
  profilePicture?: ProfilePicture | null
  size?: 'sm' | 'md' | 'lg' | 'xl'   // default 'md'
}

Theming the initials tile

The initials fallback paints a tile, and its two colours are the only themable part of the component. They are exposed as custom properties rather than props, so a theme-aware app re-themes them from its own stylesheet:

| Property | Default | Applies to | |---|---|---| | --spora-avatar-bg | #475569 (slate-600) | .avatar--initials background | | --spora-avatar-fg | #f8fafc (slate-50) | .avatar--initials text |

Set them anywhere that inherits to the avatar — a wrapper, .dark, :root. Declare each pair twice only if the values differ per theme:

/* matches `bg-muted text-foreground` */
:root {
  --spora-avatar-bg: hsl(0 0% 95.9%);
  --spora-avatar-fg: hsl(240 10% 3.9%);
}

.dark {
  --spora-avatar-bg: hsl(240 3.7% 15.9%);
  --spora-avatar-fg: hsl(240 10% 98%);
}

Leave them unset and the tile is the slate default, so an app that does nothing looks exactly as it did before. Nothing else is themable this way — the image and archetype branches take their colours from profilePicture, and sizing is yours via utility classes (see above).

<AgentAvatar> / <GroupAvatar>

Thin wrappers taking the wire payload directly:

<AgentAvatar :agent="agent" />          <!-- { name, profile_picture } -->
<GroupAvatar :group="group" />

<Icon>

Inline SVG by name — bell, refresh, chevron-right, plus, and 60 more. Glyphs are single-d path data only, so no raw SVG markup reaches the DOM.

Composables

  • useInitials(name) — 'Maria Rodriguez' → 'MR', 'Fabian' → 'FA', empty → '?'. Accepts a ref, getter, or plain string.
  • formatRelativeTime(iso) — '5m ago', '2h ago', 'yesterday', 'in a moment'.
  • useRelativeTime(iso) — reactive wrapper over formatRelativeTime; returns a zero-arg function that re-reads the ref on each call.

lib

  • PALETTES / paletteFor(key) — the 10 agent palettes, mirroring Spora\Services\AgentPictures\Palette. Unknown keys fall back to slate.
  • archetypeElements(archetype, variant) — 12 archetypes × 3 variants of inline SVG primitives, falling back to assistant/v0.
  • safeHex(color, fallback) — hex validator for values that reach a style binding. Rejects anything that is not #RGB / #RRGGBB / #RRGGBBAA.

STATUS_PALETTE

Maps a status string to its label and colour, so status pills look the same in every consumer:

| Status | Label | Colour | |---|---|---| | RUNNING | running | #10b981 | | PENDING_APPROVAL | awaiting approval | #6366f1 | | AWAITING_SUB_AGENTS | awaiting sub-agent | #f59e0b | | FAILED | failed | #ef4444 | | COMPLETED | idle | #94a3b8 | | ABORTED | aborted | #d946ef |

Unmapped statuses fall through to idle. Typed as string | null | undefined, so any wire enum is accepted.

Testing against this package

Vitest stubs CSS imports by default, so assertions on rendered size or colour read 0px. Set test.css: true and import the stylesheet in the spec if you assert on layout.

getComputedStyle cannot be used to assert that a consumer class overrides a component default. Neither happy-dom nor jsdom implements cascade layers — jsdom flattens @layer blocks and resolves them by source order alone, which answers a different question than the one a cascade-layer bug poses. Assert layer membership in the emitted CSS instead, as tests/cascadeLayer.spec.ts does, or check the built bundle in a real browser.

Development

npm install
npm run lint       # ESLint
npm test           # Vitest
npm run test:coverage
npm run build      # vue-tsc declarations + Vite lib-mode → dist/
npm run lint:pkg   # publint, validates the exports map against the build

node scripts/verify-exports.mjs asserts every exports target survives into the packed tarball — it catches a subpath that builds locally but the files field excludes from the published artifact.

MIT