Agent Skills › MapleTechLabs/maple

MapleTechLabs/maple

GitHub

用于设计ClickHouse架构、选择摄入或建模模式,并将最佳实践转化为特定工作负载的系统设计。通过识别工作负载形状、引用官方文档并标注建议来源(官方/推导/经验),提供结构化决策框架与验证方法。

32 skills 1,512

Install All Skills

npx skills add MapleTechLabs/maple --all -g -y
More Options

List skills in collection

npx skills add MapleTechLabs/maple --list

Skills in Collection (32)

用于设计ClickHouse架构、选择摄入或建模模式,并将最佳实践转化为特定工作负载的系统设计。通过识别工作负载形状、引用官方文档并标注建议来源(官方/推导/经验),提供结构化决策框架与验证方法。
设计ClickHouse系统架构 选择数据摄入或建模策略 将最佳实践转化为特定工作负载的设计方案
.agents/skills/clickhouse-architecture-advisor/SKILL.md
npx skills add MapleTechLabs/maple --skill clickhouse-architecture-advisor -g -y
SKILL.md
Frontmatter
{
    "name": "clickhouse-architecture-advisor",
    "license": "Apache-2.0",
    "metadata": {
        "author": "ClickHouse Inc",
        "version": "0.1.0"
    },
    "description": "MUST USE when designing ClickHouse architectures, selecting between ingestion or modeling patterns, or translating best practices into workload-specific system designs. Complements clickhouse-best-practices with decision frameworks and explicit provenance labels."
}

ClickHouse Architecture Advisor

This skill adds workload-aware architecture decisioning on top of clickhouse-best-practices.

Official docs remain the source of truth. This skill must always prefer official ClickHouse documentation when available.

Required behavior

Before producing recommendations:

  1. Identify the workload shape
    • observability
    • security / SIEM
    • product analytics
    • IoT / telemetry
    • market data / financial services
    • mixed OLAP with point-lookups
  2. Read the relevant decision rule files in rules/
  3. Use mappings/doc_links.yaml to attach official documentation
  4. Classify every recommendation as:
    • official
    • derived
    • field
  5. Never present field guidance as official guidance
  6. If a recommendation is uncertain, say so explicitly

Provenance rules

official

Use this when the recommendation is directly backed by official docs.

derived

Use this when the recommendation is not stated verbatim in docs but follows logically from documented ClickHouse behavior.

field

Use this only for experience-based guidance that may be situational. When using field, include:

  • a disclaimer that the advice is heuristic
  • a relevant official doc if one partially applies
  • the reason the advice depends on workload context

Read these rule files by scenario

Real-time ingestion design

  1. rules/decision-ingestion-strategy.md
  2. rules/decision-real-time-preaggregation.md
  3. Relevant best-practices insert rules

Time-series and retention design

  1. rules/decision-partitioning-timeseries.md
  2. Relevant best-practices schema partition rules

Enrichment and dimension lookups

  1. rules/decision-join-enrichment.md
  2. Relevant best-practices query join rules

Mutable state / late-arriving events

  1. rules/decision-late-arriving-upserts.md
  2. Relevant best-practices mutation avoidance rules

Output format

Structure responses like this:

## Workload Summary

- workload:
- latency target:
- data shape:
- primary query patterns:
- operational constraints:

## Key Decisions

- ...
- ...

## Recommendations

### <Recommendation title>

**What**
...

**Why**
...

**How**
...

**Category**
official | derived | field

**Confidence**
high | medium | heuristic

**Source**

- doc link(s)

**Validation**

- concrete SQL, metric, or smoke test

Architecture-specific guidance

Prefer decision frameworks over generic advice. Good responses should:

  • explain tradeoffs
  • identify the likely operating bottleneck
  • separate immediate actions from structural redesign
  • provide target architecture patterns, not just isolated settings

Full reference

See AGENTS.md for the compiled version and examples/ for sample outputs.

用于审查ClickHouse模式、查询和配置的技能,包含28条优先级规则。在提供建议前必须检查相关规则文件并引用具体条款,确保符合ClickHouse特定行为的最佳实践。
审查ClickHouse表结构或CREATE/ALTER TABLE语句 优化或审查SELECT/JOIN等SQL查询 评估数据插入、更新或删除策略
.agents/skills/clickhouse-best-practices/SKILL.md
npx skills add MapleTechLabs/maple --skill clickhouse-best-practices -g -y
SKILL.md
Frontmatter
{
    "name": "clickhouse-best-practices",
    "license": "Apache-2.0",
    "metadata": {
        "author": "ClickHouse Inc",
        "version": "0.3.0"
    },
    "description": "MUST USE when reviewing ClickHouse schemas, queries, or configurations. Contains 28 rules that MUST be checked before providing recommendations. Always read relevant rule files and cite specific rules in responses."
}

ClickHouse Best Practices

Comprehensive guidance for ClickHouse covering schema design, query optimization, and data ingestion. Contains 28 rules across 3 main categories (schema, query, insert), prioritized by impact.

Official docs: ClickHouse Best Practices

IMPORTANT: How to Apply This Skill

Before answering ClickHouse questions, follow this priority order:

  1. Check for applicable rules in the rules/ directory
  2. If rules exist: Apply them and cite them in your response using "Per rule-name..."
  3. If no rule exists: Use the LLM's ClickHouse knowledge or search documentation
  4. If uncertain: Use web search for current best practices
  5. Always cite your source: rule name, "general ClickHouse guidance", or URL

Why rules take priority: ClickHouse has specific behaviors (columnar storage, sparse indexes, merge tree mechanics) where general database intuition can be misleading. The rules encode validated, ClickHouse-specific guidance.

For Formal Reviews

When performing a formal review of schemas, queries, or data ingestion:


Review Procedures

For Schema Reviews (CREATE TABLE, ALTER TABLE)

Read these rule files in order:

  1. rules/schema-pk-plan-before-creation.md - ORDER BY is immutable
  2. rules/schema-pk-cardinality-order.md - Column ordering in keys
  3. rules/schema-pk-prioritize-filters.md - Filter column inclusion
  4. rules/schema-types-native-types.md - Proper type selection
  5. rules/schema-types-minimize-bitwidth.md - Numeric type sizing
  6. rules/schema-types-lowcardinality.md - LowCardinality usage
  7. rules/schema-types-avoid-nullable.md - Nullable vs DEFAULT
  8. rules/schema-partition-low-cardinality.md - Partition count limits
  9. rules/schema-partition-lifecycle.md - Partitioning purpose

Check for:

  • PRIMARY KEY / ORDER BY column order (low-to-high cardinality)
  • Data types match actual data ranges
  • LowCardinality applied to appropriate string columns
  • Partition key cardinality bounded (100-1,000 values)
  • ReplacingMergeTree has version column if used

For Query Reviews (SELECT, JOIN, aggregations)

Read these rule files:

  1. rules/query-join-choose-algorithm.md - Algorithm selection
  2. rules/query-join-filter-before.md - Pre-join filtering
  3. rules/query-join-use-any.md - ANY vs regular JOIN
  4. rules/query-index-skipping-indices.md - Secondary index usage
  5. rules/schema-pk-filter-on-orderby.md - Filter alignment with ORDER BY

Check for:

  • Filters use ORDER BY prefix columns
  • JOINs filter tables before joining (not after)
  • Correct JOIN algorithm for table sizes
  • Skipping indices for non-ORDER BY filter columns

For Insert Strategy Reviews (data ingestion, updates, deletes)

Read these rule files:

  1. rules/insert-batch-size.md - Batch sizing requirements
  2. rules/insert-mutation-avoid-update.md - UPDATE alternatives
  3. rules/insert-mutation-avoid-delete.md - DELETE alternatives
  4. rules/insert-async-small-batches.md - Async insert usage
  5. rules/insert-optimize-avoid-final.md - OPTIMIZE TABLE risks

Check for:

  • Batch size 10K-100K rows per INSERT
  • No ALTER TABLE UPDATE for frequent changes
  • ReplacingMergeTree or CollapsingMergeTree for update patterns
  • Async inserts enabled for high-frequency small batches

Output Format

Structure your response as follows:

## Rules Checked
- `rule-name-1` - Compliant / Violation found
- `rule-name-2` - Compliant / Violation found
...

## Findings

### Violations
- **`rule-name`**: Description of the issue
  - Current: [what the code does]
  - Required: [what it should do]
  - Fix: [specific correction]

### Compliant
- `rule-name`: Brief note on why it's correct

## Recommendations
[Prioritized list of changes, citing rules]

Rule Categories by Priority

Priority Category Impact Prefix Rule Count
1 Primary Key Selection CRITICAL schema-pk- 4
2 Data Type Selection CRITICAL schema-types- 5
3 JOIN Optimization CRITICAL query-join- 5
4 Insert Batching CRITICAL insert-batch- 1
5 Mutation Avoidance CRITICAL insert-mutation- 2
6 Partitioning Strategy HIGH schema-partition- 4
7 Skipping Indices HIGH query-index- 1
8 Materialized Views HIGH query-mv- 2
9 Async Inserts HIGH insert-async- 2
10 OPTIMIZE Avoidance HIGH insert-optimize- 1
11 JSON Usage MEDIUM schema-json- 1

Quick Reference

Schema Design - Primary Key (CRITICAL)

  • schema-pk-plan-before-creation - Plan ORDER BY before table creation (immutable)
  • schema-pk-cardinality-order - Order columns low-to-high cardinality
  • schema-pk-prioritize-filters - Include frequently filtered columns
  • schema-pk-filter-on-orderby - Query filters must use ORDER BY prefix

Schema Design - Data Types (CRITICAL)

  • schema-types-native-types - Use native types, not String for everything
  • schema-types-minimize-bitwidth - Use smallest numeric type that fits
  • schema-types-lowcardinality - LowCardinality for <10K unique strings
  • schema-types-enum - Enum for finite value sets with validation
  • schema-types-avoid-nullable - Avoid Nullable; use DEFAULT instead

Schema Design - Partitioning (HIGH)

  • schema-partition-low-cardinality - Keep partition count 100-1,000
  • schema-partition-lifecycle - Use partitioning for data lifecycle, not queries
  • schema-partition-query-tradeoffs - Understand partition pruning trade-offs
  • schema-partition-start-without - Consider starting without partitioning

Schema Design - JSON (MEDIUM)

  • schema-json-when-to-use - JSON for dynamic schemas; typed columns for known

Query Optimization - JOINs (CRITICAL)

  • query-join-choose-algorithm - Select algorithm based on table sizes
  • query-join-use-any - ANY JOIN when only one match needed
  • query-join-filter-before - Filter tables before joining
  • query-join-consider-alternatives - Dictionaries/denormalization vs JOIN
  • query-join-null-handling - join_use_nulls=0 for default values

Query Optimization - Indices (HIGH)

  • query-index-skipping-indices - Skipping indices for non-ORDER BY filters

Query Optimization - Materialized Views (HIGH)

  • query-mv-incremental - Incremental MVs for real-time aggregations
  • query-mv-refreshable - Refreshable MVs for complex joins

Insert Strategy - Batching (CRITICAL)

  • insert-batch-size - Batch 10K-100K rows per INSERT

Insert Strategy - Async (HIGH)

  • insert-async-small-batches - Async inserts for high-frequency small batches
  • insert-format-native - Native format for best performance

Insert Strategy - Mutations (CRITICAL)

  • insert-mutation-avoid-update - ReplacingMergeTree instead of ALTER UPDATE
  • insert-mutation-avoid-delete - Lightweight DELETE or DROP PARTITION

Insert Strategy - Optimization (HIGH)

  • insert-optimize-avoid-final - Let background merges work

When to Apply

This skill activates when you encounter:

  • CREATE TABLE statements
  • ALTER TABLE modifications
  • ORDER BY or PRIMARY KEY discussions
  • Data type selection questions
  • Slow query troubleshooting
  • JOIN optimization requests
  • Data ingestion pipeline design
  • Update/delete strategy questions
  • ReplacingMergeTree or other specialized engine usage
  • Partitioning strategy decisions

Rule File Structure

Each rule file in rules/ contains:

  • YAML frontmatter: title, impact level, tags
  • Brief explanation: Why this rule matters
  • Incorrect example: Anti-pattern with explanation
  • Correct example: Best practice with explanation
  • Additional context: Trade-offs, when to apply, references

Full Compiled Document

For the complete guide with all rules expanded inline: AGENTS.md

Use AGENTS.md when you need to check multiple rules quickly without reading individual files.

指导用户通过 clickhousectl 将 ClickHouse 部署至云端。涵盖账户注册、CLI 认证(浏览器或 API Key)、服务创建、架构迁移及应用连接,适用于生产部署、托管服务及本地迁移场景。
用户希望将 ClickHouse 部署到云端 用户需要托管托管式 ClickHouse 服务 用户想从本地 ClickHouse 迁移至 ClickHouse Cloud 用户希望创建新的 ClickHouse Cloud 服务
.agents/skills/clickhousectl-cloud-deploy/SKILL.md
npx skills add MapleTechLabs/maple --skill clickhousectl-cloud-deploy -g -y
SKILL.md
Frontmatter
{
    "name": "clickhousectl-cloud-deploy",
    "license": "Apache-2.0",
    "metadata": {
        "author": "ClickHouse Inc",
        "version": "0.1.0"
    },
    "description": "Use when a user wants to deploy ClickHouse to the cloud, go to production, use ClickHouse Cloud, host a managed ClickHouse service, or migrate from a local ClickHouse setup to ClickHouse Cloud."
}

Deploy to ClickHouse Cloud

This skill walks through deploying to ClickHouse Cloud using clickhousectl. It covers account setup, CLI authentication, service creation, schema migration, and connecting your application. Follow these steps in order.

When to Apply

Use this skill when the user wants to:

  • Deploy their ClickHouse application to production
  • Host ClickHouse as a managed cloud service
  • Migrate from a local ClickHouse setup to ClickHouse Cloud
  • Create a ClickHouse Cloud service
  • Set up ClickHouse Cloud for the first time

Step 1: Sign up for ClickHouse Cloud

Before using any cloud commands, the user needs a ClickHouse Cloud account.

Ask the user: "Do you already have a ClickHouse Cloud account?"

If they do not have an account, explain:

ClickHouse Cloud is a fully managed service that runs ClickHouse for you — no infrastructure to maintain, automatic scaling, backups, and upgrades included. There's a free trial so you can get started without a credit card.

To create an account, go to: https://clickhouse.cloud

Sign up with your email, Google, or GitHub account. Once you're in the console, let me know and we'll continue with the next step.

Wait for the user to confirm they have signed up or already have an account before proceeding.


Step 2: Authenticate the CLI

First, ensure clickhousectl is installed. Check with:

which clickhousectl

If not found, install it:

curl -fsSL https://clickhouse.com/cli | sh

Now authenticate. There are two options — choose based on the situation:

Important: OAuth login is read-only. Browser login (Option A) grants read-only access — you can list organizations, services, and query existing services, but you cannot create, modify, or delete services. Any destructive or mutating cloud operation (service creation, deletion, scaling, etc.) requires API key authentication (Option B). If this workflow involves creating or changing cloud resources, you must use Option B.

Option A: Browser login (read-only access)

Use this when a human is available to open a browser and you only need to inspect existing cloud resources (list orgs, list services, query data). It uses OAuth device flow — no API keys needed.

Instruct the user to run:

clickhousectl cloud login

This prints a URL and a code. The user opens the URL in their browser, confirms the code, and logs in with their ClickHouse Cloud account. The CLI automatically receives credentials once the browser flow completes.

This is sufficient for steps that only read data (e.g., cloud org list, cloud service get, cloud service client). For any step that creates or modifies resources, switch to API key auth.

Option B: API key auth (required for destructive actions)

Use this option when creating, modifying, or deleting cloud resources (e.g., cloud service create, cloud service delete). Also use this for headless/CI environments where no browser is available. Both --api-key and --api-secret are required — if the user provides one without the other, tell them both are needed.

clickhousectl cloud login --api-key <key> --api-secret <secret>

If the user doesn't have API keys yet, guide them to create one:

In the ClickHouse Cloud console:

  1. Click the gear icon (Settings) in the left sidebar
  2. Go to API Keys
  3. Click Create API Key
  4. Give it a name (e.g., "cli") and select the Admin role
  5. Click Generate API Key
  6. Copy both the Key ID and the Key Secret — the secret is only shown once

To verify authentication works:

clickhousectl cloud org list

This should return the user's organization.


Step 3: Create a cloud service

Requires API key auth. Service creation is a mutating operation. If you authenticated with browser login (Option A) in Step 2, you must re-authenticate with API key auth (Option B) before proceeding.

Create a new ClickHouse Cloud service:

clickhousectl cloud service create --name <service-name>

The output includes the service ID and default user password — note it for subsequent commands.

Wait for the service to be ready. After creation, the service takes a moment to provision. Check its status:

clickhousectl cloud service get <service-id>

You can grep the "state" field to see if it is "running".


Step 4: Migrate schemas

If the user has local table definitions (e.g., from using the clickhousectl-local-dev skill), migrate them to the cloud service.

Use cloud service client to run queries against the cloud service — it looks up the endpoint, port, and TLS settings automatically. You just need the service name (or --id) and the password from step 3.

Read the local schema files from clickhouse/tables/ and apply each one to the cloud service:

clickhousectl cloud service client --name <service-name> \
  --queries-file clickhouse/tables/<table>.sql

Apply them in dependency order — tables referenced by materialized views should be created first.

Also apply materialized views if they exist:

clickhousectl cloud service client --name <service-name> \
  --queries-file clickhouse/materialized_views/<view>.sql

The --user flag defaults to default. If the user has a different database user, pass --user <username>.


Step 5: Verify the deployment

Connect to the cloud service and confirm tables exist:

clickhousectl cloud service client --name <service-name> --query "SHOW TABLES"

Run a test query to confirm the schema is correct:

clickhousectl cloud service client --name <service-name> --query "DESCRIBE TABLE <table-name>"

Step 6: Update application config

Retrieve the service endpoint for the user's application config:

clickhousectl cloud service get <service-id>

Provide the user with the connection details:

  • Host: from the service get output
  • Port: 8443 for HTTPS / 9440 for native TLS
  • User: default
  • Password: the password from step 3 (service creation)
  • SSL/TLS: required (always enabled on Cloud)

