@mieweb/q
v1.0.0
Published
React component library for generating and editing BluehIve IVR Agent configuration files
Keywords
Readme
Q — Agent Config Generator
Q is a powerful, embeddable UI component for generating and editing AI Agent configuration files. It provides a form-based interface, a YAML/JSON code editor, and an MCP tool builder — all in one package. Built with React and @mieweb/ui, Q can be dropped into any React application or used framework-agnostically in Vanilla JS, Vue, Angular, Svelte, or any other environment.
Table of Contents
- Features
- Prerequisites
- Installation
- Quick Start — React
- Quick Start — Universal (Vanilla JS / Any Framework)
- API Reference
- Exported Utilities
- Styling & CSS
- Theming & Brand Customization
- Custom Schemas
- Architecture & Flow Diagram
- Project Structure
- Development
- Troubleshooting
- License
Features
- Form Builder — Auto-generated form UI from a customizable JSON schema. Add, remove, and reorder fields visually with a built-in schema editor.
- Config Editor — Full-featured YAML/JSON editor powered by Monaco (the engine behind VS Code). Edit configs directly with syntax highlighting and validation.
- Tool Builder — Visual builder for defining MCP (Model Context Protocol) tools with parameter schemas, following the open standard.
- Bidirectional Sync — Changes in the form update the code editor and vice versa, always keeping the config in sync.
- Download & Submit — Download the generated config as YAML or JSON, or submit it programmatically via callbacks.
- Schema Import/Export — Import and export the form schema as JSON for portability and version control.
- Customizable Theming — Powered by
@mieweb/uiCSS variables. Supports multiple built-in brands (Bluehive, MIEWeb, Enterprise Health, Ozwell, Waggleline, WebChart) or fully custom themes. - Framework Agnostic — Use the React component directly or the Universal wrapper for any framework (Vue, Angular, Svelte, plain HTML).
Prerequisites
| Requirement | Version | Notes |
| --------------- | ---------------- | ------------------------------------------------------------ |
| Node.js | 18+ | LTS recommended. Download here. Check with node -v |
| npm | 9+ | Bundled with Node.js. Check with npm -v |
| Bundler | Vite 5+, Webpack 5+, or similar | Any modern bundler that supports ES modules |
| React (React integration only) | 18+ or 19+ | The Universal wrapper includes React internally |
Installation
Install Q directly from GitHub:
npm install Q@github:avagu-mie/QThis will:
- Clone the repository
- Run the
preparescript (builds the library with Vite and compiles Tailwind CSS) - Install the generated
dist/folder into yournode_modules/Q
Note: Because Q is installed from source (not a pre-built npm package), the
preparestep builds the library on your machine. This requires all of Q's dependencies to be fetched and may take a minute or two on the first install. Subsequent installs will be faster if your npm cache is warm.
Tip: Once published to npm, you can install with
npm install Qinstead.
Quick Start — React
This section walks through creating a brand-new React application that uses Q from scratch.
1. Scaffold the Application
# Create a new project folder
mkdir my-q-react-app && cd my-q-react-app
# Initialize package.json
npm init -y
# Make sure package.json has "type": "module"Edit your package.json so it looks like this:
{
"name": "my-q-react-app",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
}
}2. Install Dependencies
# Install Q from GitHub
npm install Q@github:avagu-mie/Q
# Install React (only if your app doesn't already have React)
npm install react react-dom
# Install Vite and the React plugin as dev dependencies
npm install -D vite @vitejs/plugin-reactNote: Q includes
reactandreact-domas direct dependencies, so you don't need to install them separately. If your host app already has React installed, npm will deduplicate automatically. If you see "Invalid hook call" errors at runtime, ensure you only have one copy of React — runnpm ls reactto check. You can add a resolution override if needed.
3. Create Project Files
vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
});index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Q App</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body, #root { height: 100%; }
</style>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>src/main.jsx
import React, { useState } from 'react';
import ReactDOM from 'react-dom/client';
import { AgentConfigGenerator } from 'Q';
import 'Q/style.css'; // Required — loads all component styles
function App() {
const [submittedYaml, setSubmittedYaml] = useState('');
return (
<div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
<AgentConfigGenerator
showEditor={true}
onConfigChange={(config) => {
console.log('Config changed:', config);
}}
onDownload={(content, mode) => {
// content = serialized config string, mode = 'yaml' or 'json'
console.log(`Download (${mode}):`, content);
}}
onSubmit={(yamlString) => {
console.log('Submitted YAML:', yamlString);
setSubmittedYaml(yamlString);
}}
/>
{submittedYaml && (
<pre style={{
margin: '1rem',
padding: '1rem',
background: '#f5f5f5',
borderRadius: '8px',
overflow: 'auto',
maxHeight: '300px',
fontSize: '0.85rem',
}}>
{submittedYaml}
</pre>
)}
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);4. Run the Application
npm run devOpen the URL shown in your terminal (usually http://localhost:5173).
Quick Start — Universal (Vanilla JS / Any Framework)
The Universal wrapper lets you use Q without writing any React code. It works with plain HTML, Vue, Angular, Svelte, or any framework. React is bundled internally.
1. Scaffold the Application
mkdir my-q-universal-app && cd my-q-universal-app
npm init -yEdit your package.json:
{
"name": "my-q-universal-app",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
}
}2. Install Dependencies
# Install Q from GitHub (React is included as a dependency of Q)
npm install Q@github:avagu-mie/Q
# Install Vite as a dev dependency — no React plugin needed!
npm install -D vite3. Create Project Files
vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({});index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Q App</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
#config-container { height: 100%; }
</style>
</head>
<body>
<div id="config-container"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>src/main.js
import { UniversalAgentConfigGenerator } from 'Q/universal';
import 'Q/style.css'; // Required — loads all component styles
// Mount Q into the container element
const generator = new UniversalAgentConfigGenerator('#config-container', {
showEditor: true,
});
const container = document.getElementById('config-container');
// Listen for config changes
container.addEventListener('config-change', (e) => {
console.log('Config changed:', e.detail);
});
// Listen for download requests
container.addEventListener('config-download', (e) => {
console.log('Download requested:', e.detail);
});
// Listen for submit
container.addEventListener('config-submit', (e) => {
console.log('Submit clicked:', e.detail);
// e.detail.yaml — the config as a YAML string
// e.detail.config — the config as a JS object
});4. Run the Application
npm run devUniversal Wrapper in Vue
<template>
<div ref="container" style="height: 100%"></div>
</template>
<script>
import { UniversalAgentConfigGenerator } from 'Q/universal';
import 'Q/style.css';
export default {
mounted() {
this.generator = new UniversalAgentConfigGenerator(this.$refs.container, {
showEditor: true,
});
this.$refs.container.addEventListener('config-submit', (e) => {
this.$emit('submit', e.detail);
});
},
beforeUnmount() {
this.generator.destroy();
},
};
</script>Universal Wrapper in Angular
import { Component, ElementRef, ViewChild, AfterViewInit, OnDestroy } from '@angular/core';
import { UniversalAgentConfigGenerator } from 'Q/universal';
import 'Q/style.css';
@Component({
selector: 'app-config-generator',
template: `<div #container style="height: 100%"></div>`,
})
export class ConfigGeneratorComponent implements AfterViewInit, OnDestroy {
@ViewChild('container') container!: ElementRef;
private generator!: UniversalAgentConfigGenerator;
ngAfterViewInit() {
this.generator = new UniversalAgentConfigGenerator(this.container.nativeElement, {
showEditor: true,
});
this.container.nativeElement.addEventListener('config-submit', (e: CustomEvent) => {
console.log('Submitted:', e.detail);
});
}
ngOnDestroy() {
this.generator.destroy();
}
}API Reference
React — AgentConfigGenerator
import { AgentConfigGenerator } from 'Q';| Prop | Type | Default | Description |
| ---------------- | ------------------------------ | ---------------- | --------------------------------------------------------------------------- |
| initialConfig | object | auto-derived | Pre-populate with an existing agent config object |
| schema | object | built-in schema | Override the default questionnaire schema (MieForms v1.0 format) |
| onConfigChange | (config: object) => void | — | Callback fired on every config change |
| onDownload | (content: string, mode: 'yaml' \| 'json') => void | — | Callback when the user clicks Download. Receives the serialized config and the format. |
| onSubmit | (yamlString: string) => void | — | Callback when the user clicks Submit. Receives the config as a YAML string. Also shows the Submit button. |
| showEditor | boolean | true | Show/hide the Config Editor tab |
Universal — UniversalAgentConfigGenerator
import { UniversalAgentConfigGenerator } from 'Q/universal';Constructor
new UniversalAgentConfigGenerator(target, options?)| Parameter | Type | Description |
| ------------------ | ----------------------- | ----------------------------------------------------------- |
| target | string \| HTMLElement | CSS selector or DOM element to mount into |
| options.initialConfig | object | Pre-populate with an existing agent config |
| options.schema | object | Override the default questionnaire schema |
| options.showEditor | boolean | Show the Config Editor tab (default: true) |
Methods
| Method | Description |
| ------------------- | ---------------------------------------- |
| update(options) | Merge new options and re-render |
| destroy() | Unmount the component and free resources |
DOM Events
Events are dispatched on the container element. Use addEventListener to listen:
| Event Name | e.detail | Description |
| ----------------- | -------------------------------------- | ------------------------------------------------ |
| config-change | object (the config) | Fired on every config change |
| config-download | { content: string, mode: 'yaml' \| 'json' } | Fired when the user clicks Download |
| config-submit | { config: object, yaml: string } | Fired when the user clicks Submit |
Exported Utilities
In addition to the main components, Q exports several utilities:
import {
// Components
AgentConfigGenerator,
FormBuilder,
ConfigEditor,
ToolBuilder,
Header,
// Schema / Config helpers
defaultSchema, // The built-in questionnaire JSON schema
deriveDefaultConfig, // (schema) => config object with defaults
questionnaireToConfig, // Convert schema answers to a config object
configToQuestionnaire, // Sync a config object back into a schema
validateConfig, // Validate a config object
configToYaml, // Convert config to YAML string
yamlToConfig, // Parse YAML string to config object
downloadConfig, // Trigger a file download
copyToClipboard, // Copy text to clipboard
// Constants
TIMEZONES, // Array of timezone strings
GENERIC_TOOLS, // Built-in MCP tool definitions
} from 'Q';The Universal entry also re-exports branding helpers:
import { brands, generateBrandCSS } from 'Q/universal';Styling & CSS
Q uses Tailwind CSS internally and ships a pre-built CSS file. You must import the stylesheet in your application for components to render correctly:
import 'Q/style.css';This single import includes:
- All Tailwind utility classes used by Q components
- All
@mieweb/uicomponent styles - Custom animations and hover-state overrides
Full-Height Layout
Q is designed to fill its parent container. Make sure the mount target has a defined height:
html, body, #root { height: 100%; }Using Q alongside your own Tailwind setup
If your host app also uses Tailwind, you can extend your own config with Q's preset:
// your-app/tailwind.config.js
import miewebPreset from 'Q/tailwind-config';
export default {
presets: [miewebPreset],
content: [
'./src/**/*.{js,jsx,ts,tsx}',
'./node_modules/Q/dist/**/*.{js,cjs}', // Scan Q's built output
],
};Theming & Brand Customization
Q is styled entirely through CSS custom properties (variables) defined by @mieweb/ui. You can re-theme the entire component by setting these variables on a parent element.
Built-in Brands
@mieweb/ui ships with 6 built-in brand themes. Import the desired brand CSS before Q's styles:
| Brand | Import |
| ------------------ | ------------------------------------------ |
| Bluehive (default) | @mieweb/ui/brands/bluehive.css |
| Enterprise Health | @mieweb/ui/brands/enterprise-health.css |
| MIEWeb | @mieweb/ui/brands/mieweb.css |
| Ozwell | @mieweb/ui/brands/ozwell.css |
| WebChart | @mieweb/ui/brands/webchart.css |
| Waggleline | @mieweb/ui/brands/waggleline.css |
Example (React):
import '@mieweb/ui/brands/enterprise-health.css';
import 'Q/style.css';
import { AgentConfigGenerator } from 'Q';Custom Theme via CSS Variables
Create your own theme by setting the CSS variables on a parent element or :root:
:root {
/* Colors */
--mieweb-background: #ffffff;
--mieweb-foreground: #0a0a0a;
--mieweb-card: #ffffff;
--mieweb-card-foreground: #0a0a0a;
--mieweb-primary: #2563eb;
--mieweb-primary-foreground: #ffffff;
--mieweb-secondary: #f5f5f5;
--mieweb-secondary-foreground: #171717;
--mieweb-muted: #f5f5f5;
--mieweb-muted-foreground: #737373;
--mieweb-accent: #f5f5f5;
--mieweb-accent-foreground: #171717;
--mieweb-destructive: #ef4444;
--mieweb-destructive-foreground: #ffffff;
--mieweb-border: #e5e5e5;
--mieweb-input: #e5e5e5;
--mieweb-ring: #2563eb;
/* Typography */
--mieweb-font-sans: 'Inter', system-ui, sans-serif;
/* Border Radius */
--mieweb-radius-sm: 0.375rem;
--mieweb-radius-md: 0.5rem;
--mieweb-radius-lg: 0.75rem;
}Scoped Theming
Apply different themes to different instances on the same page by scoping CSS variables to a wrapper element:
<div class="theme-dark" style="
--mieweb-background: #1a1a1a;
--mieweb-foreground: #fafafa;
--mieweb-card: #262626;
--mieweb-border: #404040;
">
<div id="config-container"></div>
</div>Custom Schemas
Q uses a JSON schema format (MieForms v1.0) to define the form fields. You can pass a custom schema to control which fields appear in the Form Builder.
Schema Structure
{
"schemaType": "mieforms-v1.0",
"title": "My Custom Form",
"fields": [
{
"id": "section-1",
"fieldType": "section",
"title": "General Settings",
"fields": [
{
"id": "name",
"fieldType": "text",
"question": "Agent Name",
"answer": "My Agent",
"required": true
},
{
"id": "provider",
"fieldType": "dropdown",
"question": "LLM Provider",
"answer": "openai",
"options": ["openai", "anthropic", "google"]
},
{
"id": "enableLogging",
"fieldType": "check",
"question": "Enable Logging",
"answer": false
},
{
"id": "systemPrompt",
"fieldType": "longtext",
"question": "System Prompt",
"answer": ""
}
]
}
]
}Supported Field Types
| Type | Description |
| ---------- | ----------------------------------------------- |
| text | Single-line text input |
| longtext | Multi-line textarea |
| dropdown | Select dropdown (requires options array) |
| check | Boolean checkbox |
| section | Grouping container (contains nested fields) |
Using Nested Keys
Use dot-notation in the field id to map to nested config keys:
{
"id": "twilio.phoneNumber",
"fieldType": "text",
"question": "Twilio Phone Number"
}This produces:
twilio:
phoneNumber: "+1234567890"Passing a Custom Schema
import customSchema from './my-schema.json';
<AgentConfigGenerator
schema={customSchema}
onSubmit={(yaml) => console.log(yaml)}
/>Architecture & Flow Diagram
flowchart TB
subgraph Host["Host Application"]
direction TB
R["React App"]
U["Universal Wrapper<br/>(Vanilla JS / Vue / Angular)"]
end
subgraph Q["Q Package"]
direction TB
subgraph Entry["Entry Points"]
RE["Q (React)<br/>import from 'Q'"]
UE["Q/universal<br/>import from 'Q/universal'"]
end
subgraph Core["Core Components"]
ACG["AgentConfigGenerator<br/>(orchestrator)"]
FB["FormBuilder<br/>(questionnaire UI)"]
CE["ConfigEditor<br/>(Monaco YAML/JSON)"]
TB["ToolBuilder<br/>(MCP tool editor)"]
end
subgraph Data["Data Layer"]
CFG["Config State<br/>(React useState)"]
SCH["Schema State<br/>(MieForms v1.0)"]
YAML["YAML/JSON<br/>Output Sync"]
end
subgraph Style["Styling"]
CSS["Q/style.css<br/>(Tailwind bundle)"]
VAR["CSS Variables<br/>(--mieweb-*)"]
BRAND["Brand Themes<br/>(@mieweb/ui)"]
end
end
R -->|"import { AgentConfigGenerator }"| RE
U -->|"new UniversalAgentConfigGenerator()"| UE
UE -->|"wraps with createRoot"| ACG
RE --> ACG
ACG --> FB
ACG --> CE
ACG --> TB
FB -->|"form answers"| CFG
CE -->|"editor changes"| CFG
TB -->|"tool definitions"| CFG
CFG -->|"sync"| YAML
CFG <-->|"bidirectional"| SCH
BRAND --> VAR
VAR --> CSS
ACG -->|"onConfigChange"| Host
ACG -->|"onSubmit (YAML)"| Host
ACG -->|"onDownload"| HostData Flow
sequenceDiagram
participant User
participant FormBuilder
participant ConfigState
participant ConfigEditor
participant HostApp
User->>FormBuilder: Fill out form fields
FormBuilder->>ConfigState: Update config object
ConfigState->>ConfigEditor: Sync YAML/JSON strings
ConfigState->>HostApp: onConfigChange(config)
User->>ConfigEditor: Edit YAML/JSON directly
ConfigEditor->>ConfigState: Parse & update config
ConfigState->>FormBuilder: Re-render with new values
User->>HostApp: Click Submit
HostApp->>ConfigState: Read yamlOutput
ConfigState-->>HostApp: onSubmit(yamlString)Component Tabs
stateDiagram-v2
[*] --> FormBuilder
FormBuilder --> ConfigEditor: Switch tab
FormBuilder --> ToolBuilder: Switch tab
ConfigEditor --> FormBuilder: Switch tab
ConfigEditor --> ToolBuilder: Switch tab
ToolBuilder --> FormBuilder: Switch tab
ToolBuilder --> ConfigEditor: Switch tab
FormBuilder --> SchemaEditor: Click "Customize Form"
SchemaEditor --> FormBuilder: Click "Back to Form"
state FormBuilder {
[*] --> RenderForm
RenderForm: Questionnaire UI
}
state ConfigEditor {
[*] --> YAML
YAML --> JSON: Toggle
JSON --> YAML: Toggle
}
state ToolBuilder {
[*] --> ToolList
ToolList: MCP Tool Definitions
}Project Structure
Q/
├── dist/ # Built output (generated)
│ ├── index.js / index.cjs # React component library
│ ├── universal.js / .cjs # Framework-agnostic wrapper
│ └── style.css # Pre-built Tailwind CSS
├── src/
│ ├── index.js # React entry — public exports
│ ├── universal.jsx # Universal wrapper class
│ ├── index.css # Tailwind directives + custom styles
│ ├── components/
│ │ ├── AgentConfigGenerator.jsx # Main orchestrator component
│ │ ├── FormBuilder.jsx # Questionnaire form renderer
│ │ ├── ConfigEditor.jsx # Monaco-based YAML/JSON editor
│ │ └── ToolBuilder.jsx # MCP tool definition editor
│ ├── schemas/
│ │ └── agent-config-questionnaire.json # Default form schema
│ ├── constants/
│ │ └── defaults.js # Timezones, generic tools
│ └── utils/
│ ├── configHelpers.js # YAML/JSON conversion, download, clipboard
│ └── transformers.js # Schema ↔ config transformations
├── package.json
├── vite.config.js # Dev server config
├── vite.lib.config.js # Library build config
├── tailwind.config.js # Tailwind CSS config with @mieweb/ui preset
├── postcss.config.js # PostCSS config (Tailwind + Autoprefixer)
└── .npmrc # npm config (legacy-peer-deps)Development
Running the Dev Server
# Clone the repo
git clone https://github.com/avagu-mie/Q.git && cd Q
# Install dependencies
npm install
# Start the dev server with both React and Universal demo pages
npm run dev # Opens index.html (default demo)
npm run dev:react # Opens the React-specific demo page
npm run dev:universal # Opens the Universal demo pageBuilding the Library
# Build the distributable library files
npm run build:lib
# Full prepare (build + CSS) — runs automatically on npm install from GitHub
npm run prepareKey Scripts
| Script | Description |
| ----------------- | ------------------------------------------------------------- |
| npm run dev | Start Vite dev server with HMR |
| npm run build | Production build of the demo app |
| npm run build:lib | Build the library (dist/index.js, dist/universal.js) |
| npm run prepare | Full library build + Tailwind CSS compilation |
| npm run storybook | Start Storybook for component development |
Troubleshooting
npm install fails with peer dependency errors
Q uses legacy-peer-deps=true in its .npmrc to handle peer dependency conflicts between Storybook (which expects React 18) and the library (which supports React 19). If you encounter peer dep issues in your host app, create an .npmrc in your project root:
legacy-peer-deps=trueStyles not loading / unstyled components
Make sure you import the CSS file:
import 'Q/style.css';This import is required. Without it, components will render without any styling.
Component doesn't fill the page
Q renders to fill its parent container. Ensure the parent element has an explicit height:
html, body, #root { height: 100%; }Monaco Editor not loading
The Monaco editor (used in the Config Editor tab) is loaded asynchronously. Ensure your bundler supports dynamic imports (all modern bundlers do). If using a CSP (Content Security Policy), make sure worker-src allows blob URLs.
Build errors with ERR_PACKAGE_PATH_NOT_EXPORTED
If you see export map errors, check that you're importing from the correct path:
// ✅ Correct
import { AgentConfigGenerator } from 'Q';
import { UniversalAgentConfigGenerator } from 'Q/universal';
import 'Q/style.css';
// ❌ Wrong — don't import from dist/ directly
import { AgentConfigGenerator } from 'Q/dist/index.js';Sample Output
Here's an example of the YAML config that Q generates with the default schema:
_id: ""
name: New Agent Config
timezone: America/New_York
description: Agent Assistant Description
twilio:
phoneNumber: ""
accountSid: ""
authToken: ""
officeOpeningTime: 8
officeClosingTime: 17
systemPrompt: ""
tools: []Each field in the form maps directly to a key in this output. Nested keys (like twilio.phoneNumber) are expanded into nested YAML objects. The Tool Builder tab adds entries to the tools array using the MCP inputSchema format.
