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

salesive-dev-tools

v0.3.0

Published

Salesive development tools — Vite plugin, React hooks, and the App Bridge for embedded Salesive apps

Readme

Salesive Development Tools

A powerful CLI tool and development toolkit for building, managing, and deploying Salesive templates.

Features

  • 🎨 Template Management: Create, validate, and deploy Salesive templates
  • 🔐 API Authentication: Secure authentication with Salesive Themes API
  • Configuration Validation: Validate your template configuration files
  • 🚀 One-Command Deploy: Package and deploy templates with cook
  • 🔄 Auto-Update Check: Automatically notifies you of new versions
  • 🔧 Vite Plugin: Inject configuration into your React applications
  • 🪝 React Hooks: Easy access to configuration data in components
  • 🔑 App Bridge: Runtime permission requests and lifecycle events for embedded Salesive apps

Installation

Global Installation (Recommended for CLI)

npm install -g salesive-dev-tools
# or
yarn global add salesive-dev-tools
# or
pnpm add -g salesive-dev-tools
# or
bun add -g salesive-dev-tools

Local Installation (For Vite Plugin)

npm install salesive-dev-tools
# or
yarn add salesive-dev-tools
# or
pnpm add salesive-dev-tools

Usage

Setup in Vite Configuration

// vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { salesiveConfigPlugin } from "salesive-dev-tools";

export default defineConfig({
    plugins: [react(), salesiveConfigPlugin()],
});

Create a Salesive Config File

Create a salesive.config.json file in the root of your project:

{
    "name": "my-project",
    "version": "1.0.0",
    "description": "My Project Description",
    "variables": {
        "color-brand-primary": "#0d65d9",
        "color-brand-primary-x": "#e6f6ff",
        "font-brand-space": "Space Grotesk",
        "app-name": "My App",
        "app-description": "My awesome application",
        "app-logo": "https://example.com/logo.png",
        "app-favicon": "https://example.com/favicon.ico"
    }
}

Automatic Routes and routeOverrides

Some paths are automatic routes — Salesive serves them itself, so the dev server hands them to the platform app instead of your theme:

/cart  /checkout  /account  /orders  /payment  /invoice  /settings
/login  /logout  /privacy-policy  /terms  /about-us  /return-policy

A theme can claim one of these and render it itself by listing it in routeOverrides. The dev server honours this, so what you see locally matches what the platform serves after salesive cook:

{
    "name": "my-theme",
    "version": "1.0.0",
    "routeOverrides": ["/blog", "/orders"]
}

An override owns the whole subtree — declaring /orders also gives you /orders/:id. Only automatic routes can be overridden; any other value is ignored. Once you claim a route, that path no longer falls back to Salesive's built-in page, so you must implement it (and its data fetching) in your own router.

Using the React Hook

import { useSalesiveConfig } from "salesive-dev-tools";

function MyComponent() {
    // Get a specific variable value
    const appName = useSalesiveConfig("app-name");
    const brandColor = useSalesiveConfig("color-brand-primary");

    // Or get all variables
    const allVariables = useSalesiveConfig();

    return (
        <div style={{ color: brandColor }}>
            <h1>{appName}</h1>
        </div>
    );
}

Dynamic Configuration Updates

You can update the Salesive configuration at runtime and all components using the configuration will automatically update:

import { updateSalesiveConfig, setSalesiveConfig } from "salesive-dev-tools";

// Update a specific variable
setSalesiveConfig("app-name", "My Updated App Name");

// Update multiple variables at once (merges with existing config)
updateSalesiveConfig({
    variables: {
        "color-brand-primary": "#ff0000",
        "app-description": "Updated description",
    },
});

// Use a function to update based on previous state
updateSalesiveConfig((prevConfig) => ({
    ...prevConfig,
    variables: {
        ...prevConfig.variables,
        "app-name": `${prevConfig.variables["app-name"]} - Updated`,
    },
}));

// Replace the entire config (no merging)
updateSalesiveConfig(newConfig, { merge: false });