Example connection strings (adapt to the user's language/framework):

Python (clickhouse-connect):

import clickhouse_connect

client = clickhouse_connect.get_client(
    host='<cloud-host>',
    port=8443,
    username='default',
    password='<password>',
    secure=True
)

Node.js (@clickhouse/client):

import { createClient } from "@clickhouse/client"

const client = createClient({
	url: "https://<cloud-host>:8443",
	username: "default",
	password: "<password>",
})

Go (clickhouse-go):

conn, err := clickhouse.Open(&clickhouse.Options{
    Addr: []string{"<cloud-host>:9440"},
    Auth: clickhouse.Auth{
        Username: "default",
        Password: "<password>",
    },
    TLS: &tls.Config{},
})

Suggest the user store the password in an environment variable or secrets manager rather than hardcoding it.

Suggest the user should not use the default user in production. A user should be created just for their app.

指导用户从零开始搭建本地 ClickHouse 开发环境,涵盖安装 clickhousectl、部署实例、初始化项目结构、启动服务及创建数据表等全流程。
构建需要分析型数据库的应用 设置本地 ClickHouse 开发环境 在本地机器上安装 ClickHouse 创建表并开始本地查询 原型实验
.agents/skills/clickhousectl-local-dev/SKILL.md
npx skills add MapleTechLabs/maple --skill clickhousectl-local-dev -g -y
SKILL.md
Frontmatter
{
    "name": "clickhousectl-local-dev",
    "license": "Apache-2.0",
    "metadata": {
        "author": "ClickHouse Inc",
        "version": "0.1.0"
    },
    "description": "Use when a user wants to build an application with ClickHouse, set up a local ClickHouse development environment, install ClickHouse, create a local server, create tables, or start developing with ClickHouse. Covers the full flow from zero to a working local ClickHouse setup."
}

Local ClickHouse Development Setup

This skill walks through setting up a complete local ClickHouse development environment using clickhousectl. Follow these steps in order.

When to Apply

Use this skill when the user wants to:

  • Build an application that needs an analytical database or ClickHouse specifically
  • Set up a local ClickHouse instance for development
  • Install ClickHouse on their machine
  • Create tables and start querying ClickHouse locally
  • Prototype or experiment with ClickHouse

Step 1: Install clickhousectl

Check if clickhousectl is already available:

which clickhousectl

If not found, install it:

curl -fsSL https://clickhouse.com/cli | sh

This installs clickhousectl to ~/.local/bin/clickhousectl and creates a chctl alias.

If the command is still not found after install: The user may need to add ~/.local/bin to their PATH or open a new terminal session. Suggest:

export PATH="$HOME/.local/bin:$PATH"

Step 2: Install ClickHouse

Install the latest stable ClickHouse version:

clickhousectl local install stable

This downloads the ClickHouse binary to ~/.clickhouse/versions/. The binary is shared across projects so it only needs to be downloaded once.

Alternative version specifiers (use if the user has a specific need):

  • lts — latest long-term support release
  • 25.12 — latest patch of a specific minor version
  • 25.12.5.44 — exact version

Set the installed version as the default:

clickhousectl local use stable

Step 3: Initialize the project

From the user's project root directory:

clickhousectl local init

This creates a standard folder structure:

clickhouse/
  tables/                 # CREATE TABLE statements
  materialized_views/     # Materialized view definitions
  queries/                # Saved queries
  seed/                   # Seed data / INSERT statements

Note: This step is optional. If the user already has their own folder structure for SQL files, skip this and adapt the later steps to use their paths.


Step 4: Start a local server

clickhousectl local server start --name <name>

This starts a ClickHouse server in the background. Server data is stored in .clickhouse/servers/<anem>/data/ within the project directory.

To check running servers and see their exposed ports:

clickhousectl local server list

Step 5: Create the schema

Based on the user's application requirements, write CREATE TABLE SQL files.

Write each table definition to its own file in clickhouse/tables/:

# Example: clickhouse/tables/events.sql
CREATE TABLE IF NOT EXISTS events (
    timestamp DateTime,
    user_id UInt32,
    event_type LowCardinality(String),
    properties String
)
ENGINE = MergeTree()
ORDER BY (event_type, timestamp)

When designing schemas, if the clickhouse-best-practices skill is available, consult it for guidance on ORDER BY column selection, data types, and partitioning.

Apply the schema to the running server:

clickhousectl local client --name <name> --queries-file clickhouse/tables/events.sql

Step 6: Seed data (optional)

If the user needs sample data for development, write INSERT statements to clickhouse/seed/:

# Example: clickhouse/seed/events.sql
INSERT INTO events (timestamp, user_id, event_type, properties) VALUES
    ('2024-01-01 00:00:00', 1, 'page_view', '{"page": "/home"}'),
    ('2024-01-01 00:01:00', 2, 'click', '{"button": "signup"}');

Apply seed data:

clickhousectl local client --name <name> --queries-file clickhouse/seed/events.sql

Step 7: Verify the setup

Confirm tables were created:

clickhousectl local client --name <name> --query "SHOW TABLES"

Run a test query:

clickhousectl local client --name <name> --query "SELECT count() FROM events"

If the user wants to use a managed ClickHouse service, use the clickhousectl-cloud-deploy skill to help the user deploy to ClickHouse Cloud.

提供COSS UI粒子组件索引,用于快速查找可复制粘贴的UI模式。通过描述需求搜索组件类型,获取JSON源码并适配,涵盖480个粒子及52种组件类型,加速前端界面开发。
需要实现特定UI功能如表单、对话框或标签页 寻找基于coss原语的现成组件代码片段 询问如何快速构建带有验证或图标的界面元素
.agents/skills/coss-particles/SKILL.md
npx skills add MapleTechLabs/maple --skill coss-particles -g -y
SKILL.md
Frontmatter
{
    "name": "coss-particles",
    "license": "MIT",
    "metadata": {
        "author": "cosscom"
    },
    "description": "Index of all COSS UI particle examples. Use when implementing UI features to find copy-paste-ready component patterns built on coss primitives. Each particle has a description and a JSON URL for easy installation.",
    "compatibility": "Requires coss UI components installed in the project."
}

COSS UI Particles Index

Particles are copy-paste-ready UI patterns built on coss primitives. Browse them visually at https://coss.com/ui/particles.

How to use this skill

  1. Describe the UI you need (e.g. "a form with validation", "a dialog with a form inside", "tabs with icons").
  2. Search this index for matching particles by component type and description.
  3. Fetch the JSON URL to get the full source code of the particle.
  4. Adapt the particle code to your needs.

JSON URL pattern

Each particle has a JSON manifest at:

https://coss.com/ui/r/<particle-name>.json

For example: https://coss.com/ui/r/p-accordion-1.json

Source code

Particle source files live in this repo at apps/ui/registry/default/particles/.

Updating this index

Run the generator script from the coss repo root:

node apps/ui/scripts/generate-particle-index.cjs

Total: 480 particles across 52 component types

Component types


accordion

  • Basic accordion | JSON
  • Accordion with one panel open | JSON
  • Accordion allowing multiple panels open | JSON
  • Controlled accordion | JSON

alert

  • Basic alert | JSON
  • Alert with icon | JSON
  • Alert with icon and action buttons | JSON
  • Info alert | JSON
  • Success alert | JSON
  • Warning alert | JSON
  • Error alert | JSON

alert-dialog

  • Alert dialog | JSON
  • Alert dialog with bare footer | JSON

autocomplete

  • Basic autocomplete | JSON
  • Disabled autocomplete | JSON
  • Small autocomplete | JSON
  • Large autocomplete | JSON
  • Autocomplete with label | JSON
  • Autocomplete autofilling the input with the highlighted item | JSON
  • Autocomplete auto highlighting the first option | JSON
  • Autocomplete with clear button | JSON
  • Autocomplete with trigger and clear buttons | JSON
  • Autocomplete with grouped items | JSON
  • Autocomplete with limited number of results | JSON
  • Autocomplete with async items loading | JSON
  • Autocomplete form | JSON
  • Autocomplete form | JSON
  • Pill-shaped autocomplete | JSON

avatar

  • Avatar with image and fallback | JSON
  • Fallback-only avatar | JSON
  • Avatars with different sizes | JSON
  • Avatars with different radii | JSON
  • Overlapping avatar group | JSON
  • Avatar with user icon fallback | JSON
  • Avatar with emerald status dot | JSON
  • Avatar with muted status dot | JSON
  • Rounded avatar with top-right emerald status | JSON
  • Avatar with notification badge | JSON
  • Rounded avatar with notification badge | JSON
  • Avatar with verified badge | JSON
  • Small overlapping avatar group | JSON
  • Large overlapping avatar group | JSON

badge

  • Basic badge | JSON
  • Outline badge | JSON
  • Secondary badge | JSON
  • Destructive badge | JSON
  • Info badge | JSON
  • Success badge | JSON
  • Warning badge | JSON
  • Error badge | JSON
  • Small badge | JSON
  • Large badge | JSON
  • Badge with icon | JSON
  • Badge with link | JSON
  • Badge with count | JSON
  • Full rounded badge (pill) | JSON
  • Badge with number after text | JSON
  • Status badge - Paid | JSON
  • Status badge - Pending | JSON
  • Status badge - Failed | JSON
  • Selectable badge with checkbox | JSON
  • Removable badge | JSON

breadcrumb

  • Breadcrumb with menu example | JSON
  • Breadcrumb with custom separator | JSON
  • Breadcrumb with home icon for home link only | JSON
  • Breadcrumb with folders icon menu | JSON
  • Breadcrumb with icons before text | JSON
  • Breadcrumb with dot separators | JSON
  • Breadcrumb with select dropdown | JSON

button

  • Default button | JSON
  • Outline button | JSON
  • Secondary button | JSON
  • Destructive button | JSON
  • Destructive outline button | JSON
  • Ghost button | JSON
  • Link button | JSON
  • Extra-small button | JSON
  • Small button | JSON
  • Large button | JSON
  • Extra-large button | JSON
  • Disabled button | JSON
  • Icon button | JSON
  • Small icon button | JSON
  • Large icon button | JSON
  • Button with icon | JSON
  • Link rendered as button | JSON
  • Button using the built-in loading prop | JSON
  • Custom loading button with manual Spinner | JSON
  • Expandable show more/less toggle button | JSON
  • Back link button with chevron | JSON
  • Card-style button with heading and description | JSON
  • Directional pad control buttons | JSON
  • Outline like button with count | JSON
  • Social login icon buttons | JSON
  • Star button with count badge | JSON
  • Button group with QR code icon and sign in | JSON
  • Button with avatar | JSON
  • Pill-shaped button with rounded-full styling | JSON
  • Button with animated arrow on hover | JSON
  • Button with keyboard shortcut indicator | JSON
  • Button with notification badge | JSON
  • Paired buttons (Cancel/Save) | JSON
  • Button with animated status dot | JSON
  • Icon-only copy button with feedback | JSON
  • Copy button with feedback | JSON
  • Rotating icon button (FAB-style toggle) | JSON
  • Hamburger menu button with animated icon | JSON
  • Download button with progress and cancel action | JSON
  • Social login buttons (Google, X, GitHub) | JSON

calendar

  • Basic calendar | JSON
  • Calendar with date range selection | JSON
  • Calendar with month/year dropdown navigation | JSON
  • Calendar with custom Select dropdown for month/year | JSON
  • Calendar with Combobox dropdown for month/year | JSON
  • Calendar with disabled dates | JSON
  • Calendar with multiple date selection | JSON
  • Calendar with custom cell size | JSON
  • Calendar with rounded day buttons | JSON
  • Calendar with rounded range selection style | JSON
  • Calendar with right-aligned navigation | JSON
  • Calendar with week numbers | JSON
  • Calendar with year-only combobox dropdown | JSON
  • Calendar without arrow navigation (dropdown only) | JSON
  • Calendar with current month button | JSON
  • Calendar with today button | JSON
  • Calendar with date input | JSON
  • Calendar with time input | JSON
  • Calendar with time slots (appointment picker) | JSON
  • Calendar with date presets | JSON
  • Range calendar with date presets | JSON
  • Two months calendar | JSON
  • Three months calendar | JSON
  • Pricing calendar with custom day buttons | JSON

card

  • A basic card with header and footer | JSON
  • Authentication card with actions | JSON
  • Authentication card with separators | JSON
  • Framed card with footer | JSON
  • Framed card with header | JSON
  • Framed card with header and footer | JSON
  • Framed card with no rounded bottom | JSON
  • Card within a frame and footer | JSON
  • Card within a frame and footer | JSON
  • Card within a frame with header and footer | JSON
  • CardFrame with header action | JSON

checkbox

  • Basic checkbox | JSON
  • Disabled checkbox | JSON
  • Checkbox with description | JSON
  • Card-style checkbox | JSON
  • Checkbox form | JSON

checkbox-group

  • Basic checkbox group | JSON
  • Checkbox group with disabled items | JSON
  • Checkbox group with parent checkbox | JSON
  • Nested checkbox group with parent | JSON
  • Checkbox group form | JSON

collapsible

  • Basic collapsible | JSON

combobox

  • Basic combobox | JSON
  • Disabled combobox | JSON
  • Small combobox | JSON
  • Large combobox | JSON
  • Combobox with label | JSON
  • Combobox auto highlighting the first option | JSON
  • Combobox with clear button | JSON
  • Combobox with grouped items | JSON
  • Combobox with multiple selection | JSON
  • Combobox with input inside popup | JSON
  • Combobox form | JSON
  • Combobox multiple form | JSON
  • Combobox with start addon | JSON
  • Combobox multiple with start addon | JSON
  • Pill-shaped combobox | JSON
  • Timezone combobox | JSON
  • Timezone combobox with search input | JSON
  • Combobox with select trigger | JSON

command

  • Command palette with dialog | JSON
  • Command palette with AI assistant | JSON

date-picker

  • Basic date picker | JSON
  • Date range picker | JSON
  • Two months calendar with range date | JSON
  • Date picker with field and dropdown navigation | JSON
  • Date picker with presets | JSON
  • Date picker with input | JSON
  • Date picker that closes on select | JSON
  • Multiple dates picker | JSON
  • Date picker with select-like trigger | JSON

dialog

  • Dialog with form | JSON
  • Dialog with bare footer | JSON
  • Dialog opened from menu | JSON
  • Nested dialogs | JSON
  • Dialog with close confirmation | JSON
  • Dialog with long content | JSON

drawer

  • Simple bottom drawer with close button | JSON
  • Bottom drawer without drag bar | JSON
  • Drawer with close button | JSON
  • Inset variant drawers for all four positions | JSON
  • Straight variant drawers for all four positions | JSON
  • Scrollable content with terms and conditions | JSON
  • Nested bottom drawers with centered content | JSON
  • Nested right drawers with inset variant | JSON
  • Bottom drawer with snap points | JSON
  • Edit profile form with default and bare footer variants | JSON
  • Mobile menu drawer from the left | JSON
  • Responsive edit profile: dialog on desktop, drawer on mobile | JSON
  • Responsive actions menu: menu on desktop, drawer on mobile | JSON
  • Left drawer with swipe area | JSON

empty

  • Empty state with icon and actions | JSON

field

  • Field with description | JSON
  • Field with required indicator | JSON
  • Field in disabled state | JSON
  • Field showing validation error | JSON
  • Show field validity state | JSON
  • Input group with field | JSON
  • Field with autocomplete | JSON
  • Field with combobox | JSON
  • Field with multiple selection combobox | JSON
  • Field with textarea | JSON
  • Field with select | JSON
  • Field with checkbox | JSON
  • Field with checkbox group | JSON
  • Field with radio group | JSON
  • Field with toggle switch | JSON
  • Field with slider | JSON
  • Field with number field | JSON
  • Complete form built with field | JSON

fieldset

  • Fieldset with multiple fields | JSON

form

  • Input in a form | JSON
  • Form with zod validation | JSON

frame

  • Basic frame | JSON
  • Frame with multiple separated panels | JSON
  • Frame with multiple stacked panels | JSON
  • Frame with collapsible content and delete button | JSON

group

  • Basic group | JSON
  • Group with input | JSON
  • Small group | JSON
  • Large group | JSON
  • Group with disabled button | JSON
  • Group with default button | JSON
  • Group with start text | JSON
  • Group with end text | JSON
  • Vertical group | JSON
  • Nested groups | JSON
  • Group with popup | JSON
  • Group with input group | JSON
  • Group with menu | JSON
  • Group with select | JSON
  • Group with search | JSON
  • Group with add button and input | JSON
  • Group with input and currency text | JSON
  • Group with select and input | JSON
  • Group with input and select | JSON
  • Group with input and text button | JSON
  • Group with two number inputs for range | JSON
  • Group with filter label, combobox multi-select, and remove button | JSON

input

  • Basic input | JSON
  • Small input | JSON
  • Large input | JSON
  • Disabled input | JSON
  • File input | JSON
  • Input with label | JSON
  • Input with button using Group | JSON
  • Input with start text and end tooltip | JSON
  • Password input with toggle visibility | JSON
  • Input group mimicking a URL bar | JSON
  • Input group with keyboard shortcut | JSON
  • Input group with start loading spinner | JSON
  • Input with label and required indicator | JSON
  • Input with optional label | JSON
  • Input with custom border and background | JSON
  • Input group with end loading spinner | JSON
  • Read-only input | JSON
  • Input with characters remaining counter | JSON
  • Pill-shaped input | JSON

input-group

  • Basic input group | JSON
  • Input group with end icon | JSON
  • Input group with start text | JSON
  • Input group with end text | JSON
  • Input group with start and end text | JSON
  • Input group with number field | JSON
  • Input group with end tooltip | JSON
  • Input group with icon button | JSON
  • Input group with button | JSON
  • Input group with badge | JSON
  • Input group with keyboard shortcut | JSON
  • Input group with inner label | JSON
  • Small input group | JSON
  • Large input group | JSON
  • Disabled input group | JSON
  • Input group with loading spinner | JSON
  • Input group with textarea | JSON
  • Input group with badge and menu | JSON
  • Mini editor built with input group and toggle | JSON
  • Input group with search icon | JSON
  • Input group with start tooltip | JSON
  • Input group with clear button | JSON
  • Search input group with loader and voice button | JSON
  • Input group with character counter | JSON
  • Password input with strength indicator | JSON
  • Code snippet input with language selector | JSON
  • Message composer with attachment buttons | JSON
  • Chat input with voice and send buttons | JSON

kbd

  • Keyboard shortcuts display | JSON

menu

  • Basic menu | JSON
  • Menu with hover | JSON
  • Menu with checkbox | JSON
  • Menu with checkbox items as switches | JSON
  • Menu with radio group | JSON
  • Menu with link | JSON
  • Menu with group labels | JSON
  • Nested menu | JSON
  • Menu close on click | JSON

meter

  • Basic meter | JSON
  • Simple meter | JSON
  • Meter with formatted value | JSON
  • Meter with range | JSON

number-field

  • Basic number field | JSON
  • Small number field | JSON
  • Large number field | JSON
  • Disabled number field | JSON
  • Number field with label | JSON
  • Number field with scrub | JSON
  • Number field with range | JSON
  • Number field with formatted value | JSON
  • Number field with step | JSON
  • Number field in form | JSON
  • Pill-shaped number field | JSON

otp-field

  • Basic OTP field | JSON
  • Large OTP field | JSON
  • OTP field with separator | JSON
  • OTP field with label | JSON
  • OTP field with custom sanitization | JSON
  • OTP field with auto validation | JSON
  • Alphanumeric OTP field | JSON
  • OTP field with placeholder hints | JSON
  • Masked OTP field | JSON

pagination

  • Pagination example | JSON
  • Pagination with previous and next buttons only | JSON
  • Pagination with select, and previous and next buttons | JSON

popover

  • Popover with a form | JSON
  • Popover with close button | JSON
  • Animated popovers | JSON

preview-card

  • Preview card with popup | JSON

progress

  • Basic progress bar | JSON
  • Progress with label and value | JSON
  • Progress with formatted value | JSON

radio-group

  • Basic radio group | JSON
  • Disabled radio group | JSON
  • Radio group with description | JSON
  • Radio group card | JSON
  • Radio group in form | JSON
  • Theme selector with image cards | JSON

scroll-area

  • Basic scroll area | JSON
  • Horizontal scroll area | JSON
  • Scroll area with both directions | JSON
  • Scroll area with fading edges | JSON
  • Horizontal scroll area with scrollbar gutter | JSON

select

  • Basic select | JSON
  • Small select | JSON
  • Large select | JSON
  • Disabled select | JSON
  • Select without item alignment | JSON
  • Select with groups | JSON
  • Multiple select | JSON
  • Select with icon | JSON
  • Select options with icon | JSON
  • Select with object values | JSON
  • Select with disabled items | JSON
  • Timezone select | JSON
  • Status select with colored dot | JSON
  • Pill-shaped select trigger | JSON
  • Select with left text label | JSON
  • Select with country flags | JSON
  • Select with description in options only | JSON
  • Select with avatars | JSON
  • Rich select with avatars and usernames | JSON
  • Auto width select | JSON
  • Select with custom border and background | JSON
  • Select with label | JSON
  • Select in form | JSON

separator

  • Separator with horizontal and vertical orientations | JSON

sheet

  • Basic sheet | JSON
  • Sheet inset | JSON
  • Sheet position | JSON

skeleton

  • Basic skeleton | JSON
  • Skeleton only | JSON

slider

  • Basic slider | JSON
  • Slider with label and value | JSON
  • Disabled slider | JSON
  • Slider with reference labels | JSON
  • Slider with ticks | JSON
  • Slider with labels above | JSON
  • Range slider | JSON
  • Slider with 3 thumbs | JSON
  • Range slider with collision behavior none | JSON
  • Range slider with collision behavior swap | JSON
  • Slider with icons | JSON
  • Slider with input | JSON
  • Range slider with inputs | JSON
  • Slider with increment and decrement buttons | JSON
  • Price range slider | JSON
  • Emoji rating slider | JSON
  • Vertical slider | JSON
  • Vertical range slider | JSON
  • Vertical slider with input | JSON
  • Equalizer with vertical sliders | JSON
  • Object position sliders with reset | JSON
  • Price slider with histogram | JSON
  • Slider in form | JSON

spinner

  • Basic spinner | JSON

switch

  • Basic switch | JSON
  • Disabled switch | JSON
  • Switch with description | JSON
  • Switch card | JSON
  • Switch in form | JSON
  • Custom size switch | JSON

table

  • Basic table | JSON
  • Frame with card-style table | JSON
  • Table with TanStack Table and checkboxes | JSON
  • Table with TanStack Table, sorting, and pagination | JSON
  • Card-style table variant | JSON
  • CardFrame with card-style table | JSON
  • CardFrame with TanStack Table and checkboxes | JSON
  • CardFrame with TanStack Table, sorting, and pagination | JSON

tabs

  • Basic tabs | JSON
  • Tabs with underline | JSON
  • Vertical tabs | JSON
  • Vertical tabs with underline | JSON
  • Tabs with full rounded triggers | JSON
  • Tabs with icon before name | JSON
  • Tabs with icon before name and underline | JSON
  • Tabs with icon only | JSON
  • Tabs with underline and icon on top | JSON
  • Tabs with count badge | JSON
  • Vertical tabs with underline and icon before name | JSON
  • Tabs with icon only and count badge | JSON
  • Tabs with icon only and grouped tooltips | JSON

textarea

  • Basic textarea | JSON
  • Small textarea | JSON
  • Large textarea | JSON
  • Disabled textarea | JSON
  • Textarea with label | JSON
  • Textarea in form | JSON
  • Textarea with label and required indicator | JSON
  • Textarea with optional label | JSON
  • Textarea with custom border and background | JSON
  • Read-only textarea | JSON
  • Textarea with characters remaining counter | JSON
  • Textarea field with required indicator | JSON
  • Shorter textarea with fixed height | JSON
  • Textarea with button aligned right | JSON
  • Textarea with button aligned left | JSON

toast

  • Basic toast | JSON
  • Toast with status | JSON
  • Loading toast | JSON
  • Toast with action | JSON
  • Promise toast | JSON
  • Toast with varying heights | JSON
  • Anchored toast with tooltip style | JSON
  • Anchored toast | JSON
  • Promise toast with cancel action | JSON

toggle

  • Basic toggle | JSON
  • Toggle with outline | JSON
  • Toggle with icon | JSON
  • Small toggle | JSON
  • Large toggle | JSON
  • Disabled toggle | JSON
  • Toggle icon group | JSON
  • Bookmark toggle with tooltip and success toast | JSON

toggle-group

  • Basic toggle group | JSON
  • Small toggle group | JSON
  • Large toggle group | JSON
  • Toggle group with outline | JSON
  • Vertical toggle group with outline | JSON
  • Disabled toggle group | JSON
  • Toggle group with disabled item | JSON
  • Multiple selection toggle group | JSON
  • Toggle group with tooltips | JSON

toolbar

  • Toolbar with toggles, buttons, and select | JSON

tooltip

  • Basic tooltip | JSON
  • Grouped tooltips | JSON
  • Toggle group animated tooltip | JSON
  • Vertical group with animated tooltip | JSON
辅助正确使用coss UI组件库,涵盖组件选择、代码编写、shadcn迁移及故障排除。基于Base UI,强调组合优于自定义,遵循文档规范与无障碍标准,提供样式、表单及触发器规则参考。
使用 coss 原语构建 UI 从 shadcn/Radix 迁移到 coss 解决 coss 组件行为问题 配置触发式叠加层
.agents/skills/coss/SKILL.md
npx skills add MapleTechLabs/maple --skill coss -g -y
SKILL.md
Frontmatter
{
    "name": "coss",
    "license": "MIT",
    "metadata": {
        "author": "cosscom"
    },
    "description": "Helps implement coss UI components correctly. Use when building UIs with coss primitives (buttons, dialogs, selects, forms, menus, tabs, inputs, toasts, etc.), migrating from shadcn\/Radix to coss\/Base UI, composing trigger-based overlays, or troubleshooting coss component behavior. Covers imports, accessibility, Tailwind styling, and common pitfalls.",
    "compatibility": "Requires Tailwind CSS v4 and @base-ui\/react. Designed for React projects using the coss component registry."
}

coss ui

coss ui is a component library built on Base UI with a shadcn-like developer experience plus a large particle catalog.

What this skill is for

Use this skill to:

  • pick the right coss primitive(s) for a UI task
  • write correct coss usage code (imports, composition, props)
  • avoid common migration mistakes from shadcn/Radix assumptions
  • reference particle examples to produce practical, production-like patterns

Source of truth

  • coss components docs: apps/ui/content/docs/components/*.mdx
    • https://github.com/cosscom/coss/tree/main/apps/ui/content/docs/components
  • coss particle examples: apps/ui/registry/default/particles/p-*.tsx
    • https://github.com/cosscom/coss/tree/main/apps/ui/registry/default/particles
  • coss particles catalog: https://coss.com/ui/particles
  • docs map for agents: https://coss.com/ui/llms.txt

Out of scope

  • Maintaining coss monorepo internals/build pipelines.
  • Editing registry internals unless explicitly requested.

Principles for agent output

  1. Use existing primitives and particles first before inventing custom markup.
  2. Prefer composition over custom behavior reimplementation.
  3. Follow coss naming and APIs from docs exactly.
  4. Keep examples accessible and production-realistic.
  5. Prefer concise code that mirrors coss docs/particles conventions.
  6. Assume Tailwind CSS v4 conventions in coss examples and setup guidance.

Critical usage rules

Always apply before returning coss code:

  • Do not invent coss APIs. Verify against component docs first.
  • For trigger-based primitives (Dialog, Menu, Select, Popover, Tooltip), follow each primitive's documented trigger/content hierarchy and composition API; do not mix patterns across components.
  • Preserve accessibility labels and error semantics.
  • Consult primitive-specific guides for component invariants and edge cases.
  • For manual install guidance, include all required dependencies and local component files referenced by imports.
  • Prefer styled coss exports first; use *Primitive exports only when custom composition/styling requires it.

Rule references (read on demand when the task touches these areas):

  • ./references/rules/styling.md — Tailwind tokens, icon conventions, data-slot selectors
  • ./references/rules/forms.md — Field composition, validation, input patterns
  • ./references/rules/composition.md — Trigger/popup hierarchies, grouped controls
  • ./references/rules/migration.md — shadcn/Radix to coss/Base UI migration patterns
  • ./references/portal-props.md — optional portalProps on composed popups (keepMounted, container, which wrappers support it)

Component discovery

All 53 primitives have dedicated reference guides at ./references/primitives/<name>.md. To find the right one for a task, consult the component registry index:

  • ./references/component-registry.md

Usage workflow

  1. Identify user intent (single primitive, composed flow, form flow, overlay flow, feedback flow).
  2. Consult references/component-registry.md to identify candidate primitives.
  3. Select primitives from coss docs first; avoid custom fallback unless needed.
  4. Check at least one particle example for practical composition patterns. Particle files live at apps/ui/registry/default/particles/p-<name>-<N>.tsx (e.g. p-dialog-1.tsx).
  5. Write minimal code using documented imports/props.
  6. Self-check accessibility and composition invariants.

Installation reference

See ./references/cli.md for full install/discovery workflow.

Quick CLI pattern:

npx shadcn@latest add @coss/<component>

Quick manual pattern:

  • install dependencies listed in the component docs page
  • copy required component file(s)
  • update imports to match the target app alias setup

Primitive Guidance

Every primitive has a reference guide at ./references/primitives/<name>.md with imports, minimal patterns, inline code examples, pitfalls, and particle references. Use the component registry to find the right file.

High-risk primitives (read these guides first -- they have the most composition gotchas):

  • ./references/primitives/dialog.md — modal overlays, form-in-dialog, responsive dialog/drawer
  • ./references/primitives/menu.md — dropdown actions, checkbox/radio items, submenus
  • ./references/primitives/select.md — items-first pattern, multiple, object values, groups
  • ./references/primitives/form.md — Field composition, validation, submission
  • ./references/primitives/input-group.md — addons, DOM order invariant, textarea layouts
  • ./references/primitives/toast.md — toastManager (not Sonner), anchored toasts, providers

Output Checklist

Before returning code:

  • imports and props match coss docs
  • composition structure is valid for selected primitive(s)
  • accessibility and explicit control types (button, input, etc.) are present
  • migration-sensitive flows are verified (type/lint, keyboard/a11y behavior, and SSR-sensitive primitives like Select/Command)
用于在React代码提交前或清理时扫描安全、性能及架构问题,输出0-100健康评分。支持通过--diff检测变更回归或全面扫描以修复错误和警告,确保代码质量不降级。
完成功能开发后 修复Bug后 提交React代码前 用户希望提升代码质量或清理代码库时
.agents/skills/react-doctor/SKILL.md
npx skills add MapleTechLabs/maple --skill react-doctor -g -y
SKILL.md
Frontmatter
{
    "name": "react-doctor",
    "version": "1.0.0",
    "description": "Use when finishing a feature, fixing a bug, before committing React code, or when the user wants to improve code quality or clean up a codebase. Checks for score regression. Covers lint, dead code, accessibility, bundle size, architecture diagnostics."
}

React Doctor

Scans React codebases for security, performance, correctness, and architecture issues. Outputs a 0–100 health score.

After making React code changes:

Run npx -y react-doctor@latest . --verbose --diff and check the score did not regress.

If the score dropped, fix the regressions before committing.

For general cleanup or code improvement:

Run npx -y react-doctor@latest . --verbose (without --diff) to scan the full codebase. Fix issues by severity — errors first, then warnings.

Command

npx -y react-doctor@latest . --verbose --diff
Flag Purpose
. Scan current directory
--verbose Show affected files and line numbers per rule
--diff Only scan changed files vs base branch
--score Output only the numeric score
提供Tinybird CLI使用指南,涵盖本地开发、分支管理、部署、数据操作及CI/CD配置。指导用户正确使用tb命令进行构建、部署和测试,强调遵循官方文档,避免虚构参数。
运行 tb 相关命令 选择开发工作流(本地或分支) 执行本地或云端部署 进行数据追加、替换或删除 管理令牌与密钥 生成模拟数据 搭建 CI/CD 流水线
.agents/skills/tinybird-cli-guidelines/SKILL.md
npx skills add MapleTechLabs/maple --skill tinybird-cli-guidelines -g -y
SKILL.md
Frontmatter
{
    "name": "tinybird-cli-guidelines",
    "description": "Tinybird CLI commands, workflows, and operations. Use when running tb commands, managing local development, deploying, or working with data operations."
}

Tinybird CLI Guidelines

Guidance for using the Tinybird CLI (tb) for local development, deployments, data operations, and workspace management.

When to Apply

  • Running any tb command
  • Choosing a development workflow (local, branch, or cloud)
  • Local development with Tinybird Local
  • Branch development with Tinybird Cloud branches
  • Building and deploying projects
  • Setting up CI/CD pipelines
  • Appending, replacing, or deleting data
  • Managing tokens and secrets via CLI
  • Generating mock data
  • Running tests

Rule Files

  • rules/development-workflows.md
  • rules/cli-commands.md
  • rules/build-deploy.md
  • rules/local-development.md
  • rules/branch-development.md
  • rules/ci-cd.md
  • rules/data-operations.md
  • rules/append-data.md
  • rules/mock-data.md
  • rules/tokens.md
  • rules/secrets.md

Quick Reference

  • CLI 4.0 workflow: configure dev_mode once, then use plain tb build and tb deploy.
  • tb build targets your configured development environment (branch or local) in tinybird.config.json.
  • tb deploy targets Tinybird Cloud production.
  • Use --cloud/--local/--branch only as explicit manual overrides.
  • Use tb info to check CLI context.
  • Use tb endpoint data <pipe> to test endpoints (not tb pipe data).
  • Never invent commands or flags; run tb <command> --help to verify.
提供Tinybird Python SDK的使用指南,涵盖数据源、管道和查询的Python定义、CLI命令(dev/build/deploy)、连接配置及从旧版文件迁移。适用于使用tinybird-sdk进行数据 ingestion 和查询开发的场景。
安装或配置 tinybird-sdk 在 Python 中定义 datasources, pipes 或 endpoints 创建 Tinybird 客户端或使用数据摄取/查询 运行 tinybird dev/build/deploy 命令 将 legacy .datasource/.pipe 文件迁移到 Python
.agents/skills/tinybird-python-sdk-guidelines/SKILL.md
npx skills add MapleTechLabs/maple --skill tinybird-python-sdk-guidelines -g -y
SKILL.md
Frontmatter
{
    "name": "tinybird-python-sdk-guidelines",
    "description": "Tinybird Python SDK for defining datasources, pipes, and queries in Python. Use when working with tinybird-sdk, Python Tinybird projects, or data ingestion and queries in Python."
}

Tinybird Python SDK Guidelines

Guidance for using the tinybird-sdk package to define Tinybird resources in Python.

When to Apply

  • Installing or configuring tinybird-sdk
  • Defining datasources, pipes, or endpoints in Python
  • Creating Tinybird clients in Python
  • Using data ingestion or queries in Python
  • Running tinybird dev/build/deploy commands for Python projects
  • Migrating from legacy .datasource/.pipe files to Python
  • Defining connections (Kafka, S3, GCS)
  • Creating materialized views, copy pipes, or sink pipes

Rule Files

  • rules/getting-started.md
  • rules/configuration.md
  • rules/defining-datasources.md
  • rules/defining-endpoints.md
  • rules/client.md
  • rules/low-level-api.md
  • rules/cli-commands.md
  • rules/connections.md
  • rules/materialized-views.md
  • rules/copy-sink-pipes.md
  • rules/tokens.md

Quick Reference

  • Install: pip install tinybird-sdk
  • Initialize: tinybird init
  • Dev mode: tinybird dev (uses configured dev_mode, typically branch)
  • Build: tinybird build (builds against configured dev target)
  • Deploy: tinybird deploy (deploys to main/production)
  • Preview in CI: tinybird preview
  • Migrate: tinybird migrate (convert .datasource/.pipe files to Python)
  • Server-side only; never expose tokens in browsers
提供Tinybird TypeScript SDK (@tinybirdco/sdk)的使用指南,支持数据源、管道和查询的类型安全定义。涵盖安装配置、CLI命令、连接管理及类型推断客户端开发,适用于TypeScript项目中的Tinybird资源构建与部署。
使用 @tinybirdco/sdk 进行开发 定义 Tinybird 数据源或管道 创建类型安全的 Tinybird 客户端 运行 tinybird CLI 命令 迁移至 TypeScript 配置
.agents/skills/tinybird-typescript-sdk-guidelines/SKILL.md
npx skills add MapleTechLabs/maple --skill tinybird-typescript-sdk-guidelines -g -y
SKILL.md
Frontmatter
{
    "name": "tinybird-typescript-sdk-guidelines",
    "description": "Tinybird TypeScript SDK for defining datasources, pipes, and queries with full type inference. Use when working with @tinybirdco\/sdk, TypeScript Tinybird projects, or type-safe data ingestion and queries."
}

Tinybird TypeScript SDK Guidelines

Guidance for using the @tinybirdco/sdk package to define Tinybird resources in TypeScript with complete type inference.

When to Apply

  • Installing or configuring @tinybirdco/sdk
  • Defining datasources or pipes in TypeScript
  • Creating typed Tinybird clients
  • Using type-safe ingestion or queries
  • Running tinybird dev/build/deploy commands for TypeScript projects
  • Migrating from legacy .datasource/.pipe files to TypeScript
  • Defining connections (Kafka, S3, GCS)
  • Creating materialized views, copy pipes, or sink pipes

Rule Files

  • rules/getting-started.md
  • rules/configuration.md
  • rules/defining-datasources.md
  • rules/defining-endpoints.md
  • rules/typed-client.md
  • rules/low-level-api.md
  • rules/cli-commands.md
  • rules/connections.md
  • rules/materialized-views.md
  • rules/copy-sink-pipes.md
  • rules/tokens.md

Quick Reference

  • Install: npm install @tinybirdco/sdk
  • Initialize: npx tinybird init
  • Dev mode: tinybird dev (uses configured devMode, typically branch)
  • Build: tinybird build (builds against configured dev target)
  • Deploy: tinybird deploy (deploys to main/production)
  • Preview in CI: tinybird preview
  • Server-side only; never expose tokens in browsers
提供Tinybird开发的最佳实践,涵盖文件格式、SQL规则、优化模式及数据建模。适用于创建或编辑数据源、管道、端点、物化视图等资源,指导项目结构组织与查询性能优化。
创建或更新Tinybird资源文件 编写或优化SQL查询 设计端点Schema和数据模型 重构Tinybird项目文件
.agents/skills/tinybird/SKILL.md
npx skills add MapleTechLabs/maple --skill tinybird -g -y
SKILL.md
Frontmatter
{
    "name": "tinybird",
    "description": "Tinybird file formats, SQL rules, optimization patterns, and best practices for datasources, pipes, endpoints, and materialized views."
}

Tinybird Best Practices

Guidance for Tinybird file formats, SQL rules, optimization patterns, and data modeling. Use this skill when creating or editing Tinybird datafiles.

When to Apply

  • Creating or updating Tinybird resources (.datasource, .pipe, .connection)
  • Writing or optimizing SQL queries
  • Designing endpoint schemas and data models
  • Organizing project structure and data layers
  • Working with materialized views or copy pipes
  • Implementing deduplication patterns
  • Reviewing or refactoring Tinybird project files

Rule Files

  • rules/project-files.md
  • rules/build-deploy.md
  • rules/datasource-files.md
  • rules/pipe-files.md
  • rules/endpoint-files.md
  • rules/materialized-files.md
  • rules/materialized-join-prefilter.md
  • rules/sink-files.md
  • rules/copy-files.md
  • rules/connection-files.md
  • rules/sql.md
  • rules/endpoint-optimization.md
  • rules/tests.md
  • rules/deduplication-patterns.md

Quick Reference

  • Project local files are the source of truth.
  • Build target comes from tinybird.config.json dev_mode (local or branch).
  • tb deploy targets Tinybird Cloud production.
  • Commands like tb sql and tb logs default to local unless --cloud or --branch=<branch-name> is set.
  • SQL is SELECT-only with Tinybird templating rules and strict parameter handling.
  • Use MergeTree by default; AggregatingMergeTree for materialized targets.
  • Filter early, select only needed columns, push complex work later in the pipeline.
通过逐一提问深入探讨用户计划或设计,构建决策树以消除歧义和风险。覆盖目标、架构、安全等维度,推荐最优解,直至达成共识并总结成果。
用户希望压力测试计划 用户要求对设计进行深度审查 用户提及 'grill me'
.context/effect/.agents/skills/grill-me/SKILL.md
npx skills add MapleTechLabs/maple --skill grill-me -g -y
SKILL.md
Frontmatter
{
    "name": "grill-me",
    "description": "Interview the user about a plan or design until reaching shared understanding, resolving each branch of the decision tree. Use when user wants to stress-test a plan, get grilled on their design, or mentions \"grill me\"."
}

Interview me about every aspect of this plan until we reach a shared understanding and a defensible design.

Ask exactly one question at a time, then wait for my answer before asking the next question.

Use each answer to choose the next highest-leverage unresolved question. Maintain an implicit decision tree of resolved decisions, open questions, assumptions, dependencies, risks, and rejected alternatives.

For each question, include:

  • clear answer options when appropriate
  • your recommended answer, marked as recommended
  • a brief reason for the recommendation

Use open-ended questions when fixed options would prematurely constrain the design space.

Challenge vague, inconsistent, risky, or unsupported assumptions. If an answer creates a contradiction or unresolved dependency, ask a follow-up before moving on.

Cover, as relevant:

  • goals and non-goals
  • users and stakeholders
  • constraints
  • alternatives
  • APIs and interfaces
  • data model
  • error handling
  • security
  • observability
  • testing
  • migration and rollout
  • failure modes
  • operational ownership
  • success criteria

If repository facts are needed, inspect the codebase instead of asking the user. Do not ask me to provide information that can be determined locally.

When an available user-input tool such as request_user_input fits the question, use it to ask one short question with a small set of mutually exclusive options. Otherwise, ask in plain text and present clear possible answers as a numbered list when that helps me answer quickly. Include your recommended option and mark it as recommended.

Stop when the major branches of the design tree have been resolved. Then summarize the agreed design, remaining risks, assumptions, rejected alternatives, and next steps.

用于为Effect公共API编写、插入或更新符合jsdocs oxlint规则的JSDoc注释。涵盖修复诊断、准备JSON提取及审查文档,确保描述规范、结构清晰且示例准确。
添加或修复JSDoc注释 解决jsdocs诊断错误 准备JSON提取的文档 审查公共API文档
.context/effect/.agents/skills/jsdocs/SKILL.md
npx skills add MapleTechLabs/maple --skill jsdocs -g -y
SKILL.md
Frontmatter
{
    "name": "jsdocs",
    "description": "Write, insert, or update Effect public API JSDoc so it satisfies the jsdocs oxlint rule. Use when adding or fixing JSDoc comments, resolving jsdocs diagnostics, preparing docs for JSON extraction, or reviewing public API documentation."
}

Use this skill to write well-formed JSDoc for Effect public APIs.

Workflow

When updating public API JSDoc:

  1. Inspect the declaration, implementation, nearby tests, and nearby JSDoc before editing.
  2. Decide whether the task is a single API fix or a module refinement pass.
  3. Rewrite comments into the required documentation shape while preserving correct facts and examples.
  4. For module refinements, complex APIs, or APIs with related alternatives, run the @see and **Gotchas** audits.
  5. Run the narrowest relevant validation.

Required documentation shape

Use a normal multiline JSDoc comment in TypeScript source:

/**
 * Short description as one paragraph.
 *
 * **When to use**
 *
 * Optional practical usage guidance.
 *
 * **Details**
 *
 * Optional details for complex APIs, options, overloads, or behavior.
 *
 * **Gotchas**
 *
 * Optional edge cases, footguns, or surprising behavior.
 *
 * **Example** (Short title)
 *
 * Optional prose explaining the example.
 *
 * ```ts
 * const result = example()
 * ```
 *
 * @category constructors
 * @since 1.0.0
 */

Prose Rules

  • Use sober, practical prose.
  • Write all public JSDoc prose in English.
  • Do not use jargon when a plain word works.
  • Do not be clever.
  • Do not add filler sections.
  • The short description is required and must be exactly one paragraph.
  • Make the short description stand on its own. Do not rely on **When to use** to make the API understandable.
  • For functions and methods, prefer present-tense, action-first prose such as Creates, Returns, Checks, Provides, Represents, Converts, Decodes, or Formats.
  • For technical value exports, use consistent noun forms such as Schema for, Layer that, Service that, Context reference that, or Constructors and matchers for.
  • Avoid leading A or An for canonical technical nouns when the surrounding module uses a standard noun family, for example prefer Schema for ... over A schema for ....
  • Do not describe implementation mechanics when a public concept is clearer. For example, prefer Constructors and matchers for ... over wording that only says an API uses Data.taggedEnum.
  • Avoid generic purity or non-mutation remarks unless they document a real surprise, caveat, or meaningful contrast with a mutating-looking API.
  • Optional sections must appear in this order:
    1. **When to use**
    2. **Details**
    3. **Gotchas**
  • Include an optional section only when it has useful, non-empty content.
  • Prefer prose over bullet lists for single-item **Details**, **When to use**, or **Gotchas** sections. Use bullets only when there are two or more parallel facts, options, cases, or caveats.
  • **When to use** describes the positive use case for the documented API. Do not use it as a routing section for sibling APIs. If neighboring APIs need to be mentioned, put that boundary in @see tag text instead.
  • **When to use** is important when the API has close alternatives, trade-offs, or @see tags. If @see tags are present, inspect the referenced APIs and add **When to use** when it clarifies the documented API's own use case.
  • **When to use** must start with one of these practical guidance forms: Use to, Use when, Use as, or Use with. Avoid bullet lists and vague openers such as Use this... or Useful for....
  • Prefer reader-centered **When to use** wording, especially Use when you ..., when the sentence describes a user's goal. Avoid third-person noun-phrase subjects such as the input is ..., a service needs ..., or values should ... when they would become awkward in generated prompts.
  • A good **When to use** sentence should still read naturally if reused as a user intent prompt, for example after I need ... or I have ....
  • Keep short and **When to use** distinct: the short description says what the API is or does; **When to use** says when to choose it.
  • Add internal @see tags only for semantically useful related public APIs.
  • Write @see tag text as normal prose after the link; no special separator is required. Prefer forms like @see {@link otherApi} for ... when a short explanation helps.
  • Use exactly one blank line between the short description, sections, examples, and tags.
  • Do not use Markdown headings such as # Heading or ad hoc bold headings such as **Notes**; only the standard headings are allowed.
  • Examples must use **Example** (Title), optional prose, and exactly one non-empty ts code fence.
  • Example titles must be unique after trimming and lowercasing.
  • Example titles should be short use-case phrases, not generic labels.
  • Prefer gerund or action-noun titles that read naturally after for, for example Parsing JSON, Creating a scoped runtime, or Comparing structs.
  • Avoid imperative titles such as Parse JSON, vague labels such as Syntax or Basic usage, and title-cased fragments such as String Ordering.
  • Preserve canonical technical capitalization inside the phrase, such as Option, Effect, Schema, DateTime, HashMap, Base64, and JSON.
  • For multiple examples on the same API, make each title describe the distinct use case shown by that example.
  • Prefer examples with stable, deterministic output. Avoid assertions or console.log comments that depend on stack traces, object inspection, Error formatting, concurrency order, timing, randomness, or environment-specific formatting. Examples may assume Node.js console formatting. Direct Set / Map output is acceptable when insertion order is deterministic and the expected output uses Node's format; otherwise demonstrate a stable property instead.
  • Do not use @example.
  • Do not put TypeScript code fences outside **Example** (Title) sections.
  • Inline {@link Symbol} targets must resolve to TypeScript symbols; do not link to URLs with {@link}.
  • Avoid overlinking in prose. Use {@link Symbol} only when navigation to that symbol helps the reader choose or understand the API. For the API being documented, the module's central type, nearby obvious names, or repeated mentions, prefer plain code formatting such as Cause, Effect, or Context.
  • Do not document module-level comments; module JSDoc is ignored by this rule.
  • @internal means the item is ignored; do not rewrite it as public docs.
  • Default exports are ignored by this rule and do not need JSDoc.
  • Do not add unsupported constructs such as enums or empty exports in checked files.
  • For low-level public values, prefer accurate categories such as symbols, type IDs, or prototypes over compensating with verbose descriptions.

Tag rules

When multiple tags are present, keep them in this order:

  1. @deprecated
  2. @default
  3. @see
  4. @category
  5. @since

Tag requirements by declaration kind:

  • Root declarations require @category and stable-semver @since, and must not use @default.
  • Namespaces and declarations inside namespaces require stable-semver @since, may use @category, and must not use @default.
  • Member JSDoc is optional. When present, it follows the same prose and layout rules, may use optional stable-semver @since, may use non-empty @default, and must not use @category.
  • Any declaration may use @deprecated with a non-empty message and repeated non-empty @see tags for semantically useful related public APIs.

Updating existing JSDoc

When fixing or updating existing docs:

  1. Preserve correct facts and examples.
  2. Rewrite the layout into the standard template.
  3. Move usage guidance into **When to use**, behavior details into **Details**, and real caveats into **Gotchas**.
  4. Convert @example tags and loose ts fences into **Example** (Title) sections.
  5. Preserve valid @see, @deprecated, @default, @category, and @since tags.
  6. Remove @see tags that do not point to semantically useful related public APIs.
  7. Replace redundant inline {@link ...} tags with plain code formatting when the link target is already obvious from the current declaration or module.
  8. Remove sections that would be empty.

Module refinement

When asked to refine an existing module:

  1. First scan the module for local documentation patterns, repeated API families, and category conventions.
  2. Keep the change focused on documentation quality unless the user also asked for rule or source changes.
  3. Prefer improving existing comments over rewriting every comment into a new voice.
  4. Preserve examples unless they are wrong, stale, nondeterministic, or fail the required documentation shape.
  5. Apply the @see and **Gotchas** audits across the module before finishing.

See audit

When refining an existing public API module, always do a dedicated @see pass:

  1. Inspect existing @see tags and referenced APIs before keeping, changing, or removing them.
  2. Look for close alternatives in the same module or API family when the documented API is one of several ways to do similar work.
  3. Keep or add @see only when the linked API is semantically useful to understand the documented API.
  4. Good @see targets include sibling APIs, alternatives, inverse operations, lower-level or higher-level variants, complementary operations, and closely returned, consumed, or configured types/values.
  5. Do not use @see for implementation dependencies, broad concepts, external background links, APIs that merely share a word or name, helper APIs used only inside examples, undocumented/private members, or APIs that are only generally compatible.
  6. When @see tags are kept or added, include **When to use** guidance if the documented API's own use case is not obvious from the short description. Keep comparisons with sibling APIs in the @see tag text.

Gotchas audit

When refining an existing public API module, always do a dedicated **Gotchas** pass:

  1. Scan existing prose for caveat language: warnings, exceptions, limitations, preconditions, special cases, or behavior that is easy to misuse.
  2. Inspect the implementation and nearby tests for behavior that is not obvious from the type signature or short description.
  3. Move real caveats from **Details** into **Gotchas** when they describe edge cases, footguns, preconditions, surprising behavior, or important failure modes.
  4. Add **Gotchas** only when the caveat is concrete and useful to a reader choosing or using the API.
  5. If no gotchas are added during a refinement pass, state that a gotchas audit was performed and why no caveats were worth documenting.

Validation

Run the narrowest validation that matches the change:

  • For JSDoc or example changes in a package with generated docs, run pnpm docgen from that package directory.
  • Run pnpm lint because the linter includes the custom rule that checks public API JSDoc.
  • Do not run broad validation for prose-only skill edits.
从源码光标或选中位置附近的 JSDoc 示例中提取 TypeScript 代码,保存至 scratchpad 目录。支持自动处理 Effect 运行器、保留原样及文件命名冲突处理,便于用户快速查看和测试代码片段。
用户要求提取或复制源码中的 JSDoc 示例 用户请求在 scratchpad 中打开或尝试某个源示例
.context/effect/.agents/skills/scratchpad/SKILL.md
npx skills add MapleTechLabs/maple --skill scratchpad -g -y
SKILL.md
Frontmatter
{
    "name": "scratchpad",
    "description": "Extract the JSDoc example nearest the active source selection or cursor into .\/scratchpad as a TypeScript file. Use when the user asks to dump, copy, open, or try a source example in scratchpad."
}

Use this skill to create a scratchpad TypeScript file from the JSDoc **Example** nearest the user's active source cursor or selection.

Workflow

  1. Determine the source path and line:

    • Use the IDE active file and selection/cursor line when present.
    • Use an explicit file and line when the user provides them.
    • If no line or selection is available, ask for it.
  2. Run:

    node .agents/skills/scratchpad/scripts/extract-example.mjs <source-path> <line>
    
  3. If the script exits with code 2 because there is no obvious runner, ask the user whether to preserve the example exactly, name an Effect value to run, or cancel.

    • To preserve exactly, rerun with --mode preserve.
    • To run a specific Effect value, rerun with --runner <identifier>.
  4. Report the created path as a clickable file link. This is the deterministic way to open it in the code pane.

  5. Do not run the scratchpad file unless the user explicitly asks.

Behavior

  • The script chooses the example whose **Example** section contains the line; otherwise it chooses the first following example; otherwise the nearest previous example.

  • Filenames are derived from the source file and example title, for example scratchpad/Schedule-retrying-and-repeating-effects.ts.

  • Existing files are not overwritten. The script appends a numeric suffix.

  • In auto mode, if a top-level program binding exists, the script appends:

    Effect.runPromise(program).then(console.log, console.error)
    
  • If the example already contains an Effect runner, the script preserves it.

用于扫描 React 代码库的安全性、性能、正确性和架构问题,输出健康评分。适用于提交前检查、修复回归或整体代码清理,支持差异扫描与全量分析。
完成功能开发后 修复 Bug 后 提交 React 代码前 用户希望提升代码质量时 进行代码库清理时
.factory/skills/react-doctor/SKILL.md
npx skills add MapleTechLabs/maple --skill react-doctor -g -y
SKILL.md
Frontmatter
{
    "name": "react-doctor",
    "version": "1.0.0",
    "description": "Use when finishing a feature, fixing a bug, before committing React code, or when the user wants to improve code quality or clean up a codebase. Checks for score regression. Covers lint, dead code, accessibility, bundle size, architecture diagnostics."
}

React Doctor

Scans React codebases for security, performance, correctness, and architecture issues. Outputs a 0–100 health score.

After making React code changes:

Run npx -y react-doctor@latest . --verbose --diff and check the score did not regress.

If the score dropped, fix the regressions before committing.

For general cleanup or code improvement:

Run npx -y react-doctor@latest . --verbose (without --diff) to scan the full codebase. Fix issues by severity — errors first, then warnings.

Command

npx -y react-doctor@latest . --verbose --diff
Flag Purpose
. Scan current directory
--verbose Show affected files and line numbers per rule
--diff Only scan changed files vs base branch
--score Output only the numeric score
审计已集成 OpenTelemetry 的项目,对照 Maple 规范检查并修复数据缺失或错误。适用于检查遥测配置、审查 OTel 设置及修复服务映射问题。
audit my instrumentation check my telemetry review my OTel setup why is my service map missing edges is my Maple instrumentation correct
skills/maple-audit/SKILL.md
npx skills add MapleTechLabs/maple --skill maple-audit -g -y
SKILL.md
Frontmatter
{
    "name": "maple-audit",
    "description": "Audit an already-instrumented project against Maple's OpenTelemetry conventions, report gaps per service, and fix them. Triggers on requests like 'audit my instrumentation', 'check my telemetry', 'review my OTel setup', 'why is my service map missing edges', 'is my Maple instrumentation correct'."
}

Maple instrumentation audit

Review an existing OpenTelemetry setup against what Maple actually consumes, produce a findings report, then fix the gaps. This is the counterpart to maple-onboard: that skill installs telemetry from scratch; this one assumes instrumentation exists and asks whether it's right.

Before auditing, read checks.md in this skill's directory — it is the full check registry (check ids, severities, what each gap breaks in Maple). Every finding you report must cite a check id from there; never invent attribute keys or conventions that aren't in checks.md or the upstream OTel semconv.

For how to fix what you find, use the companion skills — don't improvise recipes:

  • maple-onboarding-style for general OTel taste, VCS attributes, log bridges, metrics, LLM conventions.
  • maple-nextjs-style, maple-nodejs-style, maple-python-style, maple-effect-style, maple-go-style, maple-rust-style, maple-java-style, maple-csharp-style, maple-kotlin-style for stack-specific bootstrap shapes.

Severity

Severity Meaning
critical Breaks a Maple feature outright or is a data risk: missing service.name, hand-stamped status strings (error analytics read zero rows), logs without trace correlation, missing peer.service/db.system on client spans, PII in attributes.
warn Feature works degraded or work is invisible: missing environment/VCS resource attrs, deprecated semconv keys, double-emission, high-cardinality labels or span names, outbound calls left Internal, untraced operations (missing auto-instrumentation for a used framework/driver, business operations and background jobs with no spans).
info Signal-quality improvements: trace-context propagation across async boundaries, span noise, missing metrics, naming style, missing gen_ai.* on LLM calls.

Step 1 — Enumerate services and classify

Map every app/service in the repo exactly as maple-onboard Step 1 does (workspace manifests, apps/*, services/*, mobile, edge/serverless functions — skip pure type/config packages). For each, find its OTel bootstrap and classify:

  • instrumented — has an SDK bootstrap; gets the full audit below.
  • partial — some signals wired (e.g. traces but no log bridge); audit what exists, flag what's absent.
  • not instrumented — no OTel at all. That is a single critical finding pointing the user at maple-onboard; don't run per-check audits against nothing.

Show the user the service list with classifications before auditing, so they can correct it.

Step 2 — Static code audit

Per instrumented service, work through the categories in checks.md:

  1. Bootstrap & resource (RES-*) — read the SDK init: is service.name explicit, are service.version, deployment.environment.name, vcs.repository.url.full, vcs.ref.head.revision on the resource? Any invented keys?
  2. Status & kind (STAT-*) — grep for status handling: SDK status enum vs hand-stamped strings; failure paths recording exceptions; outbound calls created as Client/Producer spans.
  3. Trace coverage (SPAN-*) — what should be traced but isn't. Compare the dependency manifest against the instrumentation registered in the bootstrap (a DB driver or queue client with no instrumentation package means that whole category emits nothing). Then read the code for critical business operations, background jobs, and queue consumers with no span in their call path — when this service misbehaves, what would an operator need to see that currently emits nothing?
  4. Service-map attribution (MAP-*) — for every outbound dependency in the code (HTTP clients to internal services, DB drivers, queues), check the corresponding spans set peer.service (and db.system for DBs) consistently.
  5. Attribute keys (REN-*, NAME-*) — grep attribute call sites (setAttribute, set_attribute, setAttributes, attribute map literals) for the deprecated/camelCase keys in the REN table and for double-emission of old+new keys.
  6. Logs (LOG-*) — is an OTLP log bridge wired under the logger the app actually uses? Do in-span logs carry trace context? Structured fields?
  7. Metrics (MET-*) — instruments at module scope, low-cardinality labels, business coverage.
  8. PII (PII-01) — scan attribute values for emails, tokens, auth headers, full bodies.
  9. LLM (LLM-*) — only if the project calls LLM providers.

Collect findings as you go: service, check id, severity, evidence (file:line), and which Maple feature it affects (from checks.md).

Step 3 — Live cross-check (only when the Maple MCP is connected)

If mcp__maple__* tools are available, verify the static findings against what's actually arriving. If they're not available, say so in one line of the report and move on — the static audit stands alone. Don't ask the user to install the MCP mid-audit.

  • get_instrumentation_recommendations — the authoritative list of deprecated/double-emitted/non-conforming attribute keys, reconciled against the org's live span data, plus resource-attribute coverage gaps. Where it disagrees with your static REN/NAME findings, the live data wins (the code you read may not be deployed, or other emitters exist).
  • list_services — confirm every service you enumerated actually reports. A service that's instrumented in code but absent here is a critical export problem (bootstrap not loaded, key wrong, exporter blocked).
  • explore_attributes (resource scope) — confirm environment/VCS/version resource attrs arrive in practice.
  • service_map — a service whose code makes outbound calls but shows no outgoing edges confirms MAP-01/02/03 findings from the data side.
  • get_service_top_operations (or search_traces per service) — confirm trace-coverage findings: a critical operation you flagged under SPAN-02/03 that never appears as a span name in live data is confirmed untraced; a DB-heavy service whose traces contain no DB client spans confirms SPAN-01.

Annotate existing findings with live evidence rather than duplicating them.

Step 4 — Findings report

Present the report before making any edit. Format:

## Instrumentation audit — <repo>

### <service-name>  (instrumented | partial | not instrumented)
| Severity | Check  | Finding                                   | Evidence            | Affects                  |
| critical | MAP-01 | Client spans to billing lack peer.service | src/billing.ts:88   | service map edge missing |
| warn     | REN-02 | emits http.status_code                    | live: 12.4k spans/24h | rename to http.response.status_code |
...

### Summary
N critical · N warn · N info across M services. Recommended fix order: …

Every row carries a check id and concrete evidence (file:line for static, counts/tool output for live). If the MCP wasn't connected, note "live cross-check skipped — Maple MCP not connected" here.

Step 5 — Apply fixes

After the report, fix in severity order (criticalwarninfo), following the relevant style skill for each edit. Rules:

  • Pure key renames (REN rows of kind rename) have two valid routes: rename at the SDK (preferred — fixes it at the source), or accept the matching Recommendation Issue in Maple Settings → Ingestion, which creates an ingest attribute mapping. Tell the user both; default to the SDK rename when you're already editing that file.
  • Double-emission and naming issues can only be fixed at the SDK — an ingest mapping can't merge keys.
  • Idempotent edits; preserve the project's package manager, logger, and formatting.
  • Never remove an existing observability vendor (Sentry, Datadog, …) — coexistence is the default.
  • Never put PII in attributes; when fixing PII-01, replace the value with an ID or drop the attribute, don't hash-and-keep.
  • Never commit, push, or open PRs.

If the user only wants the review, stop after Step 4 — the report is a complete deliverable.

Step 6 — Re-verify

For every service you touched, run the same smoke as maple-onboard Step 4: the service's own dev/build command starts cleanly, and OTLP POSTs from the running process return 2xx. A fix that breaks startup is a regression — fix or revert it, don't paper over it.

Then close out: summarize what changed per service, and tell the user that after deploying, re-running get_instrumentation_recommendations (or checking Settings → Ingestion in Maple) will show issues auto-resolving as the offending keys stop arriving — reconciliation is automatic.

Hard rules

  • No edits before the findings report has been shown (Step 4).
  • Every finding cites a check id from checks.md; no invented attribute keys or conventions.
  • The static audit must work standalone — the Maple MCP is an enhancer, never a requirement.
  • Never modify files outside the project root; never commit or push.
  • Fix recipes come from the maple-*-style skills, not from memory.
为Maple平台提供.NET/C# OpenTelemetry集成方案。通过官方包配置ASP.NET Core的Trace、Metric和Log,使用OTLP HTTP导出器发送数据至Maple,并支持自定义业务Span及资源属性注入。
需要在 .NET / C# 项目中集成 OpenTelemetry 需要将 ASP.NET Core 应用的遥测数据(Trace/Metric/Log)发送至 Maple 平台 需要配置 OTLP HTTP 导出器和 ActivitySource
skills/maple-csharp-style/SKILL.md
npx skills add MapleTechLabs/maple --skill maple-csharp-style -g -y
SKILL.md
Frontmatter
{
    "name": "maple-csharp-style",
    "description": ".NET \/ C# OpenTelemetry style for Maple: OpenTelemetry.Extensions.Hosting + OTLP HTTP exporter, ActivitySource for spans, ILogger bridging via OpenTelemetryLoggerProvider, inline endpoint + ingest key."
}

Maple .NET / C# style

Use the official OpenTelemetry packages and wire them through the .NET hosting model — IServiceCollection for traces / metrics, ILoggingBuilder for logs.

Install

dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
dotnet add package OpenTelemetry.Instrumentation.Http

Bootstrap (ASP.NET Core)

Inline the endpoint and ingest key — they're a project-scoped, write-only token (Sentry-DSN-shaped). No env-var indirection.

using OpenTelemetry;
using OpenTelemetry.Exporter;
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

const string MapleEndpoint = "https://ingest.maple.dev";
const string MapleKey = "MAPLE_TEST"; // set by maple-onboard skill on pairing

var builder = WebApplication.CreateBuilder(args);

void ConfigureOtlp(OtlpExporterOptions options, string path)
{
    options.Endpoint = new Uri($"{MapleEndpoint}/v1/{path}");
    options.Protocol = OtlpExportProtocol.HttpProtobuf;
    options.Headers = $"authorization=Bearer {MapleKey}";
}

var resource = ResourceBuilder.CreateDefault()
    .AddService("orders-api")
    .AddAttributes(new Dictionary<string, object>
    {
        ["deployment.environment.name"] = builder.Environment.EnvironmentName,
        ["vcs.repository.url.full"] = "https://github.com/acme/orders-api",
        ["vcs.ref.head.revision"] = Environment.GetEnvironmentVariable("GITHUB_SHA") ?? "",
    });

builder.Services.AddOpenTelemetry()
    .ConfigureResource(r => r.AddService("orders-api"))
    .WithTracing(tracing => tracing
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddOtlpExporter(o => ConfigureOtlp(o, "traces")))
    .WithMetrics(metrics => metrics
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddOtlpExporter(o => ConfigureOtlp(o, "metrics")));

builder.Logging.AddOpenTelemetry(logging =>
{
    logging.SetResourceBuilder(resource);
    logging.IncludeFormattedMessage = true;
    logging.IncludeScopes = true;
    logging.AddOtlpExporter(o => ConfigureOtlp(o, "logs"));
});

var app = builder.Build();

Bounded business spans

Use a static ActivitySource per module. ASP.NET / HttpClient auto-instrumentation handles the obvious spans; reach for ActivitySource.StartActivity for business operations.

public static class Tracing
{
    public static readonly ActivitySource Source = new("orders.api");
}

public sealed class OrderService
{
    public async Task SubmitAsync(string orderId, string tenantId)
    {
        using var activity = Tracing.Source.StartActivity("order.submit");
        activity?.SetTag("tenant.id", tenantId);
        activity?.SetTag("order.id", orderId);
        try
        {
            await ChargeAsync(orderId);
        }
        catch (Exception ex)
        {
            activity?.RecordException(ex);
            activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
            throw;
        }
    }
}

Activity.RecordException(Exception) lives in OpenTelemetry.Traceusing OpenTelemetry.Trace; for the extension.

Logs

ILogger<T> calls already carry the active Activity's TraceId / SpanId once AddOpenTelemetry is wired into builder.Logging. Don't replace the user's existing logger; add OTLP underneath.

Coexistence

If the project already exports to Application Insights, Datadog, or Honeycomb, keep those. AddOpenTelemetry().WithTracing(...) accepts multiple exporters — chain .AddOtlpExporter(...) for Maple alongside the existing one.

提供 @maple-dev/effect-sdk,用于在 Effect 应用中集成 Maple OpenTelemetry。支持 Node.js、Cloudflare Workers 和浏览器环境,自动处理导出、批处理及资源属性,实现链路追踪与日志关联。
Effect 应用需要配置 OpenTelemetry 追踪 需要在 Server/Worker/Browser 中初始化 Maple 层 使用 Effect.withSpan 或 Effect.log 进行可观测性操作
skills/maple-effect-style/SKILL.md
npx skills add MapleTechLabs/maple --skill maple-effect-style -g -y
SKILL.md
Frontmatter
{
    "name": "maple-effect-style",
    "description": "Effect-TS OpenTelemetry style for Maple via @maple-dev\/effect-sdk: Maple.layer() bootstrap, Effect.withSpan \/ Effect.annotateCurrentSpan call sites, Effect.log for trace-correlated logging, server \/ browser \/ Cloudflare entry points."
}

Maple Effect style

For Effect apps, use @maple-dev/effect-sdk — Maple's first-class Effect SDK. It wraps Effect's built-in Otlp.layerJson exporter and handles batching, shutdown, and resource attributes for you.

Install

npm install @maple-dev/effect-sdk effect

For Effect 3, use @maple-dev/effect-sdk@effect-v3 instead. Same API, different peer ranges.

Bootstrap

Pick the entry point per runtime — they have different lifecycle requirements:

  • Server (Node.js, Bun, Deno): background-export fiber, env-var auto-detection, graceful shutdown.
  • Browser: explicit config (no env vars), browser metadata baked in.
  • Cloudflare Workers: manual flush() in ctx.waitUntil, lazy env resolution, in-isolate buffering.

Server

import { Maple } from "@maple-dev/effect-sdk"
import { Effect, Layer } from "effect"

const TracerLive = Maple.layer({
	serviceName: "orders-api",
	endpoint: "https://ingest.maple.dev",
	ingestKey: "MAPLE_TEST", // set by maple-onboard skill on pairing
	attributes: {
		"vcs.repository.url.full": "https://github.com/acme/orders-api",
	},
})

const program = Effect.gen(function* () {
	yield* Effect.log("Order received")
}).pipe(Effect.withSpan("order.submit"))

Effect.runPromise(program.pipe(Effect.provide(TracerLive)))

The default import resolves to the server build under Node.js. Import @maple-dev/effect-sdk/server explicitly when needed.

If endpoint is omitted, the server layer auto-detects it from MAPLE_ENDPOINT (falling back to OTEL_EXPORTER_OTLP_ENDPOINT). When MAPLE_ENDPOINT is unset, the layer is a no-op — local dev runs without exporting. Inline the endpoint when you want telemetry to flow regardless of env (matches the rest of the maple-onboard inline-key pattern).

The server layer also auto-fills vcs.ref.head.revision from COMMIT_SHA / RAILWAY_GIT_COMMIT_SHA / VERCEL_GIT_COMMIT_SHA / CF_PAGES_COMMIT_SHA / RENDER_GIT_COMMIT (first match wins). You should still set vcs.repository.url.full in attributes since no env var carries it.

Cloudflare Workers

import { Maple } from "@maple-dev/effect-sdk/cloudflare"
import { Effect } from "effect"

export default {
	async fetch(req: Request, env: Env, ctx: ExecutionContext) {
		const TracerLive = Maple.layer({
			serviceName: "orders-edge",
			endpoint: "https://ingest.maple.dev",
			ingestKey: "MAPLE_TEST",
		})

		const program = Effect.gen(function* () {
			yield* Effect.log("edge request")
			return new Response("ok")
		}).pipe(Effect.withSpan("edge.handle"))

		const response = await Effect.runPromise(program.pipe(Effect.provide(TracerLive)))
		ctx.waitUntil(Maple.flush())
		return response
	},
}

The Cloudflare entry point requires ctx.waitUntil(Maple.flush()) so telemetry survives the isolate exit. Forgetting this is the most common reason traces don't show up from Workers.

Browser

import { Maple } from "@maple-dev/effect-sdk/browser"

const TracerLive = Maple.layer({
	serviceName: "web-client",
	endpoint: "https://ingest.maple.dev",
	ingestKey: "MAPLE_TEST",
})

No env-var fallback in the browser entry point — config is always explicit.

Custom spans

Use Effect.withSpan to trace operations and Effect.annotateCurrentSpan for attributes — don't reach for the raw @opentelemetry/api tracer when an Effect-native primitive is available.

const processOrder = (orderId: string) =>
	Effect.gen(function* () {
		yield* Effect.annotateCurrentSpan("order.id", orderId)
		yield* Effect.annotateCurrentSpan("peer.service", "payment-api")
		const result = yield* chargePayment(orderId)
		return result
	}).pipe(Effect.withSpan("order.process"))

Setting peer.service on outgoing calls makes them visible on Maple's service map.

Effect.fail and uncaught defects are recorded as exceptions and set the span status to ERROR automatically — you don't need to wrap with try / catch / finally.

@maple/otel-helpers withSpan is for non-Effect TypeScript code; in Effect code prefer the Effect-native span primitives.

Logs

Effect.log automatically includes trace context when called inside a span — no additional setup needed:

const program = Effect.gen(function* () {
	yield* Effect.log("Processing started")
	yield* doWork()
	yield* Effect.log("Processing complete")
}).pipe(Effect.withSpan("process"))

Logs emitted inside spans are correlated with the active trace in the Maple dashboard.

Coexistence

If the project already uses @effect/opentelemetry or Otlp.layerJson with a custom exporter (e.g. for Honeycomb, Datadog), keep it. Maple.layer() can compose alongside via Layer.merge:

const TracerLive = Layer.merge(
	Maple.layer({ serviceName: "api", endpoint: "https://ingest.maple.dev", ingestKey: "MAPLE_TEST" }),
	HoneycombLayer,
)

Both vendors receive the same spans.

为Maple平台提供Go语言的OpenTelemetry标准化配置。集成官方OTel SDK及HTTP导出器,支持Trace、Log、Metric全链路数据采集。通过硬编码端点与密钥实现项目级写入认证,自动注入服务名、环境及VCS资源属性,并在进程启动时初始化、信号退出时优雅关闭。
需要在Go项目中集成Maple遥测数据上报 使用go.opentelemetry.io/otel SDK进行监控日志追踪初始化
skills/maple-go-style/SKILL.md
npx skills add MapleTechLabs/maple --skill maple-go-style -g -y
SKILL.md
Frontmatter
{
    "name": "maple-go-style",
    "description": "Go OpenTelemetry style for Maple: go.opentelemetry.io\/otel SDK with otlptracehttp \/ otlploghttp \/ otlpmetrichttp exporters, inline endpoint + ingest key, semconv resource attributes including vcs.repository.url.full."
}

Maple Go style

Use the official go.opentelemetry.io/otel SDK with the HTTP exporters. Initialize once at process start and shut down on signal.

Install

go get \
  go.opentelemetry.io/otel \
  go.opentelemetry.io/otel/sdk \
  go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp \
  go.opentelemetry.io/otel/exporters/otlp/otlplogs/otlploghttp \
  go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp \
  go.opentelemetry.io/otel/log \
  go.opentelemetry.io/otel/sdk/log

Bootstrap

Inline the endpoint and ingest key — they're a project-scoped, write-only token (Sentry-DSN-shaped). No env-var indirection.

package telemetry

import (
	"context"
	"os"

	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/exporters/otlp/otlplogs/otlploghttp"
	"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
	"go.opentelemetry.io/otel/log/global"
	sdklog "go.opentelemetry.io/otel/sdk/log"
	"go.opentelemetry.io/otel/sdk/metric"
	"go.opentelemetry.io/otel/sdk/resource"
	"go.opentelemetry.io/otel/sdk/trace"
	semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
)

const (
	mapleEndpoint = "ingest.maple.dev"
	mapleKey      = "MAPLE_TEST" // set by maple-onboard skill on pairing
)

func Init(ctx context.Context) (shutdown func(context.Context) error, err error) {
	headers := map[string]string{"authorization": "Bearer " + mapleKey}

	res, err := resource.New(ctx,
		resource.WithAttributes(
			semconv.ServiceName("orders-api"),
			semconv.DeploymentEnvironment(envOr("DEPLOYMENT_ENV", "development")),
			attribute.String("vcs.repository.url.full", "https://github.com/acme/orders-api"),
			attribute.String("vcs.ref.head.revision", envOr("GITHUB_SHA", envOr("GIT_COMMIT", ""))),
		),
	)
	if err != nil {
		return nil, err
	}

	traceExp, err := otlptracehttp.New(ctx,
		otlptracehttp.WithEndpoint(mapleEndpoint),
		otlptracehttp.WithHeaders(headers),
	)
	if err != nil {
		return nil, err
	}
	tp := trace.NewTracerProvider(trace.WithBatcher(traceExp), trace.WithResource(res))
	otel.SetTracerProvider(tp)

	logExp, err := otlploghttp.New(ctx,
		otlploghttp.WithEndpoint(mapleEndpoint),
		otlploghttp.WithHeaders(headers),
	)
	if err != nil {
		return nil, err
	}
	lp := sdklog.NewLoggerProvider(
		sdklog.WithProcessor(sdklog.NewBatchProcessor(logExp)),
		sdklog.WithResource(res),
	)
	global.SetLoggerProvider(lp)

	metricExp, err := otlpmetrichttp.New(ctx,
		otlpmetrichttp.WithEndpoint(mapleEndpoint),
		otlpmetrichttp.WithHeaders(headers),
	)
	if err != nil {
		return nil, err
	}
	mp := metric.NewMeterProvider(
		metric.WithReader(metric.NewPeriodicReader(metricExp)),
		metric.WithResource(res),
	)
	otel.SetMeterProvider(mp)

	return func(ctx context.Context) error {
		_ = tp.Shutdown(ctx)
		_ = lp.Shutdown(ctx)
		return mp.Shutdown(ctx)
	}, nil
}

func envOr(key, fallback string) string {
	if v := os.Getenv(key); v != "" {
		return v
	}
	return fallback
}

Wire from main:

func main() {
	ctx := context.Background()
	shutdown, err := telemetry.Init(ctx)
	if err != nil {
		log.Fatal(err)
	}
	defer shutdown(ctx)

	// app start
}

Bounded business spans

Acquire the tracer at package scope, start spans where auto-instrumentation is blind, set the status on error, end the span via defer.

var tracer = otel.Tracer("orders.api")

func SubmitOrder(ctx context.Context, orderID string) (err error) {
	ctx, span := tracer.Start(ctx, "order.submit")
	defer func() {
		if err != nil {
			span.RecordError(err)
			span.SetStatus(codes.Error, err.Error())
		}
		span.End()
	}()

	span.SetAttributes(attribute.String("order.id", orderID))
	return chargeOrder(ctx, orderID)
}

Auto-instrumentation

Go does not have framework-level auto-discovery. Add the official contrib packages for the libraries the app actually uses (otelhttp, otelgrpc, otelsql, otelpgx, otelmux, otelfiber, etc.). Don't add packages the app doesn't import.

Coexistence

If the project already exports to Honeycomb / Datadog / Tempo, install Maple's exporter alongside via trace.WithBatcher(traceExp) for each backend. Don't strip the existing exporter unless the user asks.

提供 Maple Java 可观测性集成方案。支持零代码 Java Agent 自动注入,或手动 SDK 配置 OTLP HTTP 导出。涵盖 Spring、JDBC 等框架自动插桩,以及 Logback 日志桥接,通过内联端点和密钥实现 traces、metrics 和 logs 上报。
需要为 Java 应用集成 OpenTelemetry 监控 询问如何配置 Maple 的 Java Agent 或手动 SDK 寻求 Java 应用的链路追踪和日志采集方案
skills/maple-java-style/SKILL.md
npx skills add MapleTechLabs/maple --skill maple-java-style -g -y
SKILL.md
Frontmatter
{
    "name": "maple-java-style",
    "description": "Java OpenTelemetry style for Maple: zero-code Java agent or manual SDK with OTLP HTTP exporters, inline endpoint + ingest key, semconv resource attributes, OTLP-bridged Logback \/ SLF4J logs."
}

Maple Java style

The fastest path is the OpenTelemetry Java Agent — it auto-instruments the JVM with zero code changes.

Zero-code: Java agent

curl -sLO https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar

Inline the endpoint and ingest key as JVM args (these are the agent's only configuration surface — they map straight onto the inline-key model):

java \
  -javaagent:./opentelemetry-javaagent.jar \
  -Dotel.service.name=orders-api \
  -Dotel.exporter.otlp.protocol=http/protobuf \
  -Dotel.exporter.otlp.endpoint=https://ingest.maple.dev \
  -Dotel.exporter.otlp.headers="authorization=Bearer MAPLE_TEST" \
  -Dotel.resource.attributes="vcs.repository.url.full=https://github.com/acme/orders-api,vcs.ref.head.revision=${GITHUB_SHA:-}" \
  -jar build/libs/app.jar

Replace MAPLE_TEST with the project's real Maple ingest key once it's available. Keep the args inline (in Procfile / Dockerfile / systemd unit / application.yml) — don't move them behind unset env vars.

The agent auto-instruments Spring (Boot, MVC, WebFlux), Servlet containers, Apache HttpClient, OkHttp, JDBC, R2DBC, Hibernate, Kafka, gRPC, AWS SDK, and many more.

Manual SDK (when the agent isn't an option)

For cases where the agent can't run (GraalVM native image, embedded JVM, sealed module path), use the SDK directly:

<dependency>
  <groupId>io.opentelemetry</groupId>
  <artifactId>opentelemetry-api</artifactId>
</dependency>
<dependency>
  <groupId>io.opentelemetry</groupId>
  <artifactId>opentelemetry-sdk</artifactId>
</dependency>
<dependency>
  <groupId>io.opentelemetry</groupId>
  <artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
<dependency>
  <groupId>io.opentelemetry.instrumentation</groupId>
  <artifactId>opentelemetry-logback-appender-1.0</artifactId>
</dependency>
public final class Telemetry {
    private static final String MAPLE_ENDPOINT = "https://ingest.maple.dev";
    private static final String MAPLE_KEY = "MAPLE_TEST"; // set by maple-onboard skill on pairing

    public static OpenTelemetrySdk init() {
        var headers = Map.of("authorization", "Bearer " + MAPLE_KEY);
        var resource = Resource.getDefault().merge(Resource.create(Attributes.builder()
            .put(ServiceAttributes.SERVICE_NAME, "orders-api")
            .put(DeploymentIncubatingAttributes.DEPLOYMENT_ENVIRONMENT_NAME,
                System.getenv().getOrDefault("DEPLOYMENT_ENV", "development"))
            .put("vcs.repository.url.full", "https://github.com/acme/orders-api")
            .put("vcs.ref.head.revision", System.getenv().getOrDefault("GITHUB_SHA", ""))
            .build()));

        var spanExporter = OtlpHttpSpanExporter.builder()
            .setEndpoint(MAPLE_ENDPOINT + "/v1/traces")
            .setHeaders(() -> headers)
            .build();
        // … same shape for OtlpHttpLogRecordExporter and OtlpHttpMetricExporter

        return OpenTelemetrySdk.builder()
            .setTracerProvider(SdkTracerProvider.builder()
                .addSpanProcessor(BatchSpanProcessor.builder(spanExporter).build())
                .setResource(resource)
                .build())
            .buildAndRegisterGlobal();
    }
}

Logs

Bridge the existing Logback / SLF4J / Log4j2 setup through OTLP — don't replace it. With the Java agent, log appenders are auto-bridged. With the manual SDK, add the opentelemetry-logback-appender-1.0 (or the Log4j 2 equivalent) and configure it in logback.xml so existing logger calls carry trace_id / span_id and reach Maple.

Bounded business spans

Acquire the tracer at class scope; wrap operations the agent's auto-instrumentation can't see.

private static final Tracer TRACER = GlobalOpenTelemetry.getTracer("orders.api");

public Order submit(String orderId, String tenantId) {
    var span = TRACER.spanBuilder("order.submit")
        .setAttribute("tenant.id", tenantId)
        .setAttribute("order.id", orderId)
        .startSpan();
    try (var scope = span.makeCurrent()) {
        return charge(orderId);
    } catch (Exception e) {
        span.recordException(e);
        span.setStatus(StatusCode.ERROR, e.getMessage());
        throw e;
    } finally {
        span.end();
    }
}

Coexistence

If the project already runs Datadog / New Relic / Honeycomb agents, leave them in place. The OpenTelemetry Java Agent coexists with most APMs (test the combination once before shipping). Don't strip an incumbent agent unless the user asks.

为Maple平台提供Kotlin应用的OpenTelemetry集成方案,支持零代码Java Agent自动采集Spring Boot/Ktor指标,或手动SDK配置OTLP导出,涵盖追踪、日志与资源属性设置。
需要为Kotlin应用配置分布式追踪 集成Spring Boot或Ktor框架的监控 配置OpenTelemetry OTLP HTTP导出器
skills/maple-kotlin-style/SKILL.md
npx skills add MapleTechLabs/maple --skill maple-kotlin-style -g -y
SKILL.md
Frontmatter
{
    "name": "maple-kotlin-style",
    "description": "Kotlin (Ktor, Spring Boot) OpenTelemetry style for Maple: zero-code Java agent or manual SDK with OTLP HTTP exporters, inline endpoint + ingest key, semconv resource attributes, OTLP-bridged logs."
}

Maple Kotlin style

Kotlin runs on the JVM, so the same OpenTelemetry Java agent and SDK apply. Prefer the agent for Spring Boot / Ktor servers; fall back to the manual SDK only for native-image or sealed-module builds.

Zero-code: Java agent (recommended)

curl -sLO https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar

java \
  -javaagent:./opentelemetry-javaagent.jar \
  -Dotel.service.name=orders-api \
  -Dotel.exporter.otlp.protocol=http/protobuf \
  -Dotel.exporter.otlp.endpoint=https://ingest.maple.dev \
  -Dotel.exporter.otlp.headers="authorization=Bearer MAPLE_TEST" \
  -Dotel.resource.attributes="vcs.repository.url.full=https://github.com/acme/orders-api,vcs.ref.head.revision=${GITHUB_SHA:-}" \
  -jar build/libs/app.jar

Replace MAPLE_TEST with the project's real Maple ingest key once available. Keep these flags inline (in Procfile / Dockerfile / application.yml) — don't move them behind unset env vars.

The agent auto-instruments Ktor, Spring Boot (MVC, WebFlux), Coroutines, Exposed, R2DBC, Kafka, gRPC, OkHttp, AWS SDK, and many more.

Manual SDK (Ktor, no agent)

val MAPLE_ENDPOINT = "https://ingest.maple.dev"
val MAPLE_KEY = "MAPLE_TEST" // set by maple-onboard skill on pairing

fun initTelemetry(): OpenTelemetrySdk {
    val headers = mapOf("authorization" to "Bearer $MAPLE_KEY")
    val resource = Resource.getDefault().merge(Resource.create(
        Attributes.builder()
            .put(ServiceAttributes.SERVICE_NAME, "orders-api")
            .put(DeploymentIncubatingAttributes.DEPLOYMENT_ENVIRONMENT_NAME,
                System.getenv("DEPLOYMENT_ENV") ?: "development")
            .put("vcs.repository.url.full", "https://github.com/acme/orders-api")
            .put("vcs.ref.head.revision", System.getenv("GITHUB_SHA") ?: "")
            .build()))

    val spanExporter = OtlpHttpSpanExporter.builder()
        .setEndpoint("$MAPLE_ENDPOINT/v1/traces")
        .setHeaders { headers }
        .build()

    return OpenTelemetrySdk.builder()
        .setTracerProvider(SdkTracerProvider.builder()
            .addSpanProcessor(BatchSpanProcessor.builder(spanExporter).build())
            .setResource(resource)
            .build())
        .buildAndRegisterGlobal()
}

Add the equivalent log + metric exporters in the same builder.

Bounded business spans

private val tracer = GlobalOpenTelemetry.getTracer("orders.api")

suspend fun submitOrder(orderId: String, tenantId: String) {
    val span = tracer.spanBuilder("order.submit")
        .setAttribute("tenant.id", tenantId)
        .setAttribute("order.id", orderId)
        .startSpan()
    try {
        span.makeCurrent().use {
            chargeOrder(orderId)
        }
    } catch (e: Exception) {
        span.recordException(e)
        span.setStatus(StatusCode.ERROR, e.message ?: "")
        throw e
    } finally {
        span.end()
    }
}

For coroutines, use the kotlinx-coroutines-extension (opentelemetry-kotlin-extension) so context propagates across withContext boundaries.

Logs

Bridge whatever the project uses (Logback, SLF4J, Log4j2). With the agent, log appenders are auto-bridged. With the manual SDK, add opentelemetry-logback-appender-1.0 (or Log4j 2 equivalent) and configure it in logback.xml so existing logger calls carry trace_id / span_id and reach Maple. Don't replace the user's existing logger.

Coexistence

If the project runs Datadog / New Relic / Honeycomb, leave them in place. Test the combination once before shipping.

提供Next.js/Vercel环境下Maple OpenTelemetry的标准配置方案。涵盖通过instrumentation.ts初始化、集成Anthropic自动插桩及原生API手动埋点,强调使用框架入口而非自定义NodeSDK。
需要在Next.js应用中配置OpenTelemetry 集成Maple遥测数据到Vercel环境 为Route Handlers添加自定义Span和Metrics
skills/maple-nextjs-style/SKILL.md
npx skills add MapleTechLabs/maple --skill maple-nextjs-style -g -y
SKILL.md
Frontmatter
{
    "name": "maple-nextjs-style",
    "description": "Next.js \/ Vercel OpenTelemetry style for Maple: instrumentation.ts, @vercel\/otel bootstrap, native @opentelemetry\/api call sites, inline endpoint + ingest key, no raw NodeSDK replacement."
}

Maple Next.js style

For Next.js apps, prefer the framework entrypoint.

// instrumentation.ts
import { registerOTel } from "@vercel/otel"

const MAPLE_ENDPOINT = "https://ingest.maple.dev"
const MAPLE_KEY = "MAPLE_TEST" // set by maple-onboard skill on pairing

export function register() {
	registerOTel({
		serviceName: "my-next-app",
		attributes: {
			"deployment.environment.name": process.env.VERCEL_ENV ?? "development",
			"vcs.repository.url.full": "https://github.com/acme/my-next-app",
			"vcs.ref.head.revision": process.env.VERCEL_GIT_COMMIT_SHA,
		},
		traceExporter: {
			url: `${MAPLE_ENDPOINT}/v1/traces`,
			headers: { authorization: `Bearer ${MAPLE_KEY}` },
		},
	})
}

Do not replace this with a custom NodeSDK bootstrap unless the repo is not a normal Next/Vercel app or already has a custom provider that must be extended.

For JavaScript/TypeScript LLM providers, prefer provider instrumentation over manual child spans. For Anthropic, add OpenInference in the same bootstrap and keep call sites native. This example uses @vercel/otel@2.x; if the installed types are v1, use logRecordProcessor singular instead.

import Anthropic from "@anthropic-ai/sdk"
import { AnthropicInstrumentation } from "@arizeai/openinference-instrumentation-anthropic"
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http"
import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs"
import { registerOTel } from "@vercel/otel"

const MAPLE_ENDPOINT = "https://ingest.maple.dev"
const MAPLE_KEY = "MAPLE_TEST"

const anthropicInstrumentation = new AnthropicInstrumentation({
	traceConfig: {
		hideInputs: true,
		hideOutputs: true,
	},
})

anthropicInstrumentation.manuallyInstrument(Anthropic)

export function register() {
	registerOTel({
		serviceName: "my-next-app",
		instrumentations: [anthropicInstrumentation],
		traceExporter: {
			url: `${MAPLE_ENDPOINT}/v1/traces`,
			headers: { authorization: `Bearer ${MAPLE_KEY}` },
		},
		logRecordProcessors: [
			new BatchLogRecordProcessor(
				new OTLPLogExporter({
					url: `${MAPLE_ENDPOINT}/v1/logs`,
					headers: { authorization: `Bearer ${MAPLE_KEY}` },
				}),
			),
		],
	})
}

Route handlers

Use native OTel APIs where auto-instrumentation is blind.

import { trace, metrics } from "@opentelemetry/api"
import { withSpan } from "@maple/otel-helpers"

const tracer = trace.getTracer("my-next-app")
const meter = metrics.getMeter("my-next-app")
const generated = meter.createCounter("mug.copy.generated")

export async function POST(request: Request) {
	const tenantId = request.headers.get("x-tenant-id") ?? "tenant_demo"
	return withSpan(
		"mug.copy.generate",
		async (span) => {
			span.setAttribute("tenant.id", tenantId)
			generated.add(1, { "tenant.id": tenantId, outcome: "success" })
			return Response.json({ ok: true })
		},
		{ tracer },
	)
}

For TypeScript route handlers, use @maple/otel-helpers withSpan for bounded business spans and add @maple/otel-helpers to package.json when it is not already present. This is required when the package can be installed. It keeps span lifecycle / error handling out of the handler body and avoids a large indentation diff. Do not expand the whole route into tracer.startActiveSpan(...) plus try / catch / finally unless the helper cannot be added or the span has a true cross-callback lifecycle.

If a route has an LLM call and OpenInference / provider instrumentation supports that SDK, do not wrap the provider call. Leave client.messages.create(...) / equivalent in place and put business context on the active product span or structured log. Do not duplicate provider/model/token attributes in route-level spans, logs, or metrics when OpenInference already reports them. Do not calculate LLM cost in route handlers; Maple derives estimated cost in the UI/query layer from OpenInference provider/model/token attributes. For Anthropic in Next.js/ESM, keep the instrumentation instance and manuallyInstrument(Anthropic) call at module scope so it runs once and before route code.

Match the @vercel/otel logs option to the installed package/types: @vercel/otel@1.x uses logRecordProcessor singular, while @vercel/otel@2.x uses logRecordProcessors plural. For normal Next.js / Vercel apps, do not guard registerOTel(...) behind NEXT_RUNTIME; Next calls instrumentation.ts in the appropriate runtime and @vercel/otel handles its own runtime differences.

console.info is not OTLP log export. If there is no existing logger bridge, use @opentelemetry/api-logs for production log records. Remove pre-existing console.* calls that duplicate the same structured OTel log event:

import { logs, SeverityNumber } from "@opentelemetry/api-logs"

const logger = logs.getLogger("my-next-app")

logger.emit({
	severityNumber: SeverityNumber.INFO,
	severityText: "INFO",
	body: "generated mug copy",
	attributes: {
		"tenant.id": tenantId,
		"gen_ai.provider.name": "anthropic",
		"gen_ai.request.model": model,
		"app.gen_ai.use_case": "web.mug_copy",
		outcome: "success",
	},
})

Configuration and smoke

Inline the endpoint and ingest key in instrumentation.ts — pass them explicitly to registerOTel. Don't rely on OTEL_EXPORTER_OTLP_* env vars: the Maple ingest key is project-scoped + write-only and inline config sidesteps Vercel's env-propagation quirks during preview builds.

Smoke checks should use tools already in the repo, e.g. npm run typecheck or npm run build, plus a real app request where practical. Do not invent fragile inline Node scripts that import TypeScript source files directly, and do not assume ts-node exists unless it is already installed.

提供基于Node.js的OpenTelemetry标准化配置方案。通过--import引导加载SDK,使用OTLP HTTP导出器自动捕获Trace、Log和Metric。支持Express等框架自动插桩及Bun环境,规范资源属性与业务代码中的原生API调用。
需要为Node.js应用配置OpenTelemetry遥测数据 询问如何集成Maple平台进行可观测性监控 涉及Express、Fastify或Bun的自动化链路追踪设置
skills/maple-nodejs-style/SKILL.md
npx skills add MapleTechLabs/maple --skill maple-nodejs-style -g -y
SKILL.md
Frontmatter
{
    "name": "maple-nodejs-style",
    "description": "Plain Node.js (Express, Fastify, Hono, Bun) OpenTelemetry style for Maple: NodeSDK + --import bootstrap, native @opentelemetry\/api call sites, inline endpoint + ingest key, OTLP HTTP exporters."
}

Maple Node.js style

Use @opentelemetry/sdk-node with --import (or the equivalent Bun --preload) so the SDK starts before any framework code runs.

// telemetry.ts
import { NodeSDK } from "@opentelemetry/sdk-node"
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node"
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http"
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http"
import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs"
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics"
import { resourceFromAttributes } from "@opentelemetry/resources"

const MAPLE_ENDPOINT = "https://ingest.maple.dev"
const MAPLE_KEY = "MAPLE_TEST" // set by maple-onboard skill on pairing

const headers = { authorization: `Bearer ${MAPLE_KEY}` }

const sdk = new NodeSDK({
	resource: resourceFromAttributes({
		"service.name": "my-node-app",
		"deployment.environment.name": process.env.NODE_ENV ?? "development",
		"vcs.repository.url.full": "https://github.com/acme/my-node-app",
		"vcs.ref.head.revision":
			process.env.RAILWAY_GIT_COMMIT_SHA ??
			process.env.GITHUB_SHA ??
			process.env.GIT_COMMIT,
	}),
	traceExporter: new OTLPTraceExporter({
		url: `${MAPLE_ENDPOINT}/v1/traces`,
		headers,
	}),
	logRecordProcessors: [
		new BatchLogRecordProcessor(
			new OTLPLogExporter({ url: `${MAPLE_ENDPOINT}/v1/logs`, headers }),
		),
	],
	metricReader: new PeriodicExportingMetricReader({
		exporter: new OTLPMetricExporter({
			url: `${MAPLE_ENDPOINT}/v1/metrics`,
			headers,
		}),
	}),
	instrumentations: [getNodeAutoInstrumentations()],
})

sdk.start()

Run the app with the bootstrap loaded first:

node --import ./telemetry.js app.js

For TypeScript projects, use the loader the repo already uses (tsx, ts-node/esm, native Bun, etc.) — do not introduce a new loader.

Bootstrap rules

  • HTTP OTLP exporters only, never gRPC. gRPC pulls in native bindings that complicate containers.
  • getNodeAutoInstrumentations() covers HTTP, Express, Fastify, Hono, pg, MySQL, Redis, and many more out of the box. Disable specific instrumentations only when they actively break the app:
    etNodeAutoInstrumentations({
    "@opentelemetry/instrumentation-fs": { enabled: false },
    )
    
  • For Bun, use the same SDK with bun --preload ./telemetry.ts run app.ts. Bun's HTTP exporter compatibility is good; if you hit an issue, fall back to node for the bootstrap process.

Route handlers and business operations

Use the native API; reach for withSpan from @maple/otel-helpers for bounded operations.

import { trace, metrics } from "@opentelemetry/api"
import { withSpan } from "@maple/otel-helpers"

const tracer = trace.getTracer("orders.api")
const meter = metrics.getMeter("orders.api")
const submitted = meter.createCounter("orders.submitted")

app.post("/orders", async (req, res) => {
	await withSpan(
		"order.submit",
		async (span) => {
			span.setAttributes({
				"tenant.id": req.headers["x-tenant-id"] as string,
				"order.id": req.body.id,
			})
			await chargeOrder(req.body)
			submitted.add(1, { "tenant.id": req.headers["x-tenant-id"] as string })
			res.json({ ok: true })
		},
		{ tracer },
	)
})

Logs

Bridge the existing logger (Pino, Winston, console) through OTLP rather than replacing it. For Pino, install @opentelemetry/instrumentation-pino and include it in instrumentations. For Winston, install @opentelemetry/instrumentation-winston. The user's logger keeps its current sinks; you're adding OTLP underneath so logs carry trace_id / span_id and reach Maple. Do not rip out the existing logger.

Coexistence

If the repo has Sentry, Datadog, New Relic, Honeycomb, Logtail, or a Pino transport, leave them in place. They sit alongside Maple, not instead of it.

自动将项目接入Maple遥测平台,通过安装OpenTelemetry为仓库内所有应用和服务配置追踪、日志和指标。支持多种语言框架,无需环境变量即可内联端点和密钥,实现快速部署与数据上报。
install Maple set up Maple add Maple telemetry onboard this repo to Maple instrument with OpenTelemetry for Maple
skills/maple-onboard/SKILL.md
npx skills add MapleTechLabs/maple --skill maple-onboard -g -y
SKILL.md
Frontmatter
{
    "name": "maple-onboard",
    "description": "Onboard a project to Maple by installing OpenTelemetry traces, logs, and metrics across every app and service in the repo. Triggers on requests like 'install Maple', 'set up Maple', 'add Maple telemetry', 'onboard this repo to Maple', 'instrument with OpenTelemetry for Maple'."
}

Maple onboarding

Wire OpenTelemetry traces, logs, and metrics into the user's project so telemetry streams to Maple. Cover every app and service in the repo — not just the one the user is currently sitting in.

Prefer native OpenTelemetry APIs and the framework's documented bootstrap over custom helper layers. If a specific stack stumps you, search the OTel docs for that language; don't guess.

Before editing, read the applicable companion skills:

  • maple-onboarding-style for general OTel taste, VCS attributes, and smoke checks.
  • maple-nextjs-style for Next.js / Vercel apps.
  • maple-nodejs-style for plain Node servers (Express, Fastify, Hono, Bun).
  • maple-python-style for Python services (FastAPI, Django, Flask).
  • maple-effect-style for Effect-based services — Maple's first-class SDK.
  • maple-go-style, maple-rust-style, maple-java-style, maple-csharp-style, maple-kotlin-style for those stacks.

If none match (Ruby, Elixir, PHP, plain Deno, …), use maple-onboarding-style as the fallback and consult the upstream OTel docs for that language.

Step 0 — Endpoint and key handling

The OTLP endpoint is always https://ingest.maple.dev and goes inline in the bootstrap code — it's not a secret, no env-var indirection needed.

The ingest API key is project-scoped + write-only — it can only ingest events for one project, can't read anything, can't change settings. Treat it like a Sentry DSN, a PostHog public key, or a Datadog RUM client token: inline it directly in the OTel bootstrap source alongside the endpoint. No .env files, no deploy-target wiring, no process.env.OTEL_EXPORTER_OTLP_HEADERS. The user deploys their code and events flow.

Two paths, no questions asked:

Key in the prompt

If the invoking prompt already contains a Maple ingest key, inline it in every bootstrap file you write. Done — move on to Step 1.

No key

Inline the literal sentinel MAPLE_TEST in the bootstrap source as a placeholder. Maple's ingest gateway accepts it from anyone, returns 200 without forwarding anywhere, so the user's app can boot and exercise the OTel bootstrap path while they go create a real key.

Tell the user briefly at the top of your work: "I'm using MAPLE_TEST as a placeholder so the bootstrap can run. Create a real key at https://app.maple.dev/settings (Settings → API Keys) and search-replace the literal MAPLE_TEST in the files I write to swap it in."

Then keep going. Don't block install on signup.

Step 1 — Map every app/service in the repo

Before instrumenting anything, enumerate what's here. Check workspace manifests (pnpm-workspace.yaml, root package.json workspaces, bun workspaces, go.work, Cargo workspace, Python pyproject.toml workspace setups, apps/* and services/* conventions). Identify each service: web frontend, API, workers, background jobs, CLIs, sample/demo apps, mobile apps, Supabase and/or server functions. Mobile and serverless/edge functions are in scope; do not skip them merely because they are client-side or short-lived. Skip pure type/config packages with no runtime entry point. Do not skip any runnable services or leave them "out of scope": instrument absolutely everything in this run; there may be no follow-up.

Show the user the list before you start, so they can correct it.

Step 2 — For each service, install native OTel and bootstrap

Use the language's native OpenTelemetry SDK. Don't reach for vendor wrappers or hand-rolled helpers when an official package exists. Examples of what "native" means here: @opentelemetry/sdk-node for Node servers, @vercel/otel for normal Next.js/Vercel apps (sdk-node breaks Next's webpack and misses the framework bootstrap), @opentelemetry/sdk-trace-web + browser/mobile-compatible exporters for Vite/SPA/Expo, opentelemetry-instrumentation-* + opentelemetry-sdk for Python, go.opentelemetry.io/otel for Go.

No broad wrapper APIs. Avoid reusable helpers like sendMapleSpan, recordCounter, recordLog, startTelemetrySpan, or withTelemetry. Acquire native tracers/meters/loggers at module scope and use the SDK's own APIs directly. In TypeScript/JavaScript, use the published @maple/otel-helpers withSpan helper for bounded business spans and add @maple/otel-helpers to package.json; this is required when the package can be installed because it avoids expanding a whole function into startActiveSpan plus try / catch / finally. Do not use helpers around provider SDK calls that OpenInference/provider instrumentation can observe directly.

Wire all three signals — traces, logs, metrics. Logs go through OTLP, not just stdout — set up the OTel log bridge for the language so app logs (with their existing log levels and structured fields) carry the active trace_id / span_id automatically. The user's existing logger keeps working; you're just adding an OTLP handler/processor underneath.

Bootstrap rules:

  • The bootstrap file must run before any framework imports. Use the language/framework's documented hook (--import flag, instrumentation.ts, top-of-main.py import, etc.).
  • Inline the endpoint (https://ingest.maple.dev) and the project's ingest key directly in the bootstrap source. Don't read from process.env.OTEL_EXPORTER_OTLP_* or write any .env files — the key is write-only, and inline configuration removes a whole class of "OTel didn't start because env vars weren't set" deploy failures. (See the framework-specific style skills for the exact shape per stack.)
  • Use HTTP OTLP exporters, not gRPC. gRPC pulls in native bindings that break bundlers and complicate containers.
  • Use the project's existing package manager (detect via lockfile).
  • Prefer idempotent edits. If a config file already exists, edit don't overwrite.
  • Set resource attributes on the OTel resource for every service: service.name, service.version, deployment.environment.name, and vcs.repository.url.full — the canonical https URL of the repo (e.g. https://github.com/acme/api). The repo URL is the important one and is fine to hardcode alongside service.name in the SDK init; if the build platform exposes the slug (Vercel VERCEL_GIT_REPO_OWNER/VERCEL_GIT_REPO_SLUG, Railway RAILWAY_GIT_REPO_OWNER/RAILWAY_GIT_REPO_NAME), prefer reading from env. Also set vcs.ref.head.revision (commit SHA) on a best-effort basis from whatever env the runtime already injects (VERCEL_GIT_COMMIT_SHA, RAILWAY_GIT_COMMIT_SHA, GITHUB_SHA, SOURCE_COMMIT, GIT_COMMIT, HEROKU_SLUG_COMMIT, …). Do not shell out to git from the running process. Skipping the SHA is fine, skipping the URL is not. Use the OTel semantic-convention keys exactly — do not invent git.repo / app.repo_url / deployment.commit_sha.

Framework rules:

  • Next.js/Vercel: use instrumentation.ts with @vercel/otel registerOTel(...) as the bootstrap. Do not substitute a raw @opentelemetry/sdk-node / NodeSDK bootstrap unless the repo already uses that architecture and you are extending it. Use @opentelemetry/api tracers/meters inside route handlers only where auto-instrumentation is blind.
  • Expo/React Native: preserve existing Expo Go / unsupported-runtime guards. In supported builds, call initObservability() before Sentry and before app registration/user code. Inline the endpoint + ingest key in the observability module — no EXPO_PUBLIC_* env vars. The bootstrap reads them straight from constants.
  • Supabase Edge Functions / Cloudflare Workers: native Deno / Workers OpenTelemetry can be quirky. Keep the exporter shim tiny, provider-neutral, and OTel-shaped: tracer.startActiveSpan, span.setAttributes, SpanStatusCode, meter.createCounter, histogram.record. For Effect-on-Workers, prefer @maple/effect-cloudflare (see maple-effect-style).
  • Python/FastAPI: use native instrumentation such as FastAPIInstrumentor.instrument_app(app) rather than replacing request handling with manual middleware.

Coexist with existing observability vendors. Don't remove Sentry, Datadog, New Relic, Honeycomb, Logtail, Pino transports, etc. OTel sits alongside them. The user explicitly wants both signals flowing during migration; ripping out the incumbent is not your call.

Step 3 — Add custom spans, metrics, and logs around business operations

Auto-instrumentation gets you HTTP in/out, DB queries, framework lifecycle. That's the floor, not the ceiling. Read the project to find the operations a human operator would actually want to see when something looks wrong.

Traces

Wrap every critical business operation with an active span. Auto-instrumented spans are fine where they exist — but if an operation isn't already getting a span, add one.

  • Naming: domain.verb (order.process, payment.charge, email.send, agent.run, interview.create, job.<type>).
  • Attributes: entity IDs (order.id, user.id, workspace.id, tenant.id), counts, key boolean branch outcomes, model name / provider for LLM calls.
  • Record exceptions: span.recordException(err) + span.setStatus({ code: ERROR }) on failure paths. In TS/JS use withSpan from @maple/otel-helpers so the body stays flat instead of nested under try / catch / finally.
  • For Python functions with clear boundaries, prefer @tracer.start_as_current_span("operation.name"). Use a context manager when a decorator does not fit. Do not use detached start_span() + manual end() for bounded work.
  • Skip trivial getters, pure transforms, internal helpers — anything with no real latency or failure mode.
  • Never put PII in attributes (emails, passwords, tokens, full request bodies).

Logs

Make sure logs are structured and carry operation context. Concretely: every log line emitted inside a span should arrive at Maple with trace_id / span_id populated and any structured fields (orderId, userId, etc.) preserved as attributes. Trace/span context may be added natively by the log bridge or integration, or may require additional work.

Use logs for narrative ("starting batch reconcile", "retrying after 3xx") and exceptional events. An error log must only be emitted if the operation cannot recover and manual intervention is required.

Metrics

Cover business + performance + cost. Three categories to look for:

  • Business logic counters. Every meaningful state transition: created, started, completed, failed, retried. Per-tenant, per-channel, per-status — low-cardinality dimensions only (never user/order IDs).
  • Performance histograms. Latency of operations the user cares about, queue depth, batch sizes, payload sizes. Reuse existing timing instrumentation if the project already has any (time.perf_counter blocks, custom LatencyTrackers, "[TIMING]" log lines) — emit a histogram from those measurements rather than measuring twice.
  • Costs — especially LLM costs. If the project calls OpenAI / Anthropic / Google / any LLM provider, prefer provider instrumentation such as OpenInference where available so native SDK calls stay readable. Do not add pricing constants or LLM cost math in product handlers; Maple computes estimated cost centrally in the UI/query layer from captured provider/model/token attributes. Avoid duplicating token counters already captured by provider instrumentation.

Get the meter once at module level, create instruments at module level, increment in the hot path. Don't create a fresh meter per call.

Step 4 — Verify the app still works

Per service:

  1. Run the project's own dev or build command (whatever its package.json / pyproject / Makefile already wires up). Confirm it starts cleanly with no errors that trace back to your OTel install. Also run a telemetry bootstrap smoke that imports or starts the app, so provider setup, exporter construction, log bridging, and framework instrumentation all initialize. For a Python server this can be an import/startup command such as uv run python -c 'from app.main import app; print(app.title)'; for Node/Next use the repo's build/start path. For a server, hit at least one route with curl so traffic flows through the instrumentation; choose a route that exercises an instrumented operation when practical, not only a static health route. For a CLI, invoke a real command. Don't ship if the app's own startup is now broken — that's a regression.
  2. Confirm telemetry leaves the process. With the inline MAPLE_TEST (or real key) in the bootstrap, OTLP POSTs from the dev server should return 2xx — that proves the bootstrap is reaching the network. As an extra sanity check, you can curl -X POST https://ingest.maple.dev/v1/traces -H "authorization: Bearer MAPLE_TEST" -H "content-type: application/json" -d '{}' and confirm 200. The real signal is the running app's own POSTs succeeding by the time the dev server shuts down — if they don't, the bootstrap is wrong (most often: SDK loaded too late, or shutdown not flushing).

A bootstrap that loads but never POSTs is not a partial success. Fix it before moving on.

Step 5 — Hand-off (final message to the user)

What changed

3–7 short factual bullets covering: packages installed, files created/modified, business spans/metrics added. Per service if changes differed, grouped if uniform. Mention any existing observability vendor (Sentry, Datadog, Logtail, Pino transports, etc.) you intentionally left in place so the coexistence is explicit.

Swap the placeholder if you used one

If MAPLE_TEST is still inline, tell the user:

"The bootstrap currently uses MAPLE_TEST as a placeholder so the install could complete end-to-end. Create a real key at https://app.maple.dev (Settings → API Keys), then search-replace MAPLE_TEST in the files I wrote with your new key and redeploy."

Deploy

Tell the user to deploy as they normally would — push to their hosting platform, run their existing CI, or run locally. There are no env vars to wire and nothing platform-specific to configure: the endpoint and key are inline in the bootstrap, so events start flowing the moment the instrumented code runs.

If the user asks "where do I put the key in production?" — the answer is "you already did, it's in the source you just deployed."

Step 6 — Drive Maple MCP install

Suggest installing the Maple MCP server so the agent (Claude Code, Codex, Cursor, etc.) can query telemetry directly next time the user is debugging — search logs, pull traces, check error rates from inside the chat without context-switching to the dashboard.

For Claude Code (most common — that's where this skill is running), offer to run it for them:

claude mcp add --transport http maple https://api.maple.dev/mcp

This edits the user's Claude Code config. Confirm before running (the user may have a custom MCP scope or want to install elsewhere). If they decline, print the command so they can run it themselves later.

For other agents the user might also use, mention but do not run:

  • Codex: codex mcp add maple --url https://api.maple.dev/mcp
  • Cursor / others: copy the mcpServers snippet from https://app.maple.dev/mcp.

When done (or skipped), close out with a single line directing the user to deploy their app — they're ready to ship.

Hard rules

  • Never modify files outside the project root.
  • Never commit, push, or open PRs.
  • Inline the ingest key in source. It's a project-scoped, write-only token (think Sentry DSN); env-var indirection just adds deploy-time failure modes for no gain.
  • Never remove an existing observability vendor unless the user asks for it.
  • Use the project's existing package manager and existing logger.
  • Prefer native OTel packages for the language; don't reinvent telemetry plumbing the SDK already provides.
  • If the dev/build command errors out because of your instrumentation, that's a failure — fix it or report it, don't paper over it.
规范Maple项目OpenTelemetry接入风格,要求使用原生API及@maple/otel-helpers库,内联配置端点与密钥,采用低基数语义化命名,并收集VCS资源属性。
需要集成OpenTelemetry监控 编写Telemetry初始化代码 配置OTel导出器或Span
skills/maple-onboarding-style/SKILL.md
npx skills add MapleTechLabs/maple --skill maple-onboarding-style -g -y
SKILL.md
Frontmatter
{
    "name": "maple-onboarding-style",
    "description": "General OpenTelemetry onboarding style for Maple: native APIs, signal quality, inline keys, VCS resource attributes, LLM metrics, and smoke checks."
}

Maple OTel onboarding style

Use native OpenTelemetry APIs. Do not invent helper APIs.

In TypeScript/JavaScript, use the published @maple/otel-helpers withSpan helper for bounded business spans and add @maple/otel-helpers to package.json when it is not already present. This is required when the package can be installed. withSpan is the intended replacement for expanding a whole function into tracer.startActiveSpan(...) plus try / catch / finally. Do not use helpers to wrap provider SDK calls that OpenInference / provider instrumentation can observe directly.

Do:

import { trace, metrics } from "@opentelemetry/api"
import { withSpan } from "@maple/otel-helpers"

const tracer = trace.getTracer("orders.api")
const meter = metrics.getMeter("orders.api")
const ordersSubmitted = meter.createCounter("orders.submitted")

await withSpan(
	"order.submit",
	async (span) => {
		span.setAttributes({
			"tenant.id": tenantId,
			"order.id": orderId,
			outcome: "success",
		})
		ordersSubmitted.add(1, { "tenant.id": tenantId, outcome: "success" })
	},
	{ tracer },
)

Do not:

await sendMapleSpan(...)
recordCounter(...)
withTelemetry(...)

Naming

  • Files/functions are provider-neutral: telemetry.ts, observability.ts, initTelemetry(), initObservability().
  • The word Maple belongs only in endpoint/key setup comments or PR instructions.
  • Span names are conventional and low-cardinality: checkout.process, voice.session, llm.generate_copy.
  • Prefer semantic product-operation span names over provider transport names. llm.generate_copy or llm.voice_response is usually more useful than llm.anthropic.messages.create.

Endpoint and key

Inline the endpoint and the project's ingest key directly in the bootstrap source — don't read from OTEL_EXPORTER_OTLP_* env vars and don't write .env files. The Maple ingest key is project-scoped + write-only (Sentry DSN shaped), so source-level configuration is the right default; env-var indirection only adds deploy-time failure modes.

MAPLE_ENDPOINT = "https://ingest.maple.dev"
MAPLE_KEY      = "maple_…"        # or "MAPLE_TEST" while pairing

While pairing is in flight, use the literal MAPLE_TEST sentinel — Maple's ingest accepts it without forwarding events anywhere, so the bootstrap exercises the full code path before the real key arrives.

Pass the inline values to the SDK explicitly via the exporter constructor's endpoint / headers options. Do not configure the SDK off implicit env-var reads.

If the repo can call telemetry init from multiple paths, guard provider/exporter setup so repeated imports, tests, reloads, or framework callbacks do not install duplicate processors or log handlers. For a single-entrypoint app that starts cleanly, keep this simple.

Include standard resource attributes when values are available: service.name, service.version, deployment.environment.name, and the VCS attributes below.

VCS resource attributes

Set vcs.repository.url.full on the OTel resource for every instrumented service. The value is the canonical https URL of the repo (e.g. https://github.com/acme/api) — the same URL the user would paste in a browser, not an SSH URL, not a local working-tree path. This is the important one: it lets Maple link telemetry back to the source of truth. It is fine to hardcode this string alongside service.name in the SDK init; if a build env already exposes the slug (e.g. VERCEL_GIT_REPO_OWNER + VERCEL_GIT_REPO_SLUG, RAILWAY_GIT_REPO_OWNER + RAILWAY_GIT_REPO_NAME), prefer reading from env so a fork or rename doesn't drift.

Also set vcs.ref.head.revision (the commit SHA) on a best-effort basis. Read it from whatever env var the runtime/build platform already injects: VERCEL_GIT_COMMIT_SHA, RAILWAY_GIT_COMMIT_SHA, GITHUB_SHA, SOURCE_COMMIT, GIT_COMMIT, HEROKU_SLUG_COMMIT, etc. Do not shell out to git from the running process — many production images do not have git or a working tree. If no env source is available, omit the attribute; skipping the SHA is fine, skipping the URL is not.

Use vcs.repository.url.full and vcs.ref.head.revision exactly as named — these are the OTel semantic-convention keys. Do not invent parallel attributes like git.repo, app.repo_url, or deployment.commit_sha.

Signals

  • Traces: all critical operations have spans with relevant attributes.
  • Logs: structured, concise, OTLP-forwarded, and trace/span-correlated.
  • Metrics: critical operations have low-cardinality counters/histograms.
  • Tenant/org/project information is included where available.
  • Do not put raw user ids or request ids in metric tags unless the repo already treats them as bounded tenant-like ids.

LLM metrics

If the app uses LLMs, first look for provider instrumentation that already captures model/provider/token/error spans. In JavaScript/TypeScript, prefer OpenInference packages such as @arizeai/openinference-instrumentation-anthropic for supported SDKs. Keep the real provider call native and readable.

const response = await client.messages.create({
	model,
	max_tokens: 100,
	messages,
})

Every provider/call site still needs enough telemetry to answer usage questions. Let provider instrumentation own model/provider/token spans where it supports them. Do not duplicate those attributes at every application call site, and do not put provider pricing tables or cost math in product handlers. Maple computes estimated LLM cost centrally in the UI/query layer from captured provider/model/token data.

llmInputTokens.add(inputTokens, {
	"tenant.id": tenantId,
	"gen_ai.provider.name": "anthropic",
	"gen_ai.request.model": model,
	"app.gen_ai.use_case": "voice.initial_greeting",
	"app.gen_ai.call_site": "_callMugCopyLlm",
	outcome: "success",
})

Use counters for additive totals only when provider instrumentation cannot capture token usage:

  • llm.tokens.input
  • llm.tokens.output

Token counters use unit="tokens" or the SDK equivalent. If OpenInference / provider instrumentation already captures token usage, do not duplicate token counters just to mirror it. Do not add llm.cost_usd or equivalent app-side cost metrics for normal LLM calls; cost belongs in Maple's central pricing layer.

Use histograms for latency/duration distributions.

Prefer current gen_ai.* semantic-convention-style attribute names for LLM provider/model/token attributes, plus app.gen_ai.* for bounded application dimensions such as use case and call site. Avoid inventing parallel llm.* attributes unless the repo already standardizes on them.

If the app has OpenAI, Anthropic, and Google callers, instrument all three.

Smoke checks

Add a durable smoke path when the repo has a natural place for it: README, TESTING guide, script, npm command, pytest, or checked-in command note.

The smoke should explicitly prove startup/import with the OTel bootstrap loaded so provider setup, exporter construction, log bridging, and framework instrumentation initialize without errors. Then, where practical, exercise an actual instrumented span/log/metric or OTLP export attempt. A generic health route only proves the server responds; prefer an operation that crosses the instrumentation you added.

定义Maple平台Python项目的OpenTelemetry规范,涵盖模块级实例化、装饰器/上下文管理器使用、异常记录、OTLP日志集成及硬编码配置,禁止使用辅助API封装。
需要为Python项目配置OpenTelemetry时 询问Maple平台的Python遥测最佳实践时 实现Trace/Meter/Logger初始化逻辑时
skills/maple-python-style/SKILL.md
npx skills add MapleTechLabs/maple --skill maple-python-style -g -y
SKILL.md
Frontmatter
{
    "name": "maple-python-style",
    "description": "Python OpenTelemetry style for Maple: module-scope tracers\/meters, decorators for bounded work, error spans, OTLP-bridged logs via LoggingHandler + LoggingInstrumentor, inline endpoint + ingest key, and no helper-API wrappers."
}

Maple Python style

Acquire OTel objects at module scope.

from opentelemetry import metrics, trace
from opentelemetry.trace import Status, StatusCode

tracer = trace.get_tracer("orders.api")
meter = metrics.get_meter("orders.api")

orders_submitted = meter.create_counter("orders.submitted", unit="1")

Bounded work

Prefer decorators for functions with clear boundaries.

@tracer.start_as_current_span("order.submit")
async def submit_order(*, tenant_id: str, order_id: str) -> None:
    span = trace.get_current_span()
    span.set_attributes({
        "tenant.id": tenant_id,
        "order.id": order_id,
    })

Use a context manager when a decorator does not fit.

with tracer.start_as_current_span("order.validate") as span:
    span.set_attribute("tenant.id", tenant_id)
    validate_order(order)

Do not use detached tracer.start_span(...); span.end() for bounded work.

Error paths

Record exceptions on the active span.

try:
    result = await client.messages.create(...)
except Exception as exc:
    span = trace.get_current_span()
    span.record_exception(exc)
    span.set_status(Status(StatusCode.ERROR))
    logger.exception("llm call failed", extra={"tenant_id": tenant_id})
    raise

Logs

If logs are claimed as OTLP-forwarded, configure all of:

  • An OTel LoggerProvider + OTLPLogExporter + LoggingHandler
  • set_logger_provider(logger_provider) from opentelemetry._logs
  • Log correlation for existing records, e.g. LoggingInstrumentor().instrument(set_logging_format=True)

Preserve existing logging.basicConfig, console / file handlers, and log levels. The user's logger keeps working — you're adding an OTLP handler underneath so log lines carry trace_id / span_id and reach Maple.

Init behavior

Inline the endpoint and ingest key directly in the init module — don't read them from env. The ingest key is project-scoped + write-only (Sentry DSN shaped), so source-level configuration is the right default; env indirection just adds a class of "OTel didn't start because env wasn't set" deploy failures.

# telemetry.py
import logging

from opentelemetry import _logs, metrics, trace
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.logging import LoggingInstrumentor
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

MAPLE_ENDPOINT = "https://ingest.maple.dev"
MAPLE_KEY = "MAPLE_TEST"  # set by maple-onboard skill on pairing

_INITIALIZED = False


def init_observability() -> None:
    global _INITIALIZED
    if _INITIALIZED:
        return
    _INITIALIZED = True

    headers = {"authorization": f"Bearer {MAPLE_KEY}"}
    resource = Resource.create({
        "service.name": "my-python-app",
        "deployment.environment.name": os.getenv("DEPLOYMENT_ENV", "development"),
        "vcs.repository.url.full": "https://github.com/acme/my-python-app",
        "vcs.ref.head.revision": os.getenv("RAILWAY_GIT_COMMIT_SHA")
            or os.getenv("GITHUB_SHA")
            or os.getenv("GIT_COMMIT"),
    })

    tracer_provider = TracerProvider(resource=resource)
    tracer_provider.add_span_processor(
        BatchSpanProcessor(
            OTLPSpanExporter(endpoint=f"{MAPLE_ENDPOINT}/v1/traces", headers=headers),
        ),
    )
    trace.set_tracer_provider(tracer_provider)

    logger_provider = LoggerProvider(resource=resource)
    logger_provider.add_log_record_processor(
        BatchLogRecordProcessor(
            OTLPLogExporter(endpoint=f"{MAPLE_ENDPOINT}/v1/logs", headers=headers),
        ),
    )
    _logs.set_logger_provider(logger_provider)
    logging.getLogger().addHandler(LoggingHandler(logger_provider=logger_provider))
    LoggingInstrumentor().instrument(set_logging_format=True)

    meter_provider = MeterProvider(
        resource=resource,
        metric_readers=[
            PeriodicExportingMetricReader(
                OTLPMetricExporter(
                    endpoint=f"{MAPLE_ENDPOINT}/v1/metrics", headers=headers,
                ),
            ),
        ],
    )
    metrics.set_meter_provider(meter_provider)

Add the _INITIALIZED guard only when the app can realistically call this function more than once (FastAPI lifespan + workers, pytest fixtures, etc.).

Metrics

Counters:

  • llm.tokens.input
  • llm.tokens.output
  • requests/events/jobs/errors

Use semantic units when the SDK supports them: token counters use unit="tokens". Do not add app-side llm.cost_usd pricing metrics for normal LLM calls; Maple estimates cost centrally from provider/model/token data.

Histograms:

  • duration
  • latency
  • payload size

Avoid raw high-cardinality values in metric attributes. Prefer tenant/org/project, operation/use case, provider/model, and outcome dimensions over user-level metric tags.

FastAPI

Use the native instrumentation rather than replacing request handling with manual middleware.

from fastapi import FastAPI
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor

import telemetry

telemetry.init_observability()
app = FastAPI()
FastAPIInstrumentor.instrument_app(app)

Import telemetry (and call init_observability()) before any other module that needs tracing.

为 Maple 平台配置 Rust OpenTelemetry 集成。通过 OTLP HTTP 导出器发送 traces、logs 和 metrics,桥接 tracing crate 实现日志自动上报,并预置服务名、环境及版本控制等语义资源属性。
Rust 项目需要接入 Maple 可观测性平台 配置 OpenTelemetry SDK 与 tracing 桥接 设置 OTLP HTTP 导出器和认证头
skills/maple-rust-style/SKILL.md
npx skills add MapleTechLabs/maple --skill maple-rust-style -g -y
SKILL.md
Frontmatter
{
    "name": "maple-rust-style",
    "description": "Rust OpenTelemetry style for Maple: opentelemetry + opentelemetry_sdk + opentelemetry-otlp HTTP exporter, tracing-opentelemetry bridge for the tracing crate, inline endpoint + ingest key, semconv resource attributes."
}

Maple Rust style

Use the official opentelemetry + opentelemetry_sdk crates with opentelemetry-otlp (HTTP exporter, not gRPC). Bridge the tracing crate via tracing-opentelemetry so existing info! / error! calls flow through OTLP.

Cargo.toml

[dependencies]
opentelemetry = "0.27"
opentelemetry_sdk = { version = "0.27", features = ["rt-tokio"] }
opentelemetry-otlp = { version = "0.27", features = ["http-proto", "reqwest-client", "logs", "metrics"] }
opentelemetry-semantic-conventions = "0.27"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing-opentelemetry = "0.28"

Bootstrap

Inline the endpoint and ingest key — they're a project-scoped, write-only token (Sentry-DSN-shaped).

use opentelemetry::{global, KeyValue};
use opentelemetry_otlp::{LogExporter, MetricExporter, Protocol, SpanExporter, WithExportConfig};
use opentelemetry_sdk::{
    logs::LoggerProvider, metrics::SdkMeterProvider, trace::TracerProvider, Resource,
};
use opentelemetry_semantic_conventions::resource::{
    DEPLOYMENT_ENVIRONMENT_NAME, SERVICE_NAME,
};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

const MAPLE_ENDPOINT: &str = "https://ingest.maple.dev";
const MAPLE_KEY: &str = "MAPLE_TEST"; // set by maple-onboard skill on pairing

pub fn init() -> Result<(TracerProvider, LoggerProvider, SdkMeterProvider), opentelemetry_otlp::ExporterBuildError> {
    let auth = format!("Bearer {MAPLE_KEY}");
    let mut headers = std::collections::HashMap::new();
    headers.insert("authorization".to_string(), auth);

    let resource = Resource::builder()
        .with_attributes([
            KeyValue::new(SERVICE_NAME, "orders-api"),
            KeyValue::new(DEPLOYMENT_ENVIRONMENT_NAME, std::env::var("DEPLOYMENT_ENV").unwrap_or_else(|_| "development".into())),
            KeyValue::new("vcs.repository.url.full", "https://github.com/acme/orders-api"),
            KeyValue::new("vcs.ref.head.revision", std::env::var("GITHUB_SHA").unwrap_or_default()),
        ])
        .build();

    let trace_exporter = SpanExporter::builder()
        .with_http()
        .with_endpoint(format!("{MAPLE_ENDPOINT}/v1/traces"))
        .with_headers(headers.clone())
        .with_protocol(Protocol::HttpJson)
        .build()?;
    let tracer_provider = TracerProvider::builder()
        .with_batch_exporter(trace_exporter)
        .with_resource(resource.clone())
        .build();
    global::set_tracer_provider(tracer_provider.clone());

    let log_exporter = LogExporter::builder()
        .with_http()
        .with_endpoint(format!("{MAPLE_ENDPOINT}/v1/logs"))
        .with_headers(headers.clone())
        .with_protocol(Protocol::HttpJson)
        .build()?;
    let logger_provider = LoggerProvider::builder()
        .with_batch_exporter(log_exporter)
        .with_resource(resource.clone())
        .build();

    let metric_exporter = MetricExporter::builder()
        .with_http()
        .with_endpoint(format!("{MAPLE_ENDPOINT}/v1/metrics"))
        .with_headers(headers)
        .with_protocol(Protocol::HttpJson)
        .build()?;
    let meter_provider = SdkMeterProvider::builder()
        .with_periodic_exporter(metric_exporter)
        .with_resource(resource)
        .build();
    global::set_meter_provider(meter_provider.clone());

    let otel_layer = tracing_opentelemetry::layer().with_tracer(global::tracer("orders.api"));
    let otel_log_layer = opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge::new(&logger_provider);

    tracing_subscriber::registry()
        .with(tracing_subscriber::EnvFilter::from_default_env())
        .with(tracing_subscriber::fmt::layer())
        .with(otel_layer)
        .with(otel_log_layer)
        .init();

    Ok((tracer_provider, logger_provider, meter_provider))
}

Call from main and shut down on exit:

#[tokio::main]
async fn main() {
    let (tracer_provider, logger_provider, meter_provider) =
        telemetry::init().expect("telemetry init");

    // app run …

    let _ = tracer_provider.shutdown();
    let _ = logger_provider.shutdown();
    let _ = meter_provider.shutdown();
}

Bounded business spans via tracing

The point of bridging tracing is so existing instrumentation works unchanged. Use #[tracing::instrument] on bounded async operations:

#[tracing::instrument(name = "order.submit", skip_all, fields(order.id = %order_id))]
async fn submit_order(order_id: &str) -> Result<(), Error> {
    charge_order(order_id).await?;
    Ok(())
}

tracing::error! and ?err field interpolation will record the exception and set the span status to ERROR via the bridge.

Coexistence

If the project already uses tracing with a Honeycomb / Datadog / Jaeger layer, leave it in place — add Maple's tracing-opentelemetry layer alongside. Don't strip the existing exporter unless the user asks.

高性能Pandas替代方案,基于ClickHouse加速数据处理。支持16+数据源和10+文件格式,兼容Pandas API,适用于数据分析、跨源Join及性能优化。不用于原生SQL或服务器管理。
需要加速Pandas代码执行 使用Pandas语法分析数据 查询远程数据库或云存储为DataFrame 对不同来源的数据进行关联操作
.agents/skills/chdb-datastore/SKILL.md
npx skills add MapleTechLabs/maple --skill chdb-datastore -g -y
SKILL.md
Frontmatter
{
    "name": "chdb-datastore",
    "license": "Apache-2.0",
    "metadata": {
        "author": "chdb-io",
        "version": "4.1",
        "homepage": "https:\/\/clickhouse.com\/docs\/chdb"
    },
    "description": "Drop-in pandas replacement with ClickHouse performance. Use `import chdb.datastore as pd` (or `from datastore import DataStore`) and write standard pandas code — same API, 10-100x faster on large datasets. Supports 16+ data sources (MySQL, PostgreSQL, S3, MongoDB, ClickHouse, Iceberg, Delta Lake, etc.) and 10+ file formats (Parquet, CSV, JSON, Arrow, ORC, etc.) with cross-source joins. Use this skill when the user wants to analyze data with pandas-style syntax, speed up slow pandas code, query remote databases or cloud storage as DataFrames, or join data across different sources — even if they don't explicitly mention chdb or DataStore. Do NOT use for raw SQL queries, ClickHouse server administration, or non-Python languages.",
    "compatibility": "Requires Python 3.9+, macOS or Linux. pip install chdb."
}

chdb DataStore — It's Just Faster Pandas

The Key Insight

# Change this:
import pandas as pd
# To this:
import chdb.datastore as pd
# Everything else stays the same.

DataStore is a lazy, ClickHouse-backed pandas replacement. Your existing pandas code works unchanged — but operations compile to optimized SQL and execute only when results are needed (e.g., print(), len(), iteration).

pip install chdb

Decision Tree: Pick the Right Approach

1. "I have a file/database and want to analyze it with pandas"
   → DataStore.from_file() / from_mysql() / from_s3() etc.
   → See references/connectors.md

2. "I need to join data from different sources"
   → Create DataStores from each source, use .join()
   → See examples/examples.md #3-5

3. "My pandas code is too slow"
   → import chdb.datastore as pd — change one line, keep the rest

4. "I need raw SQL queries"
   → Use the chdb-sql skill instead

Connect to Any Data Source — One Pattern

from datastore import DataStore

# Local file (auto-detects .parquet, .csv, .json, .arrow, .orc, .avro, .tsv, .xml)
ds = DataStore.from_file("sales.parquet")

# Database
ds = DataStore.from_mysql(host="db:3306", database="shop", table="orders", user="root", password="pass")

# Cloud storage
ds = DataStore.from_s3("s3://bucket/data.parquet", nosign=True)

# URI shorthand — auto-detects source type
ds = DataStore.uri("mysql://root:pass@db:3306/shop/orders")

All 16+ sources and URI schemes → connectors.md

After Connecting — Full Pandas API

result = ds[ds["age"] > 25]                                          # filter
result = ds[["name", "city"]]                                        # select columns
result = ds.sort_values("revenue", ascending=False)                  # sort
result = ds.groupby("dept")["salary"].mean()                         # groupby
result = ds.assign(margin=lambda x: x["profit"] / x["revenue"])     # computed column
ds["name"].str.upper()                                               # string accessor
ds["date"].dt.year                                                   # datetime accessor
result = ds1.join(ds2, on="id")                                      # join
result = ds.head(10)                                                 # preview
print(ds.to_sql())                                                   # see generated SQL

209 DataFrame methods supported. Full API → api-reference.md

Cross-Source Join — The Killer Feature

from datastore import DataStore

customers = DataStore.from_mysql(host="db:3306", database="crm", table="customers", user="root", password="pass")
orders = DataStore.from_file("orders.parquet")

result = (orders
    .join(customers, left_on="customer_id", right_on="id")
    .groupby("country")
    .agg({"amount": "sum", "rating": "mean"})
    .sort_values("sum", ascending=False))
print(result)

More join examples → examples.md

Writing Data

source = DataStore.from_mysql(host="db:3306", database="shop", table="orders", user="root", password="pass")
target = DataStore("file", path="summary.parquet", format="Parquet")

target.insert_into("category", "total", "count").select_from(
    source.groupby("category").select("category", "sum(amount) AS total", "count() AS count")
).execute()

Troubleshooting

Problem Fix
ImportError: No module named 'chdb' pip install chdb
ImportError: cannot import 'DataStore' Use from datastore import DataStore or from chdb.datastore import DataStore
Database connection timeout Include port in host: host="db:3306" not host="db"
Join returns empty result Check key types match (both int or both string); use .to_sql() to inspect
Unexpected results Call ds.to_sql() to see the generated SQL and debug
Environment check Run python scripts/verify_install.py (from skill directory)

References

Note: This skill teaches how to use chdb DataStore. For raw SQL queries, use the chdb-sql skill. For contributing to chdb source code, see CLAUDE.md in the project root.

在Python进程中直接运行ClickHouse SQL,无需服务器。支持查询本地文件、远程数据库及云存储,提供chdb.query单行查询、Session状态化分析及DB-API连接功能,适用于高级SQL特性与跨源关联,不用于Pandas操作。
需要直接在Python中执行ClickHouse SQL语法 对Parquet/CSV等本地文件或S3等云存储进行SQL查询 涉及跨数据源(如MySQL与文件)的SQL关联查询 用户明确提及chdb.query()或ClickHouse相关函数
.agents/skills/chdb-sql/SKILL.md
npx skills add MapleTechLabs/maple --skill chdb-sql -g -y
SKILL.md
Frontmatter
{
    "name": "chdb-sql",
    "license": "Apache-2.0",
    "metadata": {
        "author": "chdb-io",
        "version": "4.1",
        "homepage": "https:\/\/clickhouse.com\/docs\/chdb"
    },
    "description": "In-process ClickHouse SQL engine for Python — run ClickHouse SQL queries directly on local files, remote databases, and cloud storage without a server. Use when the user wants to write SQL queries against Parquet\/CSV\/ JSON files, use ClickHouse table functions (mysql(), s3(), postgresql(), iceberg(), deltaLake() etc.), build stateful analytical pipelines with Session, use parametrized queries, window functions, or other advanced ClickHouse SQL features. Also use when the user explicitly mentions chdb.query(), ClickHouse SQL syntax, or wants cross-source SQL joins. Do NOT use for pandas-style DataFrame operations — use chdb-datastore instead.",
    "compatibility": "Requires Python 3.9+, macOS or Linux. pip install chdb."
}

chdb SQL — ClickHouse in Your Python Process

Run ClickHouse SQL directly in Python — no server needed. Query local files, remote databases, and cloud storage with full ClickHouse SQL power.

pip install chdb

Decision Tree: Pick the Right API

1. One-off query on files or databases → chdb.query()
2. Multi-step analysis with tables      → Session
3. DB-API 2.0 connection                → chdb.connect()
4. Pandas-style DataFrame operations    → Use chdb-datastore skill instead

chdb.query() — One Line, Any Data

import chdb

chdb.query("SELECT * FROM file('data.parquet', Parquet) WHERE price > 100 LIMIT 10")       # local files
chdb.query("SELECT * FROM mysql('db:3306', 'shop', 'orders', 'root', 'pass')")              # databases
chdb.query("SELECT * FROM s3('s3://bucket/data.parquet', NOSIGN) LIMIT 10")                 # cloud storage
chdb.query("SELECT * FROM deltaLake('s3://bucket/delta/table', NOSIGN) LIMIT 10")           # data lakes

# Cross-source join
chdb.query("""
    SELECT u.name, o.amount FROM mysql('db:3306', 'crm', 'users', 'root', 'pass') AS u
    JOIN file('orders.parquet', Parquet) AS o ON u.id = o.user_id ORDER BY o.amount DESC
""")

data = {"name": ["Alice", "Bob"], "score": [95, 87]}
chdb.query("SELECT * FROM Python(data) ORDER BY score DESC")                                # Python data
df = chdb.query("SELECT * FROM numbers(10)", "DataFrame")                                   # output formats
chdb.query("SELECT toDate({d:String}) + number FROM numbers({n:UInt64})",
    "DataFrame", params={"d": "2025-01-01", "n": 30})                                      # parametrized

Table functions → table-functions.md | SQL functions → sql-functions.md | Full API → api-reference.md

Session — Stateful Analysis Pipelines

from chdb import session as chs
sess = chs.Session("./analytics_db")   # persistent; Session() for in-memory

sess.query("CREATE TABLE users ENGINE=MergeTree() ORDER BY id AS SELECT * FROM mysql('db:3306','crm','users','root','pass')")
sess.query("CREATE TABLE events ENGINE=MergeTree() ORDER BY (ts,user_id) AS SELECT * FROM s3('s3://logs/events/*.parquet',NOSIGN)")
sess.query("""
    SELECT u.country, count() AS cnt, uniqExact(e.user_id) AS users
    FROM events e JOIN users u ON e.user_id = u.id
    WHERE e.ts >= today() - 7 GROUP BY u.country ORDER BY cnt DESC
""", "Pretty").show()
sess.close()

Connection API (DB-API 2.0)

from chdb import dbapi
conn = dbapi.connect()
cur = conn.cursor()
cur.execute("SELECT * FROM file('data.parquet', Parquet) WHERE value > 100")
print(cur.fetchall())
cur.close()
conn.close()

Troubleshooting

Problem Fix
ImportError: No module named 'chdb' pip install chdb
DB::Exception: FILE_NOT_FOUND Check file path; use absolute path or verify cwd
DB::Exception: Unknown table function Check function name spelling (e.g., deltaLake not deltalake)
Connection refused to remote DB Check host:port format; ensure remote DB allows connections
Environment check Run python scripts/verify_install.py (from skill directory)

References

Note: This skill teaches how to use chdb SQL. For pandas-style operations, use the chdb-datastore skill. For contributing to chdb source code, see CLAUDE.md in the project root.

Maple项目跨语言OpenTelemetry规范,定义自定义属性键、状态码格式及资源属性。用于编写或审查TS/Rust/Python中的遥测代码,确保与Tinybird数据视图兼容。
添加Span属性或设置状态 配置OTLP导出器或资源 审查涉及遥测的PR 通过WarehouseQueryService执行SQL查询
.agents/skills/maple-telemetry-conventions/SKILL.md
npx skills add MapleTechLabs/maple --skill maple-telemetry-conventions -g -y
SKILL.md
Frontmatter
{
    "name": "maple-telemetry-conventions",
    "version": "1.0.0",
    "description": "Maple's OpenTelemetry conventions — custom span attribute keys (`maple.*` vendor namespace, `query.context`, `db.query.*`, `result.*`, `cache.*`, `tenant.*`), Title Case status codes (`Ok`\/`Error`\/`Unset`), resource attribute dual-emit (`deployment.environment` + `deployment.environment.name`), span kinds, Tinybird MV pre-extracted columns, loop-prevention filters, and sampling. Use whenever writing or reviewing instrumentation code in any language (TypeScript, Rust, Python) in this repo — adding `setAttribute`\/`setAttributes`\/`record`\/`#[instrument(fields(...))]` calls, setting span status, configuring an OTLP exporter, defining a new resource attribute, or wiring a new query through `WarehouseQueryService.sqlQuery()`."
}

Maple Telemetry Conventions

Reference for the language-agnostic OpenTelemetry conventions Maple uses across TypeScript (apps/api, Cloudflare workers in lib/effect-sdk/), Rust (apps/ingest), and future Python services. These conventions are load-bearing — Tinybird materialized views pre-extract certain attribute keys into columns, dashboards filter on Title Case status strings, and sampling-aware throughput math relies on the SampleRate column. Use the exact attribute spellings here in every language.

When to apply

  • Adding setAttribute / Effect.annotateCurrentSpan / Span::current().record(...) / #[instrument(fields(...))] to any code path
  • Setting span status (Ok / Error / Unset)
  • Wiring a new query through WarehouseQueryService.sqlQuery() (the context and profile options become span attributes)
  • Configuring an OTLP exporter, tracer provider, or resource builder
  • Introducing a new pre-extracted MV column or a new vendor attribute under maple.*
  • Reviewing a PR that touches apps/api/src/services/WarehouseQueryService.ts, apps/ingest/src/main.rs, apps/api/src/app.ts, lib/effect-sdk/src/cloudflare/, or packages/domain/src/tinybird/materializations.ts

Index

  • rules/span-attributes.md — Master reference of every custom attribute key Maple emits, grouped by namespace, with file:line citations.
  • rules/status-and-kind.md — Title Case status code rule (Ok/Error/Unset) and span kind conventions (Server / Client / Internal).
  • rules/resource-attributes.mdservice.* identity, deployment.environment.name resolution order, the legacy deployment.environment dual-emit, and maple_org_id.
  • rules/language-bindings.md — Parallel TypeScript / Rust / Python snippets that emit the same attribute keys.
  • rules/mv-first-class-columns.md — Which span and resource attributes Tinybird MVs pre-extract into columns (and the rule for adding new ones).
  • rules/service-map-attribution.md — Required span and resource attributes for the service map to render edges, runtime icons, and platform badges. Includes the canonical peer.service registry.
  • rules/loop-prevention.md — The three guards that prevent Maple's self-traffic from creating a feedback loop: API TracerDisabledWhen, ingest loopback guard, sampling.

Quick reference

Topic Rule
Status codes Always Title Case: "Ok", "Error", "Unset". Never OK, ERROR, SUCCESS, FAILED.
Vendor namespace Custom attributes go under maple.*. Sub-namespaces: maple.ingest.*, maple.cloudflare.*.
Standard semconv Use OTel semconv keys verbatim: service.name, http.request.method, db.system.name, error.type.
Org identity orgId (camelCase) in TypeScript spans, maple.org_id (dotted) in Rust spans. Don't unify until MVs migrate.
Deployment env Dual-emit deployment.environment + deployment.environment.name. Keep both until MV coalesce() migration lands.
Warehouse SQL spans Every span from WarehouseQueryService.executeSql carries db.system.name, peer.service, db.query.text, db.query.fingerprint, db.duration_ms, result.rowCount, orgId, query.context, query.profile. Legacy spans (pre 2026-06) use db.statement*/db.system; warehouse readers coalesce both.
Service map Outbound spans need peer.service (HTTP/RPC) or db.system.name (DB) on a Client/Producer span. Resource attrs need process.runtime.name, cloud.platform, maple.sdk.type for runtime icon + platform badge. See rules/service-map-attribution.md.
Loop prevention Never remove HttpMiddleware.TracerDisabledWhen (apps/api/src/app.ts:169-175) or the ingest loopback guard (apps/ingest/src/main.rs:499-514).

Canonical references (do not modify from this skill)

  • apps/api/src/services/WarehouseQueryService.ts:441-510executeSql span emission (the canonical example for TS).
  • apps/ingest/src/otel.rs — Resource builder + platform detection + forward-span helper for Rust. The canonical example for Rust resource and outbound-span attribution.
  • apps/ingest/src/main.rshandle_signal and handle_cloudflare_logpush (search for tracing::info_span! with otel.kind = "server") — Server-kind span macros for OTLP inbound.
  • apps/api/src/app.ts:169-175TracerDisabledWhen filter.
  • lib/effect-sdk/src/cloudflare/index.tsMapleCloudflareSDK tracer setup.
  • packages/domain/src/tinybird/materializations.ts — MV SELECT lists that pre-extract attribute keys into columns.
专注优化注册后用户引导与激活体验,帮助用户快速达成“啊哈时刻”以提升留存。适用于涉及新用户上手、首次体验、空状态设计、激活率提升及减少流失的场景。
优化注册后引导流程 提升用户激活率 改善首次运行体验 缩短价值实现时间 设计空状态或引导清单 解决用户注册后不活跃问题
.agents/skills/onboarding-cro/SKILL.md
npx skills add MapleTechLabs/maple --skill onboarding-cro -g -y
SKILL.md
Frontmatter
{
    "name": "onboarding-cro",
    "metadata": {
        "version": "1.1.0"
    },
    "description": "When the user wants to optimize post-signup onboarding, user activation, first-run experience, or time-to-value. Also use when the user mentions \"onboarding flow,\" \"activation rate,\" \"user activation,\" \"first-run experience,\" \"empty states,\" \"onboarding checklist,\" \"aha moment,\" \"new user experience,\" \"users aren't activating,\" \"nobody completes setup,\" \"low activation rate,\" \"users sign up but don't use the product,\" \"time to value,\" or \"first session experience.\" Use this whenever users are signing up but not sticking around. For signup\/registration optimization, see signup-flow-cro. For ongoing email sequences, see email-sequence."
}

Onboarding CRO

You are an expert in user onboarding and activation. Your goal is to help users reach their "aha moment" as quickly as possible and establish habits that lead to long-term retention.

Initial Assessment

Check for product marketing context first: If .agents/product-marketing-context.md exists (or .claude/product-marketing-context.md in older setups), read it before asking questions. Use that context and only ask for information not already covered or specific to this task.

Before providing recommendations, understand:

  1. Product Context - What type of product? B2B or B2C? Core value proposition?
  2. Activation Definition - What's the "aha moment"? What action indicates a user "gets it"?
  3. Current State - What happens after signup? Where do users drop off?

Core Principles

1. Time-to-Value Is Everything

Remove every step between signup and experiencing core value.

2. One Goal Per Session

Focus first session on one successful outcome. Save advanced features for later.

3. Do, Don't Show

Interactive > Tutorial. Doing the thing > Learning about the thing.

4. Progress Creates Motivation

Show advancement. Celebrate completions. Make the path visible.


Defining Activation

Find Your Aha Moment

The action that correlates most strongly with retention:

  • What do retained users do that churned users don't?
  • What's the earliest indicator of future engagement?

Examples by product type:

  • Project management: Create first project + add team member
  • Analytics: Install tracking + see first report
  • Design tool: Create first design + export/share
  • Marketplace: Complete first transaction

Activation Metrics

  • % of signups who reach activation
  • Time to activation
  • Steps to activation
  • Activation by cohort/source

Onboarding Flow Design

Immediate Post-Signup (First 30 Seconds)

Approach Best For Risk
Product-first Simple products, B2C, mobile Blank slate overwhelm
Guided setup Products needing personalization Adds friction before value
Value-first Products with demo data May not feel "real"

Whatever you choose:

  • Clear single next action
  • No dead ends
  • Progress indication if multi-step

Onboarding Checklist Pattern

When to use:

  • Multiple setup steps required
  • Product has several features to discover
  • Self-serve B2B products

Best practices:

  • 3-7 items (not overwhelming)
  • Order by value (most impactful first)
  • Start with quick wins
  • Progress bar/completion %
  • Celebration on completion
  • Dismiss option (don't trap users)

Empty States

Empty states are onboarding opportunities, not dead ends.

Good empty state:

  • Explains what this area is for
  • Shows what it looks like with data
  • Clear primary action to add first item
  • Optional: Pre-populate with example data

Tooltips and Guided Tours

When to use: Complex UI, features that aren't self-evident, power features users might miss

Best practices:

  • Max 3-5 steps per tour
  • Dismissable at any time
  • Don't repeat for returning users

Multi-Channel Onboarding

Email + In-App Coordination

Trigger-based emails:

  • Welcome email (immediate)
  • Incomplete onboarding (24h, 72h)
  • Activation achieved (celebration + next step)
  • Feature discovery (days 3, 7, 14)

Email should:

  • Reinforce in-app actions, not duplicate them
  • Drive back to product with specific CTA
  • Be personalized based on actions taken

Handling Stalled Users

Detection

Define "stalled" criteria (X days inactive, incomplete setup)

Re-engagement Tactics

  1. Email sequence - Reminder of value, address blockers, offer help
  2. In-app recovery - Welcome back, pick up where left off
  3. Human touch - For high-value accounts, personal outreach

Measurement

Key Metrics

Metric Description
Activation rate % reaching activation event
Time to activation How long to first value
Onboarding completion % completing setup
Day 1/7/30 retention Return rate by timeframe

Funnel Analysis

Track drop-off at each step:

Signup → Step 1 → Step 2 → Activation → Retention
100%      80%       60%       40%         25%

Identify biggest drops and focus there.


Output Format

Onboarding Audit

For each issue: Finding → Impact → Recommendation → Priority

Onboarding Flow Design

  • Activation goal
  • Step-by-step flow
  • Checklist items (if applicable)
  • Empty state copy
  • Email sequence triggers
  • Metrics plan

Common Patterns by Product Type

Product Type Key Steps
B2B SaaS Setup wizard → First value action → Team invite → Deep setup
Marketplace Complete profile → Browse → First transaction → Repeat loop
Mobile App Permissions → Quick win → Push setup → Habit loop
Content Platform Follow/customize → Consume → Create → Engage

Experiment Ideas

When recommending experiments, consider tests for:

  • Flow simplification (step count, ordering)
  • Progress and motivation mechanics
  • Personalization by role or goal
  • Support and help availability

For comprehensive experiment ideas: See references/experiments.md


Task-Specific Questions

  1. What action most correlates with retention?
  2. What happens immediately after signup?
  3. Where do users currently drop off?
  4. What's your activation rate target?
  5. Do you have cohort analysis on successful vs. churned users?

Related Skills

  • signup-flow-cro: For optimizing the signup before onboarding
  • email-sequence: For onboarding email series
  • paywall-upgrade-cro: For converting to paid during/after onboarding
  • ab-test-setup: For testing onboarding changes
通过MCP构建、修复或审查Maple仪表板组件。涵盖原始JSON构造、数据源区分、自定义whereClause语法及聚合陷阱,指导处理多查询、公式图表等复杂场景,并强调验证查询有效性。
创建或更新仪表板组件 生成原始Widget JSON 处理QueryDraft形状 编写自定义whereClause 配置特定数据源的聚合与groupBy 处理公式图表辅助系列隐藏 验证MCP调用后的查询实际成功状态
skills/maple-dashboard-widgets/SKILL.md
npx skills add MapleTechLabs/maple --skill maple-dashboard-widgets -g -y
SKILL.md
Frontmatter
{
    "name": "maple-dashboard-widgets",
    "description": "Build, repair, or review Maple dashboard widgets via the MCP. Triggers on phrases like 'create_dashboard', 'add_dashboard_widget', 'update_dashboard_widget', 'dashboard widget JSON', 'QueryDraft', 'trace dashboard widget', 'Invalid input for getQueryBuilderTimeseries', or any session that submits raw widget JSON to the maple MCP. Covers the source-discriminated QueryDraft shape, the custom whereClause grammar, valid aggregations per data source, groupBy prefix conventions, the stat-widget `reduceToValue` transform, hiding auxiliary series on formula charts, and the verification step (MCP success ≠ query success)."
}

Maple dashboard widgets via MCP

When to use this skill

When you are constructing raw widget JSON for any of:

  • mcp__maple__create_dashboard with a dashboard_json payload
  • mcp__maple__add_dashboard_widget
  • mcp__maple__update_dashboard_widget

If you are creating a fresh dashboard, prefer the simplified widgets array on create_dashboard (SimpleWidgetSpec at apps/api/src/mcp/tools/create-dashboard.ts:94). It side-steps every trap below — fill in title, source, metric, optional group_by, optional service_name, and the tool builds the full shape for you. Raw JSON is for cases the simplified spec can't express (multi-query charts, formulas, hidden series, non-default transforms).

Trap 1 — Query drafts are source-discriminated

The query draft schema (packages/domain/src/http/query-engine.ts) is a union discriminated on dataSource. The metric-only fields — metricName, metricType, isMonotonic, signalSource — exist only on dataSource: "metrics" queries.

  • For dataSource: "traces" or "logs": do not include metricName, metricType, isMonotonic, or signalSource. They are not part of the trace/log query shape.
  • For dataSource: "metrics": include metricName (the metric to query), metricType, and optionally isMonotonic / signalSource.

Never add metricName/metricType/isMonotonic/signalSource to a trace or log query — they belong solely to dataSource: "metrics" queries. (Older stored dashboards were migrated to drop them; new widgets must not reintroduce them.)

Trap 2 — whereClause is a custom grammar, not SQL

Parser: packages/domain/src/where-clause.ts:7. Supported operators (the only supported operators):

Operator Example
= != service.name = "ingest", error.type != "Timeout"
> < >= <= http.request.body.size > 1000
contains !contains http.route contains "v1", http.route !contains "/health"
exists !exists maple.signal exists, db.system !exists

Rules:

  • Clauses join with AND (case-insensitive). No OR, no parentheses.
  • Keys are normalized to lowercase by the parser.
  • Quoted values use double quotes.
  • There is no IS NULL / IS NOT NULL. To require an attribute be present, use <key> exists; to require it absent, <key> !exists. This is the single most common mistake.
  • Attribute filters work directly. On dataSource: "traces" you can filter by any span/resource attribute — query.context = "tracesList", error.type != "Timeout", db.system = "clickhouse". Bare keys outside the small structured allowlist (service.name, span.name, deployment.environment, deployment.commit_sha, root_only, has_error) are auto-treated as span attributes; you can also write them explicitly as attr.<key> / resource.<key>. Cap: 5 attr.* + 5 resource.* filters per query.
  • Unhonored clauses are now rejected at write time. add_dashboard_widget / update_dashboard_widget / replace_dashboard_widgets run the builder before persisting and FAIL (nothing saved) if a clause can't be honored — e.g. exceeding the attr cap, or an unsupported logs/metrics filter key. (This used to be a silent drop.)

Wrong:

service.name = "ingest" AND maple.signal IS NOT NULL

Right:

service.name = "ingest" AND maple.signal exists

Minimum-viable trace chart widget JSON

Use as a template. Fill whereClause, groupBy, aggregation, display.title, display.unit, layout:

{
  "id": "w0",
  "visualization": "chart",
  "dataSource": {
    "endpoint": "custom_query_builder_timeseries",
    "params": {
      "queries": [
        {
          "id": "q-w0",
          "name": "A",
          "enabled": true,
          "hidden": false,
          "dataSource": "traces",
          "whereClause": "service.name = \"ingest\" AND maple.signal exists",
          "aggregation": "count",
          "stepInterval": "",
          "orderByDirection": "desc",
          "addOns": {
            "groupBy": true,
            "having": false,
            "orderBy": false,
            "limit": false,
            "legend": false
          },
          "groupBy": ["maple.signal"],
          "having": "",
          "orderBy": "",
          "limit": "",
          "legend": ""
        }
      ],
      "formulas": [],
      "comparison": { "mode": "none", "includePercentChange": true },
      "debug": false
    }
  },
  "display": {
    "title": "Requests by Signal",
    "chartId": "query-builder-bar",
    "chartPresentation": { "legend": "visible" },
    "stacked": true,
    "curveType": "linear",
    "unit": "number"
  },
  "layout": { "x": 0, "y": 0, "w": 6, "h": 4 }
}

Stat widget delta

For visualization: "stat", add dataSource.transform.reduceToValue. Transform schema is at apps/web/src/components/dashboard-builder/types.ts:42:

"transform": {
  "reduceToValue": { "field": "value", "aggregate": "sum" }
}

Valid aggregate values: "sum" | "first" | "count" | "avg" | "max" | "min". No "last". Without reduceToValue, the series array passes through to the renderer and the stat shows [object Object],....

Gauge widget delta

visualization: "gauge" renders the same scalar as a stat, but on a 180° radial arc. It needs the same reduceToValue transform as a stat widget. Add gauge presentation under display:

"display": {
  "unit": "percent",
  "gauge": { "min": 0, "max": 100 },
  "thresholds": [
    { "value": 70, "color": "var(--chart-3)" },
    { "value": 90, "color": "var(--destructive)" }
  ]
}

display.thresholds color the arc (the highest threshold ≤ the value wins) and place tick marks. gauge.min/max default to 0/100. Arc color falls back to var(--chart-1) when no threshold matches.

Threshold lines on time-series charts

display.thresholds also works on chart widgets — each entry draws a dashed horizontal ReferenceLine across line/area/bar charts, with an optional label. Reuse it to mark SLO/alert boundaries.

Valid aggregation values per dataSource

From normalizeTraceAggregation / normalizeMetricsAggregation in apps/web/src/components/dashboard-builder/ai/normalize-widget-proposal.ts:148:

  • traces: count, avg_duration, p50_duration, p95_duration, p99_duration, error_rate
  • metrics: rate, increase, avg, sum, count, min, max, p50, p95, p99
  • logs: count

rate / sum / increase are invalid for traces — a common mistake when porting metrics widgets to traces.

groupBy prefix conventions

The query-builder groupBy accepts a small literal allowlist per source; everything else MUST be prefixed attr.<key>. A token that isn't recognized and isn't attr.-prefixed is dropped — and now causes the widget mutation tools to reject the write rather than silently breaking the breakdown.

  • traces: bare literals are only service / service.name, span / span.name, status / status.code, http.method, none / all. Every other attribute needs the attr. prefixattr.maple.signal, attr.error.type, attr.http.response.status_code, attr.maple.org_id, etc. (Writing error.type bare in groupBy does NOT work — use attr.error.type.)
  • logs: bare literals only — service / service.name, severity, none. Logs do not support attr.* group by.
  • metrics: bare service / none; everything else uses attr.attr.signal, attr.status, attr.org_id.

If a groupBy on an attribute finds zero distinct values, the chart collapses to a single all series. inspect_chart_data now flags this as EMPTY_GROUPING (verdict broken) instead of silently showing the ungrouped total.

display.unit is mandatory

Always set display.unit on chart and stat widgets. The default is "number". Pick more specific where applicable:

  • duration_ms for latency aggregations (avg_duration, p50_duration, p95_duration, p99_duration)
  • percent for error_rate
  • number for count
  • bytes / GB for size aggregations

Hiding auxiliary queries on charts with formulas

When a chart uses formulas and the auxiliary queries shouldn't render on their own, query.hidden: true is not enough on its own for the raw-JSON path — that flag is only consumed by the UI builder to generate the actual transform. For raw JSON, pair it with dataSource.transform.hideSeries.baseNames:

"transform": {
  "hideSeries": { "baseNames": ["A", "B"] }
}

baseNames matches each hidden query's legend || name. Otherwise the auxiliary series render at full scale and skew percent-axis charts to absurd values (raw counts showing as "1200%").

Verification — MCP success ≠ query success

The widget mutation tools now do two things automatically that used to be silent gaps:

  • Pre-persist rejection: unhonored where-clause/groupBy clauses fail the write (nothing saved) — so a IS NULL, a 6th attr filter, or an unsupported logs/metrics key surfaces as an error instead of degrading to wrong/empty data.
  • Auto-validation: the response includes an inspect_chart_data summary (verdict + flags). inspect_chart_data now also evaluates formulas[] (so formula/hit-rate widgets verify end-to-end) and applies reduceToValue with the same first-numeric-field fallback the renderer uses (so stat-tile reducedValue reflects what renders). SUSPICIOUS_GAP is informational only — sparse/bursty data no longer downgrades the verdict.

Still confirm after submitting:

  1. Read the returned validation summary; if verdict is suspicious/broken, fix and resubmit.
  2. Call mcp__maple__inspect_chart_data for a deeper look, or mcp__maple__get_dashboard to read back the stored JSON, or load the dashboard URL.

Verdict flags worth knowing: EMPTY_GROUPING (groupBy found zero distinct values → one all series), METRIC_NOT_FOUND (a metrics widget references a metric name not in the warehouse — distinct from a real metric with no recent data), BUILDER_WARNINGS (a clause the engine couldn't honor).

If you see Invalid input for getQueryBuilderTimeseries, the culprit is almost always Trap 1 (metric fields on a trace/log query) or a malformed query draft.

Rebuilding many widgets at once

To replace a dashboard's whole widget list in one validated, atomic write, use mcp__maple__replace_dashboard_widgets (widgets_json = a JSON array of widget objects, same shape as widgets[] from get_dashboard). id and layout are optional per widget (auto-generated / auto-placed). Every widget is validated before anything persists — if one widget's query can't be honored, nothing is saved. This is the safe middle ground between N incremental add_dashboard_widget calls and the corruption-prone full dashboard_json replace.

Quick checklist before submitting widget JSON

  • Trace/log queries omit metricName/metricType/isMonotonic/signalSource; metrics queries include metricName + metricType.
  • whereClause uses only =, !=, >, <, >=, <=, contains, !contains, exists, !exists, joined by AND (no SQL IS NULL).
  • aggregation is valid for the chosen dataSource (no rate/sum on traces).
  • groupBy uses the right prefix: bare only for the per-source allowlist (traces: service/span/status/http.method); every other attribute needs attr.<key> (traces & metrics). Logs: bare service/severity only.
  • display.unit is set (and matches the aggregation — duration_ms, percent, etc.).
  • Stat widgets include dataSource.transform.reduceToValue.
  • Formula charts with hidden queries include dataSource.transform.hideSeries.baseNames.
  • After submitting, read the auto-validation summary; verify suspicious/broken widgets with inspect_chart_data or by loading the dashboard.

Home - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-07-13 14:41
浙ICP备14020137号-1 $Map of visitor$