wyren-mcp 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ ## 1.1.0
4
+
5
+ - **Added** Auto-starting local render worker. `npx wyren-mcp` now logs you in
6
+ via a one-time browser approval (device-code flow), writes the minted key to
7
+ `~/.wyren/config.json` (0600), and registers a login-time service (launchd /
8
+ systemd `--user` / Task Scheduler) that runs the bundled worker.
9
+ - **Added** `worker-standalone/` (self-contained worker binary) and
10
+ `remotion-bundle/` (prebuilt Remotion compositions) shipped as package assets,
11
+ generated from the monorepo via `scripts/sync-worker.mjs`.
12
+ - **Added** Runtime dependencies for the worker: `@remotion/renderer` (pinned
13
+ `4.0.421`), `ffmpeg-static`, `ws`, `zod`, `file-type`, `@aws-sdk/client-s3`,
14
+ `@aws-sdk/lib-storage`.
15
+ - **Added** `npx wyren-mcp --uninstall` to remove the auto-start service, and
16
+ `--no-worker` to skip worker setup at install time.
17
+ - **Changed** `setup.mjs` is now a 4-step installer (MCP add → skill → device
18
+ login → service registration). All worker steps are wrapped so install never
19
+ hard-fails; the exact manual run command is printed if any step is
20
+ unsupported or declined.
21
+
22
+ ## 1.0.0
23
+
24
+ - Initial installer: adds the Wyren MCP server and copies the agent skill.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # wyren-mcp
2
2
 