// Advanced usage with options
updateSalesiveConfig(newConfig, {
    merge: true, // Whether to merge with existing config (default: true)
    notify: true, // Whether to notify components to update (default: true)
});

Integration with React Router

You can use dynamic configuration with React Router to create route-specific themes:

import { Outlet, useLocation } from "react-router-dom";
import { setSalesiveConfig } from "salesive-dev-tools";

// Route-specific theme switcher
function ThemeManager() {
    const location = useLocation();

    useEffect(() => {
        // Update theme based on current route
        if (location.pathname.startsWith("/admin")) {
            setSalesiveConfig("color-brand-primary", "#2d3748");
        } else if (location.pathname.startsWith("/marketing")) {
            setSalesiveConfig("color-brand-primary", "#e53e3e");
        } else {
            setSalesiveConfig("color-brand-primary", "#0d65d9");
        }
    }, [location.pathname]);

    return <Outlet />;
}

Using the Helper Function (for non-component code)

import { getSalesiveConfig } from "salesive-dev-tools";

// Get a specific variable value
const appName = getSalesiveConfig("app-name");

// Or get all variables
const allVariables = getSalesiveConfig();

What Gets Injected?

The plugin performs several injections into your HTML:

  1. Page Title: From variables.app-name
  2. Meta Description: From variables.app-description
  3. Favicon: From variables.app-favicon
  4. CSS Variables: All entries in variables are injected as CSS custom properties
  5. Global JavaScript Object: The entire config is available at window.SALESIVE_CONFIG

Integration with React Router

For projects using React Router, you can combine this plugin with a routing setup. This works well with the nested route structure pattern where components are organized into a layout hierarchy.

Example routing structure:

// router.jsx
import { createBrowserRouter } from "react-router-dom";
import App from "./App";
import HomePage from "./pages/Home";

const router = createBrowserRouter([
    {
        path: "/",
        element: <App />,
        children: [
            { index: true, element: <HomePage /> },
            // ...other routes
        ],
    },
]);

export default router;

Options

The salesiveConfigPlugin accepts the following options:

salesiveConfigPlugin({
    // Custom path to your config file (default: 'salesive.config.json' in project root)
    configPath: "./path/to/config.json",
});

Development Mode Only

The plugin only runs during development (vite dev) and is automatically disabled for production builds to keep your production code clean.

CLI Commands

Quick Start

# 1. Set up authentication
salesive auth set-token

# 2. Initialize a new template (or use existing)
salesive init

# 3. Validate your configuration
salesive validate

# 4. Deploy your template
salesive cook

Authentication

Manage your Salesive API credentials for deploying templates.

# Set your Salesive API token
salesive auth set-token

# Check authentication status
salesive auth status

# Verify API key with server
salesive auth verify

# Clear your token if needed
salesive auth clear-token

Creating New Projects

Two starters, two commands: init scaffolds a storefront theme (React + Vite, deployed with salesive cook); create-app clones the embedded app starter (React + Express on one port — OAuth 2.1 + PKCE, signed session, socket.io, a notes CRUD proxy).

Storefront theme (salesive init):

# Initialize a new Salesive theme with interactive prompts
# (Select Build Tool, Framework, and Package Manager)
salesive init

# Initialize in the current directory (scaffolding)
salesive init --current

# Create a project with specific options (skips prompts)
salesive init --name my-project --build-tool vite --framework react

# Force creation even if directory exists
salesive init --name existing-dir --force

Embedded app (salesive create-app):

# Clone the app starter with interactive prompts (App name, Package Manager)
salesive create-app

# Skip prompts
salesive create-app --name my-app --package-manager npm

# Set up in the current directory (won't overwrite existing files)
salesive create-app --current

# Force creation even if the directory exists
salesive create-app --name existing-dir --force

create-app clones the starter, seeds .env from .env.example, and prints the next steps. Fill in SALESIVE_CLIENT_ID / SALESIVE_CLIENT_SECRET / SALESIVE_WEBHOOK_SECRET (Dashboard → Apps → Developer), then npm run dev. Optionally set MONGODB_URI to persist installs.

Development

# Start development server with config file watching
salesive dev

# Specify a custom project path
salesive dev --path ./my-project

