# Quickstart

## 1. Scaffold
:::TuffCodeBlock{lang="bash"}
---
code: |
  pnpm dlx create-tuff-plugin my-plugin
  cd my-plugin
  pnpm install
---
:::
The scaffold includes the Manifest, `index.js` Prelude, optional UI directory, and baseline build configuration.

## 2. Manifest
Start with plugin identity, entrypoint, SDK version, category, permissions, and a CoreBox feature in `manifest.json`.

:::TuffCodeBlock{lang="json"}
---
code: |
  {
    "id": "com.example.my-plugin",
    "name": "my-plugin",
    "version": "0.1.0",
    "author": "Example",
    "sdkapi": 260626,
    "category": "utilities",
    "description": "Return a copy action from CoreBox.",
    "main": "index.js",
    "dev": { "enable": true },
    "permissions": {
      "required": [],
      "optional": ["clipboard.write"]
    },
    "permissionReasons": {
      "clipboard.write": "Copy processed text to the clipboard"
    },
    "features": [
      {
        "id": "my-plugin.copy",
        "name": "Copy Input Text",
        "desc": "Type text in CoreBox and copy it",
        "keywords": ["copy", "text"],
        "push": true,
        "acceptedInputTypes": ["text"],
        "commands": [
          { "type": "over", "value": ["copy"] }
        ]
      }
    ]
  }
---
:::

## 3. Prelude Script
`index.js` runs in the plugin sandbox and reads context APIs from `globalThis`.

:::TuffCodeBlock{lang="javascript"}
---
code: |
  const { clipboard, box, TuffItemBuilder } = globalThis
  const PLUGIN_NAME = 'my-plugin'
  const COPY_ACTION_ID = 'copy'

  function getQueryText(query) {
    return typeof query === 'string' ? query : query?.text ?? ''
  }

  module.exports = {
    async onFeatureTriggered(featureId, query) {
      const text = getQueryText(query).trim()
      if (!text) {
        return []
      }

      return [
        new TuffItemBuilder('my-plugin.copy-result')
          .setSource('plugin', 'plugin-features', PLUGIN_NAME)
          .setTitle('Copy Input Text')
          .setSubtitle(text.slice(0, 80))
          .setMeta({
            pluginName: PLUGIN_NAME,
            featureId,
            defaultAction: COPY_ACTION_ID,
          })
          .createAndAddAction(COPY_ACTION_ID, 'copy', 'Copy', text)
          .build(),
      ]
    },

    async onItemAction(item) {
      const action = item.actions?.find(action => action.id === COPY_ACTION_ID || action.type === 'copy')
      if (action?.payload) {
        clipboard.writeText(action.payload)
      }
      box?.hide?.()
    },
  }
---
:::

## 4. Debug
- `pnpm core:dev` keeps the plugin hot-reloaded.
- Logs show up in DevTools and `logs/plugins/<id>.log`.
- Use `logger` for plugin logs, and avoid writing sensitive parameters to logs.

## 5. Validate And Build
:::TuffCodeBlock{lang="bash"}
---
code: |
  tuff validate --strict
  tuff build
---
:::
`tuff build` creates `dist/build/` and a `.tpex` package. Before publishing, run `tuff publish --dry-run` to preview the upload.

Continue with [Plugin Development Workflow](./plugin-workflow.en.mdc).
