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

@candoa/workflows

v0.1.0

Published

Type-safe workflow SDK for Candoa with Copilot-friendly syntax. Define chat workflows with triggers, actions, and type safety.

Readme

@candoa/workflows

Self-contained, type-safe workflow SDK for Candoa with Copilot-friendly syntax. Define chat workflows with triggers, actions, and full TypeScript support - no external dependencies required.

🚀 Features

  • Copilot-friendly syntax - Optimized for GitHub Copilot autocompletion
  • Type-safe - Full TypeScript support with parameter validation
  • Builder pattern - Fluent API with method chaining
  • Template variables - Dynamic value substitution with {{variableName}}
  • Pre-built actions - Common workflow actions ready to use
  • Self-contained - No external dependencies, all types included
  • Database integration - Seamless integration with Candoa CRM
  • Sync command - Deploy workflows to database with pnpm run sync-workflows

📦 Installation

npm install @candoa/workflows
# or
pnpm add @candoa/workflows

🔥 Quick Start

import { workflow, collectFirstName, saveToContact } from '@candoa/workflows'

export default workflow('welcome-new-user', {
  description: "Collects the user's first name when they start a chat",
})
  .on('chat.started')
  .use(
    collectFirstName({ prompt: "Hi! What's your first name?" }),
    saveToContact({ field: 'firstName', value: '{{firstName}}' }),
  )

🧠 Core Concepts

Workflow Definition

A workflow consists of:

  1. Name - Unique identifier for the workflow
  2. Description - Optional documentation
  3. Trigger - Event that starts the workflow
  4. Actions - Sequence of operations to execute

Template Variables

Use template variables to pass data between actions:

workflow('collect-contact-info')
  .on('chat.started')
  .use(
    collectFirstName({ prompt: "What's your first name?" }),
    collectEmail({ prompt: "What's your email address?" }),
    saveToContact({ field: 'firstName', value: '{{firstName}}' }),
    saveToContact({ field: 'email', value: '{{email}}' }),
    sendMessage({
      message: "Thanks {{firstName}}! I've saved your information.",
    }),
  )

🎯 Triggers

Available trigger types with full TypeScript autocompletion:

  • chat.started - When a new chat session begins
  • chat.ended - When a chat session ends
  • message.received - When user sends a message
  • intent.detected - When AI detects specific intent
  • sentiment.negative - When negative sentiment is detected
  • payment.failed - When a payment fails
  • user.signup - When a user signs up
  • custom.event - For custom events

⚡ Actions

Data Collection

// Collect user information
collectFirstName({ prompt: "What's your first name?" })
collectLastName({ prompt: "What's your last name?" })
collectEmail({ prompt: "What's your email address?" })

// Save to contact record
saveToContact({ field: 'firstName', value: '{{firstName}}' })
saveToContact({ field: 'email', value: '{{email}}' })

Communication

// Send messages
sendMessage({ message: 'Hello {{firstName}}!' })
sendMessage({ message: 'Please wait...', delay: 2000 })

// Send emails
sendEmail({
  to: '{{email}}',
  subject: 'Welcome to our platform!',
  body: 'Hi {{firstName}}, welcome aboard!',
})

Workflow Control

// Wait for a period
wait({ duration: 5, unit: 'seconds' })

// Conditional logic
conditional({
  condition: "{{sentiment}} == 'negative'",
  trueAction: handoffToHuman({ reason: 'Negative sentiment detected' }),
  falseAction: sendMessage({ message: 'How else can I help you?' }),
})

// Hand off to human agent
handoffToHuman({
  reason: 'Complex issue requiring human assistance',
})

Task Management

// Create tasks
createTask({
  title: 'Follow up with {{firstName}}',
  description: 'User expressed interest in premium features',
})

// Add tags
addTags({
  tags: ['new-user', 'interested'],
  target: 'contact',
})

🗄️ Database Integration

Workflow Storage

Workflows are stored in your workflows table:

CREATE TABLE "workflow" (
  "id" text PRIMARY KEY,
  "projectId" text NOT NULL REFERENCES "project"("id"),
  "name" text NOT NULL,
  "workflowData" json NOT NULL,  -- SDK data stored here
  "createdAt" timestamp DEFAULT now(),
  "updatedAt" timestamp DEFAULT now()
);

Manual Database Integration

import {
  prepareWorkflowForDatabase,
  validateWorkflowCompatibility,
} from '@candoa/workflows'

// 1. Define your workflow
const welcomeWorkflow = workflow('welcome-new-user', {
  description: "Collects the user's first name when they start a chat",
})
  .on('chat.started')
  .use(
    collectFirstName({ prompt: "Hi! What's your first name?" }),
    saveToContact({ field: 'firstName', value: '{{firstName}}' }),
  )

// 2. Validate workflow
const validation = validateWorkflowCompatibility(welcomeWorkflow)
if (!validation.isValid) {
  console.error('Validation errors:', validation.errors)
  return
}

// 3. Prepare for database
const dbData = prepareWorkflowForDatabase(welcomeWorkflow, projectId)

// 4. Insert into database
await db.insert(workflows).values({
  id: crypto.randomUUID(),
  name: dbData.name,
  projectId: dbData.projectId,
  workflowData: dbData.workflowData,
})

🔄 Workflow Sync System

Quick Sync

Create workflows in files and sync them to your database:

# Sync all workflows to your project
pnpm run sync-workflows --project-id=your-project-uuid

# Preview changes without applying them
pnpm run sync-workflows --project-id=your-project-uuid --dry-run

# Force overwrite existing workflows
pnpm run sync-workflows --project-id=your-project-uuid --force

Directory Structure

The sync script discovers workflows in these locations:

packages/workflows/
├── examples/               # 📁 Example workflows (use as templates)
│   ├── welcome.ts
│   ├── candoa-integration.ts
│   └── advanced-workflows.ts
└── scripts/
    └── sync-workflows.ts   # 🔧 Sync script

Workflow File Format

Export workflows from your files:

// workflows/my-workflows.ts
import { workflow, sendMessage, collectFirstName } from '@candoa/workflows'

// ✅ Default export
export default workflow('welcome', {
  description: 'Welcome new users',
})
  .on('chat.started')
  .use(
    sendMessage({ message: 'Welcome!' }),
    collectFirstName({ prompt: "What's your name?" }),
  )

// ✅ Named exports
export const supportFlow = workflow('support-flow', {
  description: 'Handle support requests',
})
  .on('intent.detected')
  .use(sendMessage({ message: 'I can help with that!' }))

Sync Output

🔄 Candoa Workflow Sync
========================
📁 Project ID: 0e8b8d5e-cade-4bf5-9e9f-c9e55a5d5ca0

📋 Found 1 workflow file:
   - /examples/welcome.ts

🔍 Processing: /examples/welcome.ts
   📝 Syncing workflow: new-user-onboarding
   ✅ Created: new-user-onboarding
   📝 Syncing workflow: frustration-escalation
   ✅ Created: frustration-escalation

📊 Sync Summary
===============
✅ Created: 2
🔄 Updated: 0
⏭️  Skipped: 1
❌ Errors: 0

🎉 Successfully synced 2 workflows!

🎨 Integration with Candoa Systems

Contact Management

Works with your existing contact schema:

const contactWorkflow = workflow('contact-creation', {
  description: 'Create comprehensive contact records',
})
  .on('chat.started')
  .use(
    collectFirstName({ prompt: "What's your first name?" }),
    collectEmail({ prompt: "What's your email?" }),
    saveToContact({ field: 'firstName', value: '{{firstName}}' }),
    saveToContact({ field: 'email', value: '{{email}}' }),
    addTags({ tags: ['new-contact'], target: 'contact' }),
  )

Automated Handoff Integration

Enhances your existing handoff system:

const smartHandoff = workflow('smart-handoff', {
  description: 'Intelligent handoff with data collection',
})
  .on('sentiment.negative')
  .use(
    sendMessage({
      message: 'Let me connect you with a human agent.',
    }),
    collectFirstName({ prompt: "What's your name?" }),
    saveToContact({ field: 'firstName', value: '{{firstName}}' }),
    handoffToHuman({
      reason: 'Negative sentiment with contact data',
    }),
  )

Execution Context

Build context from your database:

import { buildWorkflowContext } from '@candoa/workflows'

const context = buildWorkflowContext({
  conversationId: conversation.id,
  contactData: {
    id: contact.id,
    firstName: contact.name?.split(' ')[0],
    email: contact.email,
    phoneNumber: contact.phoneNumber,
  },
  messageHistory: messages.map((msg) => ({
    role: msg.role,
    content: msg.content,
    createdAt: msg.createdAt,
  })),
  triggerData: {
    sentiment: 'negative',
    intent: 'billing_question',
  },
})

📖 Advanced Examples

Customer Onboarding Flow

export const customerOnboarding = workflow('customer-onboarding', {
  description: 'Complete customer onboarding process',
})
  .on('user.signup')
  .use(
    sendMessage({
      message: "Welcome! Let's get you set up in just a few steps.",
    }),
    collectFirstName({ prompt: "First, what's your first name?" }),
    collectEmail({ prompt: "What's your email address?" }),
    saveToContact({ field: 'firstName', value: '{{firstName}}' }),
    saveToContact({ field: 'email', value: '{{email}}' }),
    sendMessage({ message: 'Perfect! Welcome aboard {{firstName}} 🎉' }),
    addTags({ tags: ['onboarded'], target: 'contact' }),
    createTask({
      title: 'Send welcome materials to {{firstName}}',
      assignee: 'onboarding-team',
    }),
  )

Support Escalation

export const supportEscalation = workflow('support-escalation', {
  description: 'Escalate negative sentiment to human agents',
})
  .on('sentiment.negative')
  .use(
    sendMessage({
      message:
        'I sense you might be frustrated. Let me get you connected with a human agent.',
    }),
    handoffToHuman({
      reason: 'Negative sentiment detected',
    }),
    createTask({
      title: 'Urgent: Frustrated customer needs assistance',
    }),
    addTags({ tags: ['escalated', 'urgent'], target: 'conversation' }),
  )

Payment Recovery

export const paymentRecovery = workflow('payment-recovery', {
  description: 'Handle failed payment with automated retry',
})
  .on('payment.failed')
  .use(
    sendMessage({
      message:
        'It looks like there was an issue with your payment. Let me help you resolve this.',
    }),
    wait({ duration: 1, unit: 'hours' }),
    sendEmail({
      to: '{{email}}',
      subject: 'Payment Issue - Action Required',
      body: 'Hi {{firstName}}, we had trouble processing your payment. Please update your payment method.',
    }),
    createTask({
      title: 'Follow up on failed payment for {{firstName}}',
      description: 'Payment failed, email sent, needs follow-up',
    }),
  )

📊 Analytics & Monitoring

Workflow Performance Tracking

import { extractWorkflowMetrics, updateWorkflowStats } from '@candoa/workflows'

// After workflow execution
const metrics = extractWorkflowMetrics(workflowDef, {
  success: true,
  executionTime: 1250, // milliseconds
  actionsCompleted: 5,
})

// Update workflow stats in database
const updatedWorkflowData = updateWorkflowStats(
  existingWorkflow.workflowData,
  true, // was successful
)

await db
  .update(workflows)
  .set({ workflowData: updatedWorkflowData })
  .where(eq(workflows.id, workflowId))

🛠️ Best Practices

Organization

  • Keep production workflows in workflows/ directory
  • Use examples/ for testing and documentation
  • Group related workflows in the same file
  • Use descriptive workflow names and descriptions

Naming Conventions

  • Use kebab-case for workflow names: new-user-onboarding
  • Include purpose in name: support-ticket-creation
  • Avoid conflicts with existing workflows

Development Workflow

  1. Develop workflows locally using the SDK
  2. Test with --dry-run flag first
  3. Sync to staging environment
  4. Validate in CRM interface
  5. Deploy to production

Version Control

  • Commit workflow files to your repository
  • Tag releases for workflow deployments
  • Document changes in workflow descriptions
  • Use branches for workflow development

🐛 Troubleshooting

Common Sync Issues

Project ID required:

# ❌ Missing project ID
pnpm run sync-workflows

# ✅ Provide project ID
pnpm run sync-workflows --project-id=your-uuid

Workflow already exists:

# ❌ Workflow exists, won't overwrite
pnpm run sync-workflows --project-id=your-uuid

# ✅ Use force flag to overwrite
pnpm run sync-workflows --project-id=your-uuid --force

Validation errors:

  • Check workflow structure matches SDK format
  • Ensure all required fields are present
  • Verify action parameters are valid
  • Review error details in sync output

Getting Help

  • Review workflow examples in examples/ directory
  • Check sync output for detailed error messages
  • Use --dry-run to preview changes without applying
  • Validate workflows before syncing

🎨 IDE Support

This SDK is optimized for modern IDEs and GitHub Copilot:

  • Auto-completion for trigger types, action parameters, and template variables
  • Type checking for all parameters and return types
  • IntelliSense documentation for all functions
  • Error highlighting for invalid configurations

📄 License

MIT License - see LICENSE file for details