# Specify a custom config file
salesive dev --config ./custom-config.json

The development server provides:

  • Hot Reloading: Automatically restarts when configuration changes
  • Beautiful Logs: Clean, colored output
  • Keyboard Shortcuts:
    • [r]: Restart the server
    • [q]: Quit the CLI
    • Ctrl+C: Exit gracefully

Template Deployment (Cook)

The cook command packages and deploys your Salesive template to the themes API. It automatically runs your project build script (e.g., npm run build) before packaging.

# Deploy template from current directory (builds first)
salesive cook

# Skip the automatic build step
salesive cook --no-build

# Deploy from a specific path
salesive cook --path ./my-template

# Use a custom config file
salesive cook --config ./custom-config.json

# Keep temporary files for debugging
salesive cook --keep-temp

# Show detailed error information
salesive cook --verbose

What gets deployed:

  • All template files (HTML, CSS, JS, images, etc.)
  • salesive.config.json (required)
  • salesive.form.json (optional, but recommended)

Automatic exclusions:

  • node_modules/
  • .git/
  • .salesive-temp/
  • *.log
  • .DS_Store

Generate Form Configuration:

If you don't have a salesive.form.json, generate one at: https://template-form.salesive.com

Configuration Validation

Validate your salesive.config.json before deployment:

# Validate current project
salesive validate

# Validate specific project
salesive validate --path ./my-project

# Validate specific config file
salesive validate --config ./custom-config.json

# Show verbose output
salesive validate --verbose

Validation checks:

  • Required fields: name, version, description
  • Name format: lowercase, no spaces (use hyphens)
  • Version format: semantic versioning (x.x.x)
  • Variables structure

Configuration File Format

Your salesive.config.json must follow this structure:

{
    "name": "my-template",
    "version": "1.0.0",
    "description": "My awesome template",
    "author": "Your Name",
    "license": "MIT",
    "website": "https://example.com",
    "icon": "https://example.com/icon.png",
    "email": "[email protected]",
    "variables": {
        "primary-color": "#0d65d9",
        "secondary-color": "#e6f6ff",
        "font-family": "Space Grotesk"
    }
}

Available Options

Cook Options

| Option | Description | | ------------------------ | ------------------------------------------------------- | | -p, --path <path> | Path to template directory (default: current directory) | | -c, --config <path> | Path to salesive.config.json file | | --no-build | Skip the automatic build step | | -b, --build-dir <path> | Alias for --out-dir to specify build directory | | -k, --keep-temp | Keep temporary files after cooking | | -v, --verbose | Show verbose output and detailed error information |

Auth Options

| Option | Description | | --------------- | ---------------------------------------------------- | | -v, --verbose | Show detailed error information (for verify command) |

Init Options

| Option | Description | | --------------------------------- | -------------------------------------- | | -n, --name <name> | Project name | | -c, --current | Initialize in current directory | | -b, --build-tool <tool> | Build tool (vite) | | --framework <framework> | Framework (react) | | -p, --package-manager <manager> | Package manager (npm, yarn, bun, pnpm) | | -f, --force | Overwrite existing directory | | -v, --verbose | Show verbose output |

Validate Options

| Option | Description | | --------------------- | --------------------------------- | | -p, --path <path> | Path to project directory | | -c, --config <path> | Path to salesive.config.json file | | -v, --verbose | Show verbose output |

Automatic Updates

The CLI automatically checks for updates once every 24 hours and notifies you when a new version is available.

┌──────────────────────────────────────────────────────────┐
│  Update available!  0.0.1 → 1.0.0                        │
│  Run npm install -g salesive-dev-tools to update         │
└──────────────────────────────────────────────────────────┘

Error Handling

The CLI provides detailed error messages with actionable suggestions:

✖ API key verification failed

✗ Error 401: Unauthorized
  Invalid API key format

💡 Suggestion:
  • Invalid or expired API key
  • Run salesive auth set-token to update your API key
  • Ensure the API key starts with sk-

Use the --verbose flag with any command to see detailed error information.

Troubleshooting

Common Issues

Authentication Errors:

# Verify your API key is valid
salesive auth verify

# Set a new API key if needed
salesive auth set-token

Configuration Errors:

# Validate your config file
salesive validate

# Check for common issues:
# - Name must be lowercase with no spaces
# - Version must follow x.x.x format
# - All required fields must be present

App Bridge — Permissions & Lifecycle

Embedded Salesive apps run inside an iframe. The App Bridge lets your app communicate with the merchant's dashboard to request runtime permissions and react to open/close events.

Installation

npm install salesive-dev-tools

Import

// Subpath import (recommended — tree-shakes everything else out):
import {
  PERMISSIONS,
  DURATIONS,
  APP_EVENTS,
  requestPermission,
  getGrantedPermissions,
  onLaunch,
  onAppWake,
  onAppOpened,
  onAppMinimized,
  onAppClosed,
  onLogout,
  onStoreChanged,
  onEvent,
} from "salesive-dev-tools/permissions";

// Or from the main package:
import { requestPermission, getGrantedPermissions } from "salesive-dev-tools";

TypeScript types are included — no separate @types/ package needed.


Available permissions

| Constant | String value | What it enables | |---|---|---| | PERMISSIONS.AUTO_LAUNCH | "AUTO_LAUNCH" | App can call onLaunch() to un-minimize itself | | PERMISSIONS.WRITE_DOMAINS | "WRITE_DOMAINS" | App can add / remove custom domains | | PERMISSIONS.CLIPBOARD_READ | "CLIPBOARD_READ" | App can read from the system clipboard | | PERMISSIONS.WRITE_ORDERS_CANCEL | "WRITE_ORDERS_CANCEL" | App can cancel orders on the merchant's behalf |


requestPermission(permission, options?, timeoutMs?)

Shows a merchant-facing approval modal in the dashboard. The merchant picks a duration and clicks Allow or Deny. Returns a promise that resolves to true (approved) or false (denied or timed out).

function requestPermission(
  permission : Permission,
  options?   : DurationOption[],  // duration choices shown in the modal; omit = all five
  timeoutMs? : number,            // default 30 000 ms
): Promise<boolean>

options — which duration choices to offer. The first element is selected by default.

| Constant | String value | Lasts | |---|---|---| | DURATIONS.ONCE | "once" | One API call, then consumed | | DURATIONS.ONE_DAY | "1day" | 1 day | | DURATIONS.SEVEN_DAYS | "7days" | 7 days | | DURATIONS.FOURTEEN_DAYS | "14days" | 14 days | | DURATIONS.THIRTY_ONE_DAYS | "31days" | 31 days |

// Request AUTO_LAUNCH — offer 7-day and 31-day choices:
const granted = await requestPermission(
  PERMISSIONS.AUTO_LAUNCH,
  [DURATIONS.SEVEN_DAYS, DURATIONS.THIRTY_ONE_DAYS]
);

if (granted) {
  console.log("Permission granted!");
}

The dashboard modal has a 10-second auto-deny countdown. Keep timeoutMs above 10 000 ms (the default 30 000 ms is fine).


getGrantedPermissions(timeoutMs?)

Silently query which permissions are currently active — no modal is shown.

function getGrantedPermissions(timeoutMs?: number): Promise<PermissionGrantMap>

Returns a PermissionGrantMap — a partial map of Permission → GrantRecord:

interface GrantRecord {
  mode      : DurationOption;
  grantedAt : string;          // ISO-8601
  expiresAt : string | null;   // null for "once" grants
  usedAt    : string | null;   // set after a "once" grant is consumed
}
// Check on mount before requesting:
const grants = await getGrantedPermissions();

if (PERMISSIONS.AUTO_LAUNCH in grants) {
  console.log("AUTO_LAUNCH already granted until", grants.AUTO_LAUNCH.expiresAt);
} else {
  const ok = await requestPermission(PERMISSIONS.AUTO_LAUNCH);
}

onLaunch()

Ask the dashboard to expand (un-minimize) this app. The dashboard silently ignores the call if AUTO_LAUNCH hasn't been granted.

onLaunch(); // fire-and-forget

onAppWake(callback)

Fires when the dashboard first loads your app in the background — before the merchant has ever opened it. The Salesive dashboard pre-loads every installed app at startup in a hidden, full-size iframe so apps warm up instantly. This is the earliest point your JavaScript executes.

function onAppWake(callback: () => void): () => void  // returns unsubscribe
import { onAppWake, getGrantedPermissions, PERMISSIONS } from "salesive-dev-tools/permissions";

onAppWake(async () => {
  // Called as soon as the dashboard loads; merchant hasn't opened the app yet
  connectWebSocket();
  prefetchCriticalData();

  // Check grants silently on wake
  const grants = await getGrantedPermissions();
  if (PERMISSIONS.AUTO_LAUNCH in grants) scheduleAutoLaunchTimer();
});

onAppOpened(callback) / onAppMinimized(callback) / onAppClosed(callback)

Subscribe to foreground lifecycle events. All three return an unsubscribe function.

function onAppOpened(callback: () => void): () => void
function onAppMinimized(callback: () => void): () => void
function onAppClosed(callback: () => void): () => void

| Event | When it fires | |---|---| | onAppOpened | App expands to full view (first open or restored from minimized) | | onAppMinimized | Merchant presses − (minimize); app is hidden but still running | | onAppClosed | Merchant presses × (close/dismiss); app keeps running in the background |

// Refresh data when the merchant opens the app:
const unsubOpen = onAppOpened(() => refreshDashboard());

// Save unsaved state when dismissed:
const unsubClose = onAppClosed(() => saveDraft());

// Auto-reopen 30 seconds after minimize:
const unsubMin = onAppMinimized(() => {
  const timer = setTimeout(onLaunch, 30_000);
  const unsubNext = onAppOpened(() => { clearTimeout(timer); unsubNext(); });
});

// Clean up in React:
useEffect(() => {
  const unOpen = onAppOpened(() => { /* ... */ });
  const unMin  = onAppMinimized(() => { /* ... */ });
  const unCls  = onAppClosed(() => { /* ... */ });
  return () => { unOpen(); unMin(); unCls(); };
}, []);

onLogout(callback) / onStoreChanged(callback)

Session-boundary events: the merchant is no longer the same person, or is no longer looking at the same store. Both are broadcast to every installed app, background or foreground — a backgrounded app holds the departing merchant's data just as wrongly as the visible one. Both return an unsubscribe function.

function onLogout(callback: () => void): () => void
function onStoreChanged(callback: (store: {
  shopId         : string;         // now-active store; matches the new `shop` param
  shopName?      : string;         // display name, when the dashboard knows it
  previousShopId : string | null;  // the store just left, or null on a cold switch
}) => void): () => void

| Event | When it fires | |---|---| | onLogout | Merchant logs out of Salesive; your iframe is destroyed moments later | | onStoreChanged | Merchant switches active store; your iframe reloads with the new shop param |

// The dashboard clears only ITS origin on logout — your app is on another one,
// so its token survives unless you drop it here.
onLogout(() => {
  localStorage.removeItem("my-app-token");
  sessionStorage.clear();
});

// The reload (with the new ?shop=) is what boots you under the new store, so
// only clear what would otherwise SURVIVE it.
onStoreChanged(({ shopId, previousShopId }) => {
  if (previousShopId) caches.delete(`orders-${previousShopId}`);
  console.log("now operating on store", shopId);
});

⚠️ Keep both handlers synchronous. Each announces that your iframe is about to be torn down, and the dashboard pauses only briefly (~150 ms on logout) before doing it. An await on a network round trip will not finish — use navigator.sendBeacon() if your server must be told too.


Full React example

import { useEffect, useState } from "react";
import {
  PERMISSIONS, DURATIONS,
  requestPermission, getGrantedPermissions,
  onLaunch, onAppWake, onAppOpened, onAppMinimized, onAppClosed,
} from "salesive-dev-tools/permissions";

export default function App() {
  const [autoLaunchGranted, setAutoLaunchGranted] = useState(false);

  // Background init — fires before the merchant opens the app
  useEffect(() => {
    return onAppWake(async () => {
      connectWebSocket();
      const grants = await getGrantedPermissions();
      setAutoLaunchGranted(PERMISSIONS.AUTO_LAUNCH in grants);
    });
  }, []);

  // Foreground lifecycle
  useEffect(() => {
    const unOpen = onAppOpened(() => refreshDashboard());
    const unMin  = onAppMinimized(() => pauseHeavyWork());
    const unCls  = onAppClosed(() => saveDraft());
    return () => { unOpen(); unMin(); unCls(); };
  }, []);

  // Auto-reopen 30 s after minimize (only when granted)
  useEffect(() => {
    if (!autoLaunchGranted) return;
    return onAppMinimized(() => {
      const timer = setTimeout(onLaunch, 30_000);
      const unsub = onAppOpened(() => { clearTimeout(timer); unsub(); });
    });
  }, [autoLaunchGranted]);

  async function handleRequestAutoLaunch() {
    const ok = await requestPermission(
      PERMISSIONS.AUTO_LAUNCH,
      [DURATIONS.SEVEN_DAYS, DURATIONS.THIRTY_ONE_DAYS]
    );
    setAutoLaunchGranted(ok);
  }

  return (
    <div>
      {autoLaunchGranted
        ? <p>✓ Auto-launch active — app will reopen when minimized.</p>
        : <button onClick={handleRequestAutoLaunch}>Enable auto-reopen</button>
      }
    </div>
  );
}

Environment notes

  • Browser only — the bridge is a no-op in Node.js / SSR environments (all functions return false / {} / unsubscribe no-ops). Safe to import in Next.js or Remix.
  • No dependencies — the permissions module has no runtime dependencies.
  • Single listener — one window.addEventListener("message", …) is registered per browser context when the module is first imported. Multiple calls to requestPermission for different permissions run concurrently without conflict.

WebMCP — AI tools for Ola

Register tools from your app's page that Ola (the merchant's AI assistant in the Salesive dashboard) can discover and call — each call gated by a merchant approval card in the chat showing your app's logo, with an Always allow option per tool.

The API follows the WebMCP proposal (navigator.modelContext). No native browser support is required — this module is a complete implementation on its own: in browsers without WebMCP it does everything itself and also installs the standard navigator.modelContext / document.modelContext aliases; in browsers with native WebMCP it mirrors your registrations into the native registry too. You never feature-detect.

Import

// Subpath import (recommended):
import { modelContext, installWebMCP } from "salesive-dev-tools/webmcp";

// Or from the main package:
import { modelContext } from "salesive-dev-tools";

modelContext.registerTool(tool, options?)

modelContext.registerTool({
  name: "track_delivery",            // [a-zA-Z0-9_], ≤ 30 chars
  title: "Track delivery",           // shown on the approval card
  description: "Track a delivery by order id and return its live status.",
  inputSchema: {                     // JSON Schema, type "object"
    type: "object",
    properties: { orderId: { type: "string" } },
    required: ["orderId"],
  },
  annotations: { readOnlyHint: true },
  async execute({ orderId }) {       // runs IN YOUR PAGE after approval
    const status = await fetchStatus(orderId);
    return { content: [{ type: "text", text: `Delivery: ${status}` }] };
  },
});

execute may return MCP-style {content:[{type:"text",text}]}, a plain string, or any JSON value. Throw to report an error. Unregister with modelContext.unregisterTool(name) or spec-style via an AbortSignal in options.signal. Registry changes fire a toolchange event (modelContext.ontoolchange / addEventListener).

| Fact | Value | |---|---| | Tools per app | 32 | | Approval | Per call, or "Always allow" per tool per installation | | Call timeout | ~2 min including merchant approval | | Execution | Client-side in your iframe — no scopes, no tokens | | Result cap | 16 KB |

Register at startup (e.g. in onAppWake): your app is pre-loaded in the background, so tools are available to Ola before the merchant opens the app. Full guide: the "AI tools (WebMCP)" page in the Salesive docs.


Contributing

Contributions are welcome! Please feel free to submit issues or pull requests.

License

MIT