youwee-sdk
v2.2.0
Published
Youwee plugin SDK for JavaScript runtimes.
Maintainers
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:
- create a plugin workspace
- write and test the plugin inside that workspace
- build and sign it into a
.ywpfile - import the
.ywpfile into Youwee
That is the only supported end-user distribution flow.
Table of contents
- Concepts
- Lifecycle
- Workspace layout
- Step-by-step workflow
- GitHub Actions in scaffolded workspaces
- Manifest reference
- Plugin module contract
- Execution context
- Results and mutations
- Internationalization
- Build and pack commands
- Packaged
.ywpformat - Import into Youwee
- Debugging model
- Runtime and compatibility
- AI agent notes
- Troubleshooting
- 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.jsonpackage.jsonsrc/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:
- create a plugin workspace
- install workspace dependencies
- edit
plugin.json - write hook code in
src/plugin.ts - add locale files in
locales/ - test locally
- build runtime output
- generate a signing key
- pack a signed
.ywpfile - import the
.ywpplugin file into Youwee - enable the plugin and assign it to workflows
This flow is valid whether the workspace is created by:
- Youwee's
Create Workspaceaction - 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.tsis the default TypeScript entrypoint for new workspaces.tsconfig.jsonis generated for editor diagnostics and CI type checking.dist/is generated by the build step.release/is generated by the pack step.release/*.ywpis what you import into Youwee.Create Workspacein Youwee also generates.github/workflows/ci.ymland.github/workflows/release.yml.- For plugin documentation shown inside Youwee, you can add localized README files such as
README.vi.mdorREADME.zh-CN.md. Youwee prefersREADME.<app-locale>.mdand falls back toREADME.md.
Step-by-step workflow
1. Create a workspace
You can create a workspace in either of these ways:
- use
Create Workspaceinside 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 installFor JavaScript plugins:
Denois the runtime used by Youwee to execute the pluginBunis 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 exampleAtomorShieldCheckkebab-case, for examplepackage-openorcircle-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(...).
5. Add locale files
If your plugin emits user-facing messages, add locale files under locales/.
Example:
locales/
en.json
vi.json
zh-CN.jsonSee Internationalization.
6. Test locally
Typical local test commands:
bun run typecheck
bun run test:denoThese 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 buildThis produces:
dist/plugin.cjs8. Generate a signing key
Create a signing key:
bunx youwee-sdk keygen ./plugin.youwee-plugin-key.json9. Pack
Create a signed packaged plugin with the Bun toolchain:
bunx youwee-sdk pack --private-key ./plugin.youwee-plugin-key.jsonThis produces:
release/<slug>.ywpGitHub 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 installbun run typecheckbun run buildbun 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:
- restores your signing key from a GitHub secret
- runs
bun install - runs
bun run typecheck - runs
bun run build - runs
bun run pack - generates a
.sha256file - uploads the
.ywpand 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 --tagsThat tag triggers .github/workflows/release.yml in the plugin repository.
10. Import into Youwee
Open Youwee:
- go to
Settings > Plugins - choose
Import Plugin - select the
.ywpfile - review plugin file info, compatibility, triggers, and permissions
- install it
- approve permissions
- enable it
- 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 identifierslug: readable short name used in paths and filenamesname: display nameversion: plugin version
Runtime
For JavaScript plugins:
languagemust be"javascript"supportedProvidersmust be a subset of:"deno"
preferredProvideris optionalentrypointpoints to the source module inside the workspace
Triggers
Currently supported trigger strings:
download.queueddownload.beforeStartdownload.completeddownload.failed
Important:
plugin.jsonmust use raw runtime strings- do not write values like
"triggers.downloadCompleted"inplugin.json
Correct:
{
"triggers": ["download.completed"]
}Incorrect:
{
"triggers": ["triggers.downloadCompleted"]
}Permissions
permissions declares what the plugin may request from Youwee.
Fields:
network: booleanfs: 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.readfs.plugin.writefs.payload-file.readfs.payload-directory.readfs.payload-directory.writefs.temp.readfs.temp.writefs.user-selected.readfs.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.runtool.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(...)orctx.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.readallows reading files from the installed plugin package/workspacefs.plugin.writeallows writing inside the plugin package/workspace only when explicitly approvedfs.payload-file.readallows reading the current downloaded filefs.payload-directory.readallows reading and listing files beside the current downloaded filefs.payload-directory.writeallows writing files beside the current downloaded filefs.temp.readandfs.temp.writeallow app-managed temporary filesfs.user-selected.readandfs.user-selected.writeresolve from user-selectedfileordirectoryconfig 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.0and set the plugin compatibility range accordingly plugin.jsonmust 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
limitis 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:
texttextareapasswordnumberbooleanfiledirectoryselectmulti-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:
labelis shown in Youwee instead of the raw keyrequiredfields must be set before the plugin can rundefaultValueis used when no saved value existssensitivefields are stored and passed to runtime, but Youwee masks them in the UI after savingoptionsare required forselectandmulti-selectmin,max, andstepare only valid fornumber
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.appVersioncompatibility.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.triggerctx.downloadctx.filectx.mediactx.chainctx.configctx.envctx.logctx.youweectx.i18n
Common fields:
ctx.download.jobId
ctx.file.path
ctx.file.name
ctx.media.url
ctx.media.title
ctx.download.kindYouwee 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:
successmessageartifactsmetadatamutations
Example:
return {
success: true,
message: "Prepared merged file",
mutations: {
activeFilepath: "/tmp/final.mp4",
activeFilename: "final.mp4",
},
};Supported mutation fields:
activeFilepathactiveFilenameextraFilesmetadataPatch
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.jsonlocales/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:
- current app locale
- app fallback locale
- plugin
defaultLocale - raw key
Build and pack commands
Build
bunx youwee-sdk buildBuild does the following:
- loads
plugin.json - validates the manifest
- validates trigger names
- validates i18n configuration
- bundles the source entrypoint to
dist/plugin.cjs
Pack
bunx youwee-sdk pack --private-key ./plugin.youwee-plugin-key.jsonPack does the following:
- runs the build step
- creates a runtime
manifest.json - creates
build.json - creates
checksums.json - creates
signature.json - 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.mdImportant 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.cjsthrough 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:
.ywpstructuremanifest.jsonbuild.jsonchecksums.jsonsignature.json- compatibility ranges
- runtime entrypoint existence
- declared locale files
If validation fails, import is blocked.
Debugging model
The recommended debugging model is:
- work inside a plugin workspace
- attach that workspace to Youwee when you want live source-level debugging
- edit source files directly inside the workspace
- let the next trigger run the updated source without rebuilding
- run local tests against the source module when needed
- generate a signing key if needed
- pack a signed
.ywp - 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:
- create or open a plugin workspace
- run
bun install - in Youwee, go to
Settings > Plugins - choose
Attach Workspace - select the workspace folder
- enable the attached plugin
- add it to the workflow you want to test
- trigger the real app flow
- edit
src/plugin.tsorplugin.json - 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 Workspaceis 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.jsonRuntime and compatibility
Supported JavaScript providers
Plugin manifests must declare supported providers explicitly.
Supported JavaScript providers:
deno
Compatibility helpers
The SDK exports:
SDK_VERSIONparseSemver(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:
- treat the plugin as a workspace first, not an installed app plugin
- write or update
plugin.json - write the hook module in
src/plugin.ts - use raw trigger strings in
plugin.json - use
triggers.*only inside source code - place translations in
locales/*.json - assume the final deliverable is a signed
.ywppackage - do not invent per-plugin runner files
- 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.jsonsignature.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
.ywppackage 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, ordenoas 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