npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

youwee-sdk

v2.2.0

Published

Youwee plugin SDK for JavaScript runtimes.

Readme

youwee-sdk

youwee-sdk is the JavaScript and TypeScript authoring SDK for Youwee plugins.

This package is for building plugin workspaces. Youwee itself installs and runs packaged .ywp files, not raw source folders.

If you remember only one model, remember this:

  1. create a plugin workspace
  2. write and test the plugin inside that workspace
  3. build and sign it into a .ywp file
  4. import the .ywp file into Youwee

That is the only supported end-user distribution flow.


Table of contents

  1. Concepts
  2. Lifecycle
  3. Workspace layout
  4. Step-by-step workflow
  5. GitHub Actions in scaffolded workspaces
  6. Manifest reference
  7. Plugin module contract
  8. Execution context
  9. Results and mutations
  10. Internationalization
  11. Build and pack commands
  12. Packaged .ywp format
  13. Import into Youwee
  14. Debugging model
  15. Runtime and compatibility
  16. AI agent notes
  17. Troubleshooting
  18. Appendix: minimal example

Concepts

There are only two important objects in the Youwee plugin system:

1. Plugin workspace

A plugin workspace is the source folder used by the plugin author.

It contains:

  • plugin.json
  • package.json
  • src/
  • locales/
  • optional docs, examples, and assets

This is where you write code.

2. Packaged plugin

A packaged plugin is a .ywp file produced from a workspace.

It contains:

  • a packaged runtime manifest
  • bundled runtime output
  • checksums and build metadata
  • locale files and packaged assets

This is what Youwee installs and executes.

Important rule

Youwee does not treat a raw workspace as an installed plugin.

Workspaces are for development. .ywp is for installation and sharing.


Lifecycle

The expected lifecycle is:

  1. create a plugin workspace
  2. install workspace dependencies
  3. edit plugin.json
  4. write hook code in src/plugin.ts
  5. add locale files in locales/
  6. test locally
  7. build runtime output
  8. generate a signing key
  9. pack a signed .ywp file
  10. import the .ywp plugin file into Youwee
  11. enable the plugin and assign it to workflows

This flow is valid whether the workspace is created by:

  • Youwee's Create Workspace action
  • a manual scaffold
  • an AI agent

Workspace layout

Recommended workspace structure:

my-plugin/
  plugin.json
  package.json
  tsconfig.json
  README.md
  README.vi.md
  README.zh-CN.md
  CHANGELOG.md
  .gitignore
  .github/
    workflows/
      ci.yml
      release.yml
  src/
    plugin.ts
  locales/
    en.json
  examples/
    payload.download.completed.json
    result.success.json
    result.failure.json
  dist/
  release/

Notes:

  • src/ is authoring source.
  • src/plugin.ts is the default TypeScript entrypoint for new workspaces.
  • tsconfig.json is generated for editor diagnostics and CI type checking.
  • dist/ is generated by the build step.
  • release/ is generated by the pack step.
  • release/*.ywp is what you import into Youwee.
  • Create Workspace in Youwee also generates .github/workflows/ci.yml and .github/workflows/release.yml.
  • For plugin documentation shown inside Youwee, you can add localized README files such as README.vi.md or README.zh-CN.md. Youwee prefers README.<app-locale>.md and falls back to README.md.

Step-by-step workflow

1. Create a workspace

You can create a workspace in either of these ways:

  • use Create Workspace inside Youwee and choose a destination folder
  • create the folder manually and add the required files

The workspace should live in a location you control, such as:

  • a project folder
  • a git repository
  • a personal development directory

It should not be treated as part of Youwee's installed plugin storage.

2. Install dependencies

From the workspace root:

bun install

For JavaScript plugins:

  • Deno is the runtime used by Youwee to execute the plugin
  • Bun is only the local authoring toolchain used for install, build, and pack commands

At minimum, your workspace should depend on:

{
  "dependencies": {
    "youwee-sdk": "^2.2.0"
  }
}

3. Write the manifest

Create or edit plugin.json.

This file declares:

  • plugin identity
  • optional plugin icon
  • runtime support
  • triggers
  • requested permissions
  • compatibility
  • i18n metadata

See Manifest reference.

You can optionally set icon in plugin.json so Youwee shows a custom icon for the plugin. If you omit it, Youwee uses the default Puzzle icon. Youwee resolves the icon dynamically from Lucide, so you can use either:

  • PascalCase, for example Atom or ShieldCheck
  • kebab-case, for example package-open or circle-alert

Unknown icon names safely fall back to Puzzle.

4. Write the plugin module

Write the hook module in src/plugin.ts.

The module should export a plugin definition created by definePlugin(...).

See Plugin module contract.

5. Add locale files

If your plugin emits user-facing messages, add locale files under locales/.

Example:

locales/
  en.json
  vi.json
  zh-CN.json

See Internationalization.

6. Test locally

Typical local test commands:

bun run typecheck
bun run test:deno

These commands execute the shared SDK bootstrap against your source plugin module. They run the plugin through Deno, while the surrounding install/build/pack workflow still uses Bun as the local toolchain. The shared runtime-cli bootstrap sanitizes macOS linker environment variables before your plugin module loads. The local Deno check intentionally does not grant direct write/run permissions, so it can catch accidental direct Deno.write*, Deno.Command(...), or shell usage early. Full testing for app-mediated APIs such as ctx.youwee.fs and ctx.youwee.tools should be done by attaching the workspace in Youwee, because those APIs require the Youwee runtime bridge.

7. Build

Build bundled runtime output with the Bun toolchain:

bunx youwee-sdk build

This produces:

dist/plugin.cjs

8. Generate a signing key

Create a signing key:

bunx youwee-sdk keygen ./plugin.youwee-plugin-key.json

9. Pack

Create a signed packaged plugin with the Bun toolchain:

bunx youwee-sdk pack --private-key ./plugin.youwee-plugin-key.json

This produces:

release/<slug>.ywp

GitHub Actions in scaffolded workspaces

If you create a workspace from inside Youwee, the scaffold already includes:

  • .github/workflows/ci.yml
  • .github/workflows/release.yml

These workflows are meant for plugin authors, not for the Youwee app repository itself.

ci.yml

The CI workflow validates the workspace on push and pull request:

  • bun install
  • bun run typecheck
  • bun run build
  • bun run test:deno

This means the workflow uses Bun as the authoring toolchain, then runs a Deno runtime check to make sure the plugin still executes through the shared SDK runtime.

release.yml

The release workflow is a convenience flow for packaging and publishing your plugin repository releases.

It:

  1. restores your signing key from a GitHub secret
  2. runs bun install
  3. runs bun run typecheck
  4. runs bun run build
  5. runs bun run pack
  6. generates a .sha256 file
  7. uploads the .ywp and checksum file to the GitHub release

Here too, Bun is only the packaging toolchain. The installed JavaScript plugin still runs in Youwee through Deno.

Required GitHub secret

The scaffolded release workflow expects:

  • YOUWEE_PLUGIN_SIGNING_KEY

Store the entire JSON contents of plugin.youwee-plugin-key.json in that secret.

Recommended release flow

From your workspace:

bun install
bun run keygen
git add .
git commit -m "feat: prepare release"
git tag v0.1.0
git push origin main --tags

That tag triggers .github/workflows/release.yml in the plugin repository.

10. Import into Youwee

Open Youwee:

  1. go to Settings > Plugins
  2. choose Import Plugin
  3. select the .ywp file
  4. review plugin file info, compatibility, triggers, and permissions
  5. install it
  6. approve permissions
  7. enable it
  8. assign it to one or more workflows

Manifest reference

The source manifest is plugin.json.

Example:

{
  "id": "local.gg-drive",
  "slug": "gg-drive",
  "name": "GG Drive",
  "version": "0.1.0",
  "description": "Upload completed downloads to Google Drive.",
  "author": "Your Name",
  "homepage": "https://example.com",
  "repository": "https://github.com/example/gg-drive",
  "license": "MIT",
  "runtime": {
    "language": "javascript",
    "supportedProviders": ["deno"],
    "preferredProvider": "deno",
    "entrypoint": "src/plugin.ts"
  },
  "compatibility": {
    "appVersion": ">=0.15.0 <0.16.0",
    "sdkVersion": ">=2.2.0 <3.0.0"
  },
  "triggers": [
    "download.completed"
  ],
  "permissions": {
    "network": true,
    "fs": [
      "fs.payload-file.read",
      "fs.user-selected.write"
    ],
    "tools": [
      "tool.ffmpeg.run"
    ]
  },
  "timeoutSec": 300,
  "i18n": {
    "defaultLocale": "en",
    "supportedLocales": ["en", "vi", "zh-CN"],
    "directory": "locales"
  }
}

Identity fields

  • id: immutable package-style identifier
  • slug: readable short name used in paths and filenames
  • name: display name
  • version: plugin version

Runtime

For JavaScript plugins:

  • language must be "javascript"
  • supportedProviders must be a subset of:
    • "deno"
  • preferredProvider is optional
  • entrypoint points to the source module inside the workspace

Triggers

Currently supported trigger strings:

  • download.queued
  • download.beforeStart
  • download.completed
  • download.failed

Important:

  • plugin.json must use raw runtime strings
  • do not write values like "triggers.downloadCompleted" in plugin.json

Correct:

{
  "triggers": ["download.completed"]
}

Incorrect:

{
  "triggers": ["triggers.downloadCompleted"]
}

Permissions

permissions declares what the plugin may request from Youwee.

Fields:

  • network: boolean
  • fs: PluginFilesystemPermission[]
  • tools: PluginToolPermission[]

These values are:

  • shown to the user during install/enable
  • enforced by the Youwee runtime before execution

Supported filesystem capabilities:

  • fs.plugin.read
  • fs.plugin.write
  • fs.payload-file.read
  • fs.payload-directory.read
  • fs.payload-directory.write
  • fs.temp.read
  • fs.temp.write
  • fs.user-selected.read
  • fs.user-selected.write

Use capabilities instead of hardcoding absolute paths from the user's machine. For example, fs.user-selected.* should be paired with configFields that use file or directory inputs so Youwee can resolve the actual path on each machine.

Supported tool execution capabilities:

  • tool.ffmpeg.run
  • tool.ytdlp.run

Tool permissions are required before ctx.youwee.tools.ffmpeg.run(...) or ctx.youwee.tools.ytdlp.run(...) can execute.

Youwee does not grant direct Deno --allow-run. Tool execution is app-mediated:

  • plugin code calls ctx.youwee.tools.ffmpeg.run(...) or ctx.youwee.tools.ytdlp.run(...)
  • the SDK sends the request to Youwee's local runtime bridge
  • Youwee runs only the approved bundled/system tool binary
  • arguments are passed without a shell
  • dangerous command-like arguments are rejected
  • file inputs must stay inside approved read scopes
  • output paths must stay inside approved write scopes

Do not use Deno.Command(...), child_process, shell wrappers, or spawnCommand(...) for plugin tool execution. spawnCommand(...) is kept only as a compatibility export and rejects direct command execution in SDK 2.x.

Filesystem permission model

SDK 2.x also removes direct write access from installed plugins. Youwee does not grant direct Deno --allow-write for installed plugins.

Use ctx.youwee.fs for filesystem work:

const content = await ctx.youwee.fs.readText(ctx.file.path);
const bytes = await ctx.youwee.fs.readBytes(ctx.file.path);
await ctx.youwee.fs.ensureDir(outputDirectory);
await ctx.youwee.fs.writeText(`${outputDirectory}/result.txt`, content);
await ctx.youwee.fs.writeBytes(`${outputDirectory}/copy.bin`, bytes);
const tempDirectory = await ctx.youwee.fs.tempDir("my-plugin-");
const scratchFile = `${tempDirectory}/scratch.txt`;
await ctx.youwee.fs.writeText(scratchFile, "temporary data");
await ctx.youwee.fs.removeFile(scratchFile);

Filesystem calls are mediated by Youwee:

  • fs.plugin.read allows reading files from the installed plugin package/workspace
  • fs.plugin.write allows writing inside the plugin package/workspace only when explicitly approved
  • fs.payload-file.read allows reading the current downloaded file
  • fs.payload-directory.read allows reading and listing files beside the current downloaded file
  • fs.payload-directory.write allows writing files beside the current downloaded file
  • fs.temp.read and fs.temp.write allow app-managed temporary files
  • fs.user-selected.read and fs.user-selected.write resolve from user-selected file or directory config fields

Use readText(...) / writeText(...) for UTF-8 text files. Use readBase64(...), readBytes(...), writeBase64(...), or writeBytes(...) for binary files such as media, thumbnails, archives, and upload payloads. Use readDir(...) when the plugin has directory read permission and needs to list files in an approved directory.

Write permissions are still constrained even after approval. Youwee blocks dangerous write scopes such as the filesystem root, home directory root, startup/autostart locations, shell profile files, SSH/keychain/browser profile areas, application/system folders, and other sensitive OS locations.

Youwee also blocks dangerous output filenames/extensions such as shell scripts, executables, dynamic libraries, launch agents/services, desktop launchers, shortcuts, and similar executable formats.

For cleanup, use ctx.youwee.fs.removeFile(path). It can only remove regular files created during the current plugin run or files inside a Youwee-managed temp directory returned by ctx.youwee.fs.tempDir(...). It cannot remove directories, symlinks, dangerous output paths, or user files that existed before the plugin run started.

Plugin result mutations are validated too. If a plugin returns mutations.activeFilepath or mutations.extraFiles, those paths must be safe and inside approved write scopes before Youwee passes them to the next workflow step.

Network permission model

network: true allows the plugin runtime to make outbound network requests, for example through ctx.youwee.http, fetch, or app-mediated network features such as ctx.youwee.youtube.searchVideos(...).

If network is omitted or false, Youwee only grants the minimal local bridge access needed by SDK context APIs. Only request network: true when the plugin actually needs external HTTP access, such as calling Telegram, Discord, a webhook, or a public API.

YouTube keyword search

Plugins can ask Youwee to perform the same app-managed YouTube keyword search used by the YouTube download page:

const results = await ctx.youwee.youtube.searchVideos({
  query: "free hd video no copyright",
  limit: 20,
  filters: {
    uploadDate: "thisMonth",
    duration: "medium",
    sort: "viewCount",
    features: ["hd", "creativeCommons"],
  },
});

for (const video of results.videos) {
  ctx.log.info("Found YouTube video", {
    title: video.title,
    url: video.url,
    channel: video.channel,
  });
}

Requirements:

  • use youwee-sdk >=2.2.0 and set the plugin compatibility range accordingly
  • plugin.json must request "network": true
  • the user must approve the plugin's network permission in Youwee
  • the plugin does not provide cookies, authorization headers, visitor IDs, or YouTube session identifiers
  • limit is clamped by Youwee to the supported range

Manifest example:

{
  "permissions": {
    "network": true
  }
}

Supported filters:

  • uploadDate: "today", "thisWeek", "thisMonth", "thisYear"
  • duration: "short", "medium", "long"
  • sort: "relevance", "viewCount" (YouTube labels this as popularity)
  • features: "live", "fourK", "hd", "subtitles", "creativeCommons", "threeSixty", "vr180", "threeD", "hdr"

Use continuation to load more results with the same query/filter context:

const firstPage = await ctx.youwee.youtube.searchVideos({
  query: "lofi study music",
  limit: 20,
});

if (firstPage.continuation) {
  const nextPage = await ctx.youwee.youtube.searchVideos({
    query: "lofi study music",
    continuation: firstPage.continuation,
    limit: 20,
  });

  ctx.log.info("Loaded more videos", { count: nextPage.videos.length });
}

The returned videos use this shape:

interface YoutubeSearchVideo {
  id: string;
  url: string;
  title: string;
  thumbnail?: string | null;
  duration?: string | null;
  channel?: string | null;
  viewCountText?: string | null;
  publishedTimeText?: string | null;
}

This API returns search results only. It does not enqueue downloads by itself. If a plugin wants to persist the selected URLs somewhere, write them to an approved output path or include them in the plugin result metadata.

Plugin configuration fields

Use configFields in plugin.json for plugin-defined settings that Youwee should render automatically.

Supported input types:

  • text
  • textarea
  • password
  • number
  • boolean
  • file
  • directory
  • select
  • multi-select

Example:

{
  "configFields": [
    {
      "key": "driveFolderId",
      "inputType": "text",
      "label": "Google Drive folder ID",
      "required": true,
      "placeholder": "1AbCdEf..."
    },
    {
      "key": "uploadMode",
      "inputType": "select",
      "label": "Upload mode",
      "defaultValue": "copy",
      "options": [
        { "value": "copy", "label": "Copy" },
        { "value": "move", "label": "Move" }
      ]
    },
    {
      "key": "apiToken",
      "inputType": "password",
      "label": "API token",
      "required": true,
      "sensitive": true
    }
  ]
}

Field rules:

  • label is shown in Youwee instead of the raw key
  • required fields must be set before the plugin can run
  • defaultValue is used when no saved value exists
  • sensitive fields are stored and passed to runtime, but Youwee masks them in the UI after saving
  • options are required for select and multi-select
  • min, max, and step are only valid for number

Do not use permissions.env for plugin-defined settings. It is obsolete in this SDK version.

Compatibility

Compatibility ranges allow Youwee to block incompatible packages early.

  • compatibility.appVersion
  • compatibility.sdkVersion

These are checked:

  • when inspecting/importing a packaged plugin
  • again before execution

Plugin module contract

Example:

import { definePlugin, triggers, type PluginDefinition } from "youwee-sdk";

const plugin = definePlugin({
  meta: {
    name: "GG Drive",
    version: "0.1.0",
    description: "Upload completed downloads to Google Drive."
  },
  hooks: {
    [triggers.downloadCompleted]: async (ctx) => {
      ctx.log.info(ctx.i18n.t("upload.started", { filename: ctx.file.name }));

      return ctx.ok(
        ctx.i18n.t("upload.success", { filename: ctx.file.name }),
      );
    },
  },
} satisfies PluginDefinition);

export default plugin;

Requirements:

  • export a plugin definition object
  • include meta
  • include hooks
  • hook keys should map to supported trigger names
  • hook values must be async or sync functions

The SDK validates this shape at runtime.


Execution context

Each hook receives ctx.

Top-level sections include:

  • ctx.trigger
  • ctx.download
  • ctx.file
  • ctx.media
  • ctx.chain
  • ctx.config
  • ctx.env
  • ctx.log
  • ctx.youwee
  • ctx.i18n

Common fields:

ctx.download.jobId
ctx.file.path
ctx.file.name
ctx.media.url
ctx.media.title
ctx.download.kind

Youwee app bridge examples:

await ctx.youwee.fs.readText(ctx.file.path);
await ctx.youwee.tools.ytdlp.run(["--version"]);
await ctx.youwee.youtube.searchVideos({ query: "ambient video", limit: 10 });

Plugin configuration:

ctx.config.get("driveFolderId")
ctx.config.require("apiToken")
ctx.config.has("uploadMode")
ctx.config.all()

Use ctx.config for values declared in plugin.json under configFields.

Use ctx.env only when you intentionally need raw runtime environment variables provided by Youwee or the host process.


Results and mutations

A hook may return:

ctx.ok(message?, metadata?)
ctx.fail(message?, metadata?)

The result contract supports:

  • success
  • message
  • artifacts
  • metadata
  • mutations

Example:

return {
  success: true,
  message: "Prepared merged file",
  mutations: {
    activeFilepath: "/tmp/final.mp4",
    activeFilename: "final.mp4",
  },
};

Supported mutation fields:

  • activeFilepath
  • activeFilename
  • extraFiles
  • metadataPatch

These mutations are merged into workflow chain state and passed to later plugins in the same run.


Internationalization

If your plugin emits user-facing text, use plugin-local translations.

Manifest

Declare i18n in plugin.json:

{
  "i18n": {
    "defaultLocale": "en",
    "supportedLocales": ["en", "vi"],
    "directory": "locales"
  }
}

Locale files

Example:

locales/
  en.json
  vi.json

locales/en.json

{
  "upload.started": "Starting upload for {{filename}}",
  "upload.success": "Uploaded {{filename}}"
}

Runtime API

Use:

ctx.i18n.t("upload.started", { filename: ctx.file.name })
ctx.i18n.has("upload.success")
ctx.i18n.raw("upload.success")

The SDK automatically uses:

  • the current Youwee app locale
  • the app fallback locale
  • the plugin's default locale

Fallback order:

  1. current app locale
  2. app fallback locale
  3. plugin defaultLocale
  4. raw key

Build and pack commands

Build

bunx youwee-sdk build

Build does the following:

  1. loads plugin.json
  2. validates the manifest
  3. validates trigger names
  4. validates i18n configuration
  5. bundles the source entrypoint to dist/plugin.cjs

Pack

bunx youwee-sdk pack --private-key ./plugin.youwee-plugin-key.json

Pack does the following:

  1. runs the build step
  2. creates a runtime manifest.json
  3. creates build.json
  4. creates checksums.json
  5. creates signature.json
  6. writes release/<slug>.ywp

Packaged .ywp format

.ywp is the distribution format consumed by Youwee.

It is a ZIP-based archive with a fixed layout.

Expected contents:

manifest.json
build.json
checksums.json
signature.json
dist/
  plugin.cjs
locales/
  ...
assets/
  ...
README.md
CHANGELOG.md

Important rules:

  • packaged plugins ship runtime output, not source
  • packaged plugins do not need to ship node_modules
  • packaged plugins do not need to ship vendor/youwee-sdk
  • Youwee runs the packaged dist/plugin.cjs through its shared runtime bootstrap

build.json

build.json records:

  • package format
  • format version
  • SDK version used to build the package
  • bundle metadata

checksums.json

checksums.json records file-level SHA-256 checksums.

Youwee validates these checksums during import.

signature.json

signature.json records:

  • the signing algorithm
  • the signer key id
  • the signer fingerprint
  • the embedded public key
  • the signed timestamp
  • the signed payload for checksums.json

Youwee requires a valid ed25519 signature before installation.


Import into Youwee

Only signed .ywp files are supported as the end-user import format.

Youwee does not use raw folders or ZIP source packages as the productized install format.

At import time, Youwee validates:

  • .ywp structure
  • manifest.json
  • build.json
  • checksums.json
  • signature.json
  • compatibility ranges
  • runtime entrypoint existence
  • declared locale files

If validation fails, import is blocked.


Debugging model

The recommended debugging model is:

  1. work inside a plugin workspace
  2. attach that workspace to Youwee when you want live source-level debugging
  3. edit source files directly inside the workspace
  4. let the next trigger run the updated source without rebuilding
  5. run local tests against the source module when needed
  6. generate a signing key if needed
  7. pack a signed .ywp
  8. import the packaged plugin into Youwee to verify packaged install/runtime behavior

This keeps development and installation separate while still giving you a fast debug loop inside Youwee.

Live workspace debugging in Youwee

Youwee can attach a plugin workspace directly for development.

Use this when you want:

  • real trigger execution inside Youwee
  • source-level iteration without rebuilding on every edit
  • the same app runtime bridge (ctx, approved tools, i18n, env, workflow payloads)

Recommended flow:

  1. create or open a plugin workspace
  2. run bun install
  3. in Youwee, go to Settings > Plugins
  4. choose Attach Workspace
  5. select the workspace folder
  6. enable the attached plugin
  7. add it to the workflow you want to test
  8. trigger the real app flow
  9. edit src/plugin.ts or plugin.json
  10. trigger again

No .ywp rebuild is required for this loop.

Attach Workspace is also the recommended way to test APIs that require the app-mediated bridge:

  • ctx.youwee.fs.*
  • ctx.youwee.tools.ffmpeg.run(...)
  • ctx.youwee.tools.ytdlp.run(...)
  • permission approval and rejection behavior
  • workflow chain mutations between plugins

Important:

  • Attach Workspace is a development-only workflow
  • it runs source files directly from the selected workspace
  • it does not use packaged plugin integrity checks
  • it still uses Youwee's permission bridge for filesystem and tool execution
  • only attach workspaces you control and trust

Use .ywp packaging when you want to:

  • verify packaged behavior
  • share the plugin with another user
  • test the exact artifact Youwee will install for end users

Recommended local commands:

bun install
bun run typecheck
bun run test:deno
bunx youwee-sdk build
bunx youwee-sdk keygen ./plugin.youwee-plugin-key.json
bunx youwee-sdk pack --private-key ./plugin.youwee-plugin-key.json

Runtime and compatibility

Supported JavaScript providers

Plugin manifests must declare supported providers explicitly.

Supported JavaScript providers:

  • deno

Compatibility helpers

The SDK exports:

  • SDK_VERSION
  • parseSemver(version)
  • compareSemver(a, b)
  • satisfiesVersionRange(version, range)
  • checkAppVersionCompatibility(currentVersion, range)
  • assertCompatibleAppVersion(currentVersion, range)

Runtime access:

ctx.youwee.sdk.version
ctx.youwee.sdk.checkAppVersion(">=0.15.0 <0.16.0")
ctx.youwee.sdk.assertAppVersion(">=0.15.0 <0.16.0")

Use these when your plugin depends on specific app features.


AI agent notes

If you are generating plugin code with an AI agent, follow these rules:

  1. treat the plugin as a workspace first, not an installed app plugin
  2. write or update plugin.json
  3. write the hook module in src/plugin.ts
  4. use raw trigger strings in plugin.json
  5. use triggers.* only inside source code
  6. place translations in locales/*.json
  7. assume the final deliverable is a signed .ywp package
  8. do not invent per-plugin runner files
  9. do not assume Youwee imports raw source folders

When generating user instructions, always present the flow as:

  • create workspace
  • install dependencies
  • build
  • keygen
  • pack with --private-key
  • import .ywp

Troubleshooting

The manifest says my trigger is invalid

Use raw runtime strings in plugin.json.

Correct:

"triggers": ["download.completed"]

Incorrect:

"triggers": ["triggers.downloadCompleted"]

The plugin works locally but Youwee refuses to import it

Check:

  • compatibility ranges
  • required locale files
  • packaged entrypoint existence
  • checksums.json
  • signature.json
  • package format version

The plugin imports but does not run

Check:

  • the plugin is enabled
  • required permissions are approved
  • the selected provider is supported by the manifest
  • the workflow for the trigger includes the plugin

The plugin says the bridge is unavailable

ctx.youwee.fs and ctx.youwee.tools require Youwee's app-mediated runtime bridge.

Use one of these flows:

  • attach the workspace in Youwee and trigger the real workflow
  • import and run a signed .ywp package in Youwee

bun run test:deno is a fast contract check for the plugin module, payload parsing, translations, and hook shape. It is not a full replacement for bridge-backed permission testing.

Deno reports missing --allow-write or --allow-run

That usually means the plugin is using direct Deno APIs or a shell instead of the SDK context APIs.

Use:

  • ctx.youwee.fs.readText(...)
  • ctx.youwee.fs.readBytes(...)
  • ctx.youwee.fs.readBase64(...)
  • ctx.youwee.fs.writeText(...)
  • ctx.youwee.fs.writeBytes(...)
  • ctx.youwee.fs.writeBase64(...)
  • ctx.youwee.fs.removeFile(...)
  • ctx.youwee.fs.ensureDir(...)
  • ctx.youwee.tools.ffmpeg.run(...)
  • ctx.youwee.tools.ytdlp.run(...)

Do not use:

  • Deno.Command(...)
  • Deno.writeTextFile(...)
  • Deno.mkdir(...)
  • child_process.spawn(...)
  • bash, sh, cmd, powershell, python, node, bun, or deno as plugin-run commands

The plugin needs user configuration

Declare user-provided values with configFields, not permissions.env. Youwee renders those fields in the plugin settings UI and injects the saved values into the runtime.


Appendix: minimal example

plugin.json

{
  "id": "local.example",
  "slug": "example",
  "name": "Example",
  "version": "0.1.0",
  "runtime": {
    "language": "javascript",
    "supportedProviders": ["deno"],
    "preferredProvider": "deno",
    "entrypoint": "src/plugin.ts"
  },
  "triggers": ["download.completed"],
  "timeoutSec": 60
}

src/plugin.ts

import { definePlugin, triggers, type PluginDefinition } from "youwee-sdk";

const plugin = definePlugin({
  meta: {
    name: "Example",
    version: "0.1.0",
  },
  hooks: {
    [triggers.downloadCompleted]: async (ctx) => {
      ctx.log.info("Plugin executed", {
        filename: ctx.file.name,
      });

      return ctx.ok(`Processed ${ctx.file.name}`);
    },
  },
} satisfies PluginDefinition);

export default plugin;

Build and pack:

bun install
bun run typecheck
bunx youwee-sdk keygen ./plugin.youwee-plugin-key.json
bunx youwee-sdk build
bunx youwee-sdk pack --private-key ./plugin.youwee-plugin-key.json