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

@lorenzoangelini1995/musubi

v0.1.5

Published

AI orchestration layer with Gemini client, HITL support, and cost tracking

Downloads

797

Readme

Musubi 結び

Generic AI orchestration layer for Expo + Supabase apps. Typed Gemini client, intent-based orchestrator, HITL pattern, cost tracking.

Named after the Shinto concept of musubi (結び) — the generative force that creates and connects. An AI that generates adventures, validates photos, and orchestrates complex flows.


Features

  • Typed Gemini clientgenerate, chat, vision with full TypeScript types
  • Intent-based orchestrator — register handlers, route requests, handle errors uniformly
  • HITL pattern — built-in Human-in-the-Loop for multi-step flows
  • Runtime-agnostic input contract — core models human input needs, app layers map them to concrete UI
  • Cost tracking — per-request token usage and USD cost for every Gemini call
  • JSON mode — structured output with automatic parsing and markdown fence fallback
  • Provider-ready — designed to run in both Node.js (monorepo) and Deno (Supabase Edge Functions via npm:)

Installation

pnpm add "@lorenzoangelini/musubi@workspace:*"

For Supabase Edge Functions (Deno):

import { GeminiClient, BaseOrchestrator } from 'npm:@lorenzoangelini/musubi'

GeminiClient

Typed wrapper around @google/generative-ai with built-in cost tracking.

Text generation

import { GeminiClient } from '@lorenzoangelini/musubi'

const gemini = new GeminiClient(process.env.GEMINI_API_KEY!)

const result = await gemini.generate(
  'Write a short mystery riddle set in Tokyo.',
  { model: 'gemini-2.5-flash', temperature: 0.8 }
)

console.log(result.text)
console.log(`Cost: $${result.costUsd.toFixed(5)} — ${result.inputTokens}in / ${result.outputTokens}out tokens`)

Structured JSON output

type Adventure = { title: string; checkpoints: { name: string; hint: string }[] }

const result = await gemini.generate(prompt, { json: true })
const adventure = gemini.parseJSON<Adventure>(result)
// adventure.checkpoints[0].name — fully typed

Multi-turn chat

const result = await gemini.chat([
  { role: 'user',  content: 'Create an adventure in Kyoto, history theme.' },
  { role: 'model', content: 'Sure! How long should it be?' },
  { role: 'user',  content: '90 minutes, on foot.' },
], { model: 'gemini-2.5-flash' })

Vision (photo validation)

const result = await gemini.vision(
  'Does this photo show a maneki-neko (lucky cat)? Reply with JSON: { match: boolean, confidence: number, reason: string }',
  { mimeType: 'image/jpeg', data: base64Image },
  { json: true, model: 'gemini-2.5-flash' }
)

const { match, confidence } = gemini.parseJSON<{ match: boolean; confidence: number }>(result)

Models

| Model | Best for | Input $/1M | Output $/1M | |-------|----------|-----------|------------| | gemini-2.5-flash | Generation, vision | $0.30 | $2.50 | | gemini-2.5-flash-lite | Simple tasks, high volume | $0.10 | $0.40 | | gemini-2.0-flash | Fast multimodal | $0.10 | $0.40 |

Default model: gemini-2.5-flash-lite.


BaseOrchestrator

Register intent handlers and route requests through a uniform interface.

import { BaseOrchestrator } from '@lorenzoangelini/musubi'
import type { IntentHandler } from '@lorenzoangelini/musubi'

// Define a handler
const createAdventureHandler: IntentHandler<CreateAdventurePayload, Adventure> = {
  intent: 'create_adventure',
  async handle(req, ctx) {
    // ... generate with Gemini
    return { status: 'done', result: adventure, runId: ctx.runId }
  },
}

// Build the orchestrator
const orchestrator = new BaseOrchestrator()
  .register(createAdventureHandler)
  .register(validateCheckpointHandler)
  .register(requestHintHandler)

// Run
const response = await orchestrator.run({
  intent: 'create_adventure',
  payload: { city: 'Tokyo', theme: 'historic', durationMinutes: 90 },
  context: { userId: 'uuid-123', location: { lat: 35.6762, lng: 139.6503 } },
})

HITL — Human in the Loop

When the AI needs user input mid-flow, return waiting_human and resume later with the same runId. Musubi describes the input abstractly; your app or runtime maps it to a concrete widget.

const createAdventureHandler: IntentHandler = {
  intent: 'create_adventure',
  async handle(req, ctx) {
    const { theme, city } = req.payload

    // Missing required slot — ask the user
    if (!theme) {
      return ctx.requestHITL({
        runId: ctx.runId,
        question: 'What theme would you like for the adventure?',
        input: {
          kind: 'single_select',
          label: 'Choose a theme',
          options: [
            { label: 'Historic', value: 'historic' },
            { label: 'Food', value: 'food' },
            { label: 'Mystery', value: 'mystery' },
          ],
          metadata: { uiType: 'chip_selector' },
        },
        slotsFilled: ['city'],
        slotsMissing: ['theme'],
      })
    }

    // All slots filled — generate
    const result = await generateAdventure({ city, theme })
    return { status: 'done', result, runId: ctx.runId }
  },
}

// Client resumes with user's choice
const response = await orchestrator.run({
  intent: 'resume',
  runId: previousRunId,       // same run
  payload: { user_response: 'historic' },
  context: { userId },
})

Cost tracking

import { calculateCost, formatCost, aggregateCost } from '@lorenzoangelini/musubi'

const cost = calculateCost('gemini-2.5-flash', 1200, 800)
console.log(formatCost(cost))  // "$0.0024"

// Aggregate across multiple calls
const total = aggregateCost([usage1, usage2, usage3])

OrchestratorResponse shape

type OrchestratorResponse<T> =
  | { status: 'done';          result: T;     runId: string }
  | { status: 'waiting_human'; runId: string; message: string; input: HumanInputSchema; slotsFilled: string[]; slotsMissing: string[] }
  | { status: 'error';         runId: string; code: string; message: string }

Example input contract:

type HumanInputSchema = {
  kind: 'text' | 'single_select' | 'multi_select' | 'number' | 'confirm' | 'media'
  label: string
  options?: { label: string; value: string }[]
  min?: number
  max?: number
  defaultValue?: unknown
  metadata?: Record<string, unknown>
}

Architecture

src/
  clients/
    gemini.ts          Typed Gemini wrapper — generate, chat, vision, parseJSON
  orchestrator/
    base.ts            BaseOrchestrator — register(), run(), error boundary
  utils/
    cost.ts            calculateCost, formatCost, aggregateCost
  types.ts             All shared types
  index.ts

Musubi is intentionally generic — no Kibbo-specific logic. App-level intent handlers live in the Supabase Edge Function layer and use Musubi as a dependency.


License

Private — part of the Kibbo monorepo.