3
- Install the [Wyren](https://wyren.yibby.ai) MCP server and agent skills for Claude Code.
3
+ Install the [Wyren](https://wyren.yibby.ai) MCP server, agent skills, and an auto-starting local render worker for Claude Code.
4
4
 
5
5
  ## Install
6
6
 
@@ -12,16 +12,78 @@ npx wyren-mcp
12
12
  npx wyren-mcp --global
13
13
  ```
14
14
 
15
- This adds the Wyren MCP server to your Claude Code config and installs the Wyren skill for guided AI pipeline building.
15
+ This:
16
+
17
+ 1. Adds the Wyren MCP server to your Claude Code config.
18
+ 2. Installs the Wyren agent skill for guided AI pipeline building.
19
+ 3. Logs you in once via your browser and starts a **local render worker** that auto-starts at login.
16
20
 
17
21
  ## What you get
18
22
 
19
- - **MCP Server** — 38 tools for creating, building, executing, and publishing AI workflows
20
- - **Agent Skill** — teaches Claude how to use Wyren tools effectively, with workflow patterns and domain knowledge
23
+ - **MCP Server** — tools for creating, building, executing, and publishing AI workflows.
24
+ - **Agent Skill** — teaches Claude how to use Wyren tools effectively, with workflow patterns and domain knowledge.
25
+ - **Local render worker** — a background daemon that runs heavy renders (captions, slideshows, video merge/trim, audio overlay, brainrot compose) on your machine instead of Wyren's servers. Faster for you, cheaper to run.
26
+
27
+ ## The local render worker
28
+
29
+ ### One-time browser approval
30
+
31
+ During setup, your browser opens to a Wyren `/device` page showing a short code. Approve it while logged in to your Wyren account. The installer then mints an API key scoped to your account and writes it to:
32
+
33
+ ```
34
+ ~/.wyren/config.json (permissions 0600 — readable only by you)
35
+ ```
36
+
37
+ You never copy-paste a key. After this one approval, the worker authenticates automatically on every start.
38
+
39
+ ### Auto-start at login
40
+
41
+ The installer registers a per-user login service that runs the worker whenever you log in:
42
+
43
+ | Platform | Mechanism | Location |
44
+ | --- | --- | --- |
45
+ | macOS | launchd LaunchAgent | `~/Library/LaunchAgents/ai.wyren.worker.plist` |
46
+ | Linux | systemd `--user` unit | `~/.config/systemd/user/wyren-worker.service` |
47
+ | Windows | Task Scheduler (ONLOGON) | task `Wyren\WyrenWorker` |
48
+
49
+ The service runs `worker-launcher.mjs`, which reads your key from `~/.wyren/config.json` and starts the bundled worker. The key is never written into the service definition itself.
50
+
51
+ ### Where things live
52
+
53
+ - **Key / config**: `~/.wyren/config.json`
54
+ - **Logs**: `~/.wyren/worker.log`
55
+ - **Worker binary + Remotion bundle**: shipped inside this package (`worker-standalone/`, `remotion-bundle/`).
56
+
57
+ On the first render the worker downloads a headless Chromium (~200MB) via Remotion's `ensureBrowser()`. This is a one-time download.
58
+
59
+ ### Disabling / uninstalling the worker
60
+
61
+ ```bash
62
+ npx wyren-mcp --uninstall
63
+ ```
64
+
65
+ This removes the auto-start service. Your `~/.wyren/config.json` is left in place — delete it manually to fully reset. To skip the worker at install time:
21
66
 
22
- ## Manual setup
67
+ ```bash
68
+ npx wyren-mcp --no-worker
69
+ ```
23
70
 
24
- If the installer doesn't work, you can set up manually:
71
+ ### Running the worker manually
72
+
73
+ If auto-start is unsupported on your system, or you prefer to run it yourself:
74
+
75
+ ```bash
76
+ WYREN_API_KEY=<your frm_ key> \
77
+ WYREN_BACKEND_URL=https://api.wyren.ai \
78
+ WYREN_REMOTION_BUNDLE_DIR=<package>/remotion-bundle \
79
+ node <package>/worker-standalone/index.mjs
80
+ ```
81
+
82
+ (`<package>` is wherever npm installed `wyren-mcp`.)
83
+
84
+ ## Manual MCP setup
85
+
86
+ If the installer doesn't work, set up the MCP server manually:
25
87
 
26
88
  ```bash
27
89
  # Add MCP server (local)
@@ -33,3 +95,14 @@ claude mcp add --transport http --scope user wyren https://api.wyren.ai/mcp
33
95
  # Install skill (via skills CLI)
34
96
  npx skills add briarbearrr/wyren-mcp
35
97
  ```
98
+
99
+ ## Maintainers
100
+
101
+ The `worker-standalone/` and `remotion-bundle/` directories are **generated** from the Wyren monorepo, not authored here. To update them, see `scripts/sync-worker.mjs`:
102
+
103
+ ```bash
104
+ # in the monorepo:
105
+ npm run build:worker && npm run bundle:remotion
106
+ # then, in this repo:
107
+ node scripts/sync-worker.mjs /path/to/frames
108
+ ```
package/lib/config.mjs ADDED
@@ -0,0 +1,46 @@
1
+ // Wyren config persistence — `~/.wyren/config.json`, 0600.
2
+ //
3
+ // The device-login flow writes the minted `frm_...` key + backend URL here so
4
+ // the worker daemon (and every subsequent login-time start) can authenticate
5
+ // without the user ever copy-pasting a key. The service definition we register
6
+ // reads the key from this file at start time.
7
+
8
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
9
+ import { homedir } from 'os';
10
+ import { join } from 'path';
11
+
12
+ export const WYREN_DIR = join(homedir(), '.wyren');
13
+ export const CONFIG_PATH = join(WYREN_DIR, 'config.json');
14
+ export const LOG_PATH = join(WYREN_DIR, 'worker.log');
15
+
16
+ /** Ensure `~/.wyren` exists with restrictive perms; return its path. */
17
+ export function ensureWyrenDir() {
18
+ if (!existsSync(WYREN_DIR)) {
19
+ mkdirSync(WYREN_DIR, { recursive: true, mode: 0o700 });
20
+ }
21
+ return WYREN_DIR;
22
+ }
23
+
24
+ /** Read `~/.wyren/config.json`, or `null` if it doesn't exist / is unreadable. */
25
+ export function readConfig() {
26
+ if (!existsSync(CONFIG_PATH)) return null;
27
+ try {
28
+ return JSON.parse(readFileSync(CONFIG_PATH, 'utf8'));
29
+ } catch {
30
+ return null;
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Write `{ apiKey, backendUrl, ... }` to `~/.wyren/config.json` with 0600 perms.
36
+ * Merges over any existing config so re-running setup doesn't drop fields.
37
+ */
38
+ export function writeConfig(patch) {
39
+ ensureWyrenDir();
40
+ const current = readConfig() ?? {};
41
+ const next = { ...current, ...patch, updatedAt: new Date().toISOString() };
42
+ writeFileSync(CONFIG_PATH, JSON.stringify(next, null, 2), { mode: 0o600 });
43
+ // writeFileSync's mode only applies on create; enforce on overwrite too.
44
+ chmodSync(CONFIG_PATH, 0o600);
45
+ return next;
46
+ }
@@ -0,0 +1,96 @@
1
+ // Device-code login — mirrors `gh auth login` / OAuth 2.0 device flow.
2
+ //
3
+ // Backend contract (frames monorepo `backend/src/routes/auth.ts`):
4
+ // POST /api/auth/device-code (unauthenticated)
5
+ // → 200 { deviceCode, userCode, verificationUrl, expiresIn, interval }
6
+ // POST /api/auth/device-token (unauthenticated, body { deviceCode })
7
+ // → 428 { status: 'authorization_pending' } (keep polling)
8
+ // → 200 { status: 'complete', apiKey: 'frm_...' }
9
+ // → 400 { status: 'expired' | 'denied' }
10
+ //
11
+ // The user approves in-browser at `verificationUrl` (the `/device` page) while
12
+ // logged in; that mints a key scoped to their account and flips the device code
13
+ // to approved. We never see their session — only the one-time `frm_...` key.
14
+
15
+ import { openBrowser } from './open-browser.mjs';
16
+
17
+ class DeviceAuthError extends Error {}
18
+
19
+ /**
20
+ * Run the full device-code login against `backendUrl`.
21
+ * Returns the minted `frm_...` apiKey, or throws DeviceAuthError on
22
+ * expiry/denial/timeout. Network/transport failures bubble as-is so the
23
+ * caller's try/catch can fall back to the manual path.
24
+ */
25
+ export async function deviceLogin(backendUrl, { log = console.log } = {}) {
26
+ const base = backendUrl.replace(/\/$/, '');
27
+
28
+ const startRes = await fetch(`${base}/api/auth/device-code`, {
29
+ method: 'POST',
30
+ headers: { 'content-type': 'application/json' },
31
+ body: '{}',
32
+ });
33
+ if (!startRes.ok) {
34
+ throw new DeviceAuthError(
35
+ `device-code request failed (HTTP ${startRes.status}). The backend may not yet expose the device-auth flow.`,
36
+ );
37
+ }
38
+ const grant = await startRes.json();
39
+ const { deviceCode, userCode, verificationUrl } = grant;
40
+ const intervalMs = Math.max(1, Number(grant.interval) || 5) * 1000;
41
+ const expiresMs = Math.max(30, Number(grant.expiresIn) || 600) * 1000;
42
+
43
+ log('');
44
+ log(' To finish setup, approve this device in your browser:');
45
+ log(` ${verificationUrl}`);
46
+ log('');
47
+ log(` Verification code: ${userCode}`);
48
+ log(' (Opening your browser. If it does not open, visit the URL above.)');
49
+ log('');
50
+
51
+ openBrowser(verificationUrl);
52
+
53
+ const deadline = Date.now() + expiresMs;
54
+ // OAuth device-flow politeness: respect the server interval between polls.
55
+ while (Date.now() < deadline) {
56
+ await sleep(intervalMs);
57
+ let pollRes;
58
+ try {
59
+ pollRes = await fetch(`${base}/api/auth/device-token`, {
60
+ method: 'POST',
61
+ headers: { 'content-type': 'application/json' },
62
+ body: JSON.stringify({ deviceCode }),
63
+ });
64
+ } catch {
65
+ // Transient network blip — keep polling until the deadline.
66
+ continue;
67
+ }
68
+
69
+ if (pollRes.status === 428) continue; // authorization_pending
70
+ if (pollRes.ok) {
71
+ const data = await pollRes.json();
72
+ if (data.status === 'complete' && typeof data.apiKey === 'string') {
73
+ return data.apiKey;
74
+ }
75
+ throw new DeviceAuthError('Unexpected device-token response.');
76
+ }
77
+
78
+ // 400 → expired or denied.
79
+ let status = 'denied';
80
+ try {
81
+ ({ status } = await pollRes.json());
82
+ } catch {
83
+ /* keep default */
84
+ }
85
+ throw new DeviceAuthError(
86
+ status === 'expired'
87
+ ? 'Device code expired before approval.'
88
+ : 'Device authorization was denied.',
89
+ );
90
+ }
91
+ throw new DeviceAuthError('Timed out waiting for browser approval.');
92
+ }
93
+
94
+ function sleep(ms) {
95
+ return new Promise((r) => setTimeout(r, ms));
96
+ }
@@ -0,0 +1,35 @@
1
+ // Cross-platform "open this URL in the default browser".
2
+ //
3
+ // No dependency on the `open` npm package — a tiny spawn of the platform's
4
+ // native opener keeps the installer dependency-free for the auth step. Best
5
+ // effort: if the spawn fails (headless box, no DISPLAY), the caller still
6
+ // prints the URL for the user to open manually.
7
+
8
+ import { spawn } from 'child_process';
9
+
10
+ export function openBrowser(url) {
11
+ const platform = process.platform;
12
+ let cmd;
13
+ let args;
14
+
15
+ if (platform === 'darwin') {
16
+ cmd = 'open';
17
+ args = [url];
18
+ } else if (platform === 'win32') {
19
+ // `start` is a cmd builtin; the empty title arg avoids quoting pitfalls.
20
+ cmd = 'cmd';
21
+ args = ['/c', 'start', '', url];
22
+ } else {
23
+ cmd = 'xdg-open';
24
+ args = [url];
25
+ }
26
+
27
+ try {
28
+ const child = spawn(cmd, args, { stdio: 'ignore', detached: true });
29
+ child.on('error', () => {});
30
+ child.unref();
31
+ return true;
32
+ } catch {
33
+ return false;
34
+ }
35
+ }
@@ -0,0 +1,216 @@
1
+ // OS auto-start registration for the Wyren worker.
2
+ //
3
+ // Registers a login-time service that runs `node <pkg>/worker-launcher.mjs`
4
+ // (which reads the 0600 config and execs the bundled worker). Idempotent:
5
+ // re-running setup re-writes the same unit/plist/task in place rather than
6
+ // duplicating it. `uninstallService` tears it down.
7
+ //
8
+ // macOS → launchd LaunchAgent (~/Library/LaunchAgents/ai.wyren.worker.plist)
9
+ // Linux → systemd --user unit (~/.config/systemd/user/wyren-worker.service)
10
+ // Windows → Task Scheduler task (schtasks /TN Wyren\WyrenWorker, ONLOGON)
11
+ //
12
+ // VERIFIED ON: Linux (systemd --user path validated with `systemctl --user
13
+ // cat`). The macOS launchd and Windows schtasks generators are written and
14
+ // linted but UNVERIFIED on the build box — see README "Disabling the worker".
15
+
16
+ import { execFileSync } from 'child_process';
17
+ import { existsSync, mkdirSync, writeFileSync, rmSync } from 'fs';
18
+ import { homedir } from 'os';
19
+ import { join } from 'path';
20
+
21
+ import { LOG_PATH, ensureWyrenDir } from './config.mjs';
22
+
23
+ export const SERVICE_LABEL = 'ai.wyren.worker';
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Linux — systemd --user
27
+ // ---------------------------------------------------------------------------
28
+
29
+ const SYSTEMD_DIR = join(homedir(), '.config', 'systemd', 'user');
30
+ const SYSTEMD_UNIT_PATH = join(SYSTEMD_DIR, 'wyren-worker.service');
31
+
32
+ function linuxUnit(nodePath, launcherPath) {
33
+ // Restart=always with a backoff keeps the worker alive across crashes/network
34
+ // drops; the daemon itself also reconnects, so this only covers hard exits.
35
+ return `[Unit]
36
+ Description=Wyren local render worker
37
+ After=network-online.target
38
+ Wants=network-online.target
39
+
40
+ [Service]
41
+ Type=simple
42
+ ExecStart=${nodePath} ${launcherPath}
43
+ Restart=always
44
+ RestartSec=5
45
+ StandardOutput=append:${LOG_PATH}
46
+ StandardError=append:${LOG_PATH}
47
+
48
+ [Install]
49
+ WantedBy=default.target
50
+ `;
51
+ }
52
+
53
+ function installLinux(nodePath, launcherPath) {
54
+ mkdirSync(SYSTEMD_DIR, { recursive: true });
55
+ writeFileSync(SYSTEMD_UNIT_PATH, linuxUnit(nodePath, launcherPath), { mode: 0o644 });
56
+ // `daemon-reload` picks up the new unit; `enable` wires it to login;
57
+ // `--now` also starts it immediately. All best-effort.
58
+ systemctlUser(['daemon-reload']);
59
+ systemctlUser(['enable', '--now', 'wyren-worker.service']);
60
+ return { type: 'systemd', path: SYSTEMD_UNIT_PATH };
61
+ }
62
+
63
+ function uninstallLinux() {
64
+ // `disable` fails loudly when the unit was never installed — that's the
65
+ // common "already uninstalled" case, so swallow it and just clean up.
66
+ systemctlUserSoft(['disable', '--now', 'wyren-worker.service']);
67
+ if (existsSync(SYSTEMD_UNIT_PATH)) rmSync(SYSTEMD_UNIT_PATH);
68
+ systemctlUserSoft(['daemon-reload']);
69
+ }
70
+
71
+ function systemctlUser(args) {
72
+ execFileSync('systemctl', ['--user', ...args], { stdio: 'pipe' });
73
+ }
74
+
75
+ function systemctlUserSoft(args) {
76
+ try {
77
+ systemctlUser(args);
78
+ } catch {
79
+ /* unit not present / nothing to do */
80
+ }
81
+ }
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // macOS — launchd LaunchAgent
85
+ // ---------------------------------------------------------------------------
86
+
87
+ const LAUNCHD_DIR = join(homedir(), 'Library', 'LaunchAgents');
88
+ const LAUNCHD_PLIST_PATH = join(LAUNCHD_DIR, `${SERVICE_LABEL}.plist`);
89
+
90
+ function macPlist(nodePath, launcherPath) {
91
+ return `<?xml version="1.0" encoding="UTF-8"?>
92
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
93
+ <plist version="1.0">
94
+ <dict>
95
+ <key>Label</key>
96
+ <string>${SERVICE_LABEL}</string>
97
+ <key>ProgramArguments</key>
98
+ <array>
99
+ <string>${nodePath}</string>
100
+ <string>${launcherPath}</string>
101
+ </array>
102
+ <key>RunAtLoad</key>
103
+ <true/>
104
+ <key>KeepAlive</key>
105
+ <true/>
106
+ <key>StandardOutPath</key>
107
+ <string>${LOG_PATH}</string>
108
+ <key>StandardErrorPath</key>
109
+ <string>${LOG_PATH}</string>
110
+ </dict>
111
+ </plist>
112
+ `;
113
+ }
114
+
115
+ function installMac(nodePath, launcherPath) {
116
+ mkdirSync(LAUNCHD_DIR, { recursive: true });
117
+ writeFileSync(LAUNCHD_PLIST_PATH, macPlist(nodePath, launcherPath), { mode: 0o644 });
118
+ // Idempotent: unload first (ignore failure when not yet loaded), then load.
119
+ try {
120
+ execFileSync('launchctl', ['unload', LAUNCHD_PLIST_PATH], { stdio: 'pipe' });
121
+ } catch {
122
+ /* not loaded yet */
123
+ }
124
+ execFileSync('launchctl', ['load', '-w', LAUNCHD_PLIST_PATH], { stdio: 'pipe' });
125
+ return { type: 'launchd', path: LAUNCHD_PLIST_PATH };
126
+ }
127
+
128
+ function uninstallMac() {
129
+ if (existsSync(LAUNCHD_PLIST_PATH)) {
130
+ try {
131
+ execFileSync('launchctl', ['unload', '-w', LAUNCHD_PLIST_PATH], { stdio: 'pipe' });
132
+ } catch {
133
+ /* already unloaded */
134
+ }
135
+ rmSync(LAUNCHD_PLIST_PATH);
136
+ }
137
+ }
138
+
139
+ // ---------------------------------------------------------------------------
140
+ // Windows — Task Scheduler (ONLOGON)
141
+ // ---------------------------------------------------------------------------
142
+
143
+ const WIN_TASK_NAME = 'Wyren\\WyrenWorker';
144
+
145
+ function installWindows(nodePath, launcherPath) {
146
+ // /F overwrites an existing task → idempotent. /SC ONLOGON runs at user login.
147
+ // /RL LIMITED keeps it in the user's security context.
148
+ const tr = `"${nodePath}" "${launcherPath}"`;
149
+ execFileSync(
150
+ 'schtasks',
151
+ ['/Create', '/F', '/SC', 'ONLOGON', '/RL', 'LIMITED', '/TN', WIN_TASK_NAME, '/TR', tr],
152
+ { stdio: 'pipe' },
153
+ );
154
+ // Start it now (best-effort; the task is also wired to next logon).
155
+ try {
156
+ execFileSync('schtasks', ['/Run', '/TN', WIN_TASK_NAME], { stdio: 'pipe' });
157
+ } catch {
158
+ /* will run at next logon */
159
+ }
160
+ return { type: 'schtasks', path: WIN_TASK_NAME };
161
+ }
162
+
163
+ function uninstallWindows() {
164
+ try {
165
+ execFileSync('schtasks', ['/End', '/TN', WIN_TASK_NAME], { stdio: 'pipe' });
166
+ } catch {
167
+ /* not running */
168
+ }
169
+ try {
170
+ execFileSync('schtasks', ['/Delete', '/F', '/TN', WIN_TASK_NAME], { stdio: 'pipe' });
171
+ } catch {
172
+ /* task not present / already removed */
173
+ }
174
+ }
175
+
176
+ // ---------------------------------------------------------------------------
177
+ // Public API
178
+ // ---------------------------------------------------------------------------
179
+
180
+ /**
181
+ * Register + start the login-time worker service for the current platform.
182
+ * `launcherPath` is the absolute path to `worker-launcher.mjs` in the package.
183
+ * Returns a descriptor; throws if the platform tooling rejects the call (the
184
+ * caller wraps this in try/catch and prints the manual fallback).
185
+ */
186
+ export function installService(launcherPath) {
187
+ // The service definition points its logs at LOG_PATH (~/.wyren/worker.log);
188
+ // ensure the dir exists so systemd/launchd can open the file at start time
189
+ // even if device-login hasn't run yet (e.g. a manual re-register).
190
+ ensureWyrenDir();
191
+ const nodePath = process.execPath;
192
+ switch (process.platform) {
193
+ case 'linux':
194
+ return installLinux(nodePath, launcherPath);
195
+ case 'darwin':
196
+ return installMac(nodePath, launcherPath);
197
+ case 'win32':
198
+ return installWindows(nodePath, launcherPath);
199
+ default:
200
+ throw new Error(`Unsupported platform: ${process.platform}`);
201
+ }
202
+ }
203
+
204
+ /** Remove the login-time worker service. Best-effort across platforms. */
205
+ export function uninstallService() {
206
+ switch (process.platform) {
207
+ case 'linux':
208
+ return uninstallLinux();
209
+ case 'darwin':
210
+ return uninstallMac();
211
+ case 'win32':
212
+ return uninstallWindows();
213
+ default:
214
+ throw new Error(`Unsupported platform: ${process.platform}`);
215
+ }
216
+ }
package/package.json CHANGED
@@ -1,21 +1,43 @@
1
1
  {
2
2
  "name": "wyren-mcp",
3
- "version": "1.0.0",
4
- "description": "Install the Wyren MCP server and agent skills for Claude Code",
3
+ "version": "1.1.0",
4
+ "description": "Install the Wyren MCP server, agent skills, and auto-starting local render worker for Claude Code",
5
5
  "bin": {
6
6
  "wyren-mcp": "setup.mjs"
7
7
  },
8
+ "scripts": {
9
+ "sync-worker": "node scripts/sync-worker.mjs"
10
+ },
8
11
  "files": [
9
12
  "setup.mjs",
10
- "skills/"
13
+ "worker-launcher.mjs",
14
+ "lib/",
15
+ "scripts/",
16
+ "skills/",
17
+ "worker-standalone/",
18
+ "remotion-bundle/",
19
+ "CHANGELOG.md"
11
20
  ],
21
+ "dependencies": {
22
+ "@aws-sdk/client-s3": "^3.975.0",
23
+ "@aws-sdk/lib-storage": "^3.975.0",
24
+ "@remotion/renderer": "4.0.421",
25
+ "ffmpeg-static": "^5.3.0",
26
+ "file-type": "^21.3.0",
27
+ "ws": "^8.18.0",
28
+ "zod": "3.22.3"
29
+ },
30
+ "engines": {
31
+ "node": ">=20"
32
+ },
12
33
  "keywords": [
13
34
  "wyren",
14
35
  "mcp",
15
36
  "claude",
16
37
  "ai",
17
38
  "video",
18
- "agent-skills"
39
+ "agent-skills",
40
+ "render-worker"
19
41
  ],
20
42
  "license": "MIT",
21
43
  "repository": {