exposes the duress lockdown as an endpoint (needs the distress password in the
body, rate-limited) so it can be tripped from a shortcut/webhook, not only by
typing the distress password at the login form.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a duress password (DISTRESS_PASSWORD env): entering it at login trips an
in-memory full lockdown — all new logins (password + passkey) and every existing
session are refused until the server is restarted, and the login itself returns a
normal "invalid credentials" so it gives nothing away. Also add
POST /api/auth/blacklist-token as a clearly-named alias for revoking the current
JWT (same effect as signout).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
APP_ORIGINS now includes EXPO_PUBLIC_CLIENT_ORIGIN when set, so the mobile app's
officer:// custom-scheme origin passes origin validation without weakening it for
anyone else. The origin (with its embedded token) lives in .env, not the repo.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
/download-video now delegates to the ReClip service (its own yt-dlp) and runs as
a background job: POST returns a jobId immediately and the client polls
GET /download-video/:jobId, so a large download no longer holds one long request
open (which was 504-ing behind the reverse proxy). the finished file is streamed
into the user's folder with a title-based name.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
a script task that goes silent for a while (e.g. ffmpeg's faststart pass rewrites
a huge file for minutes with no output) would hit Bun's default 120s websocket
idle timeout → close(ws) → killTree killed the task mid-run, corrupting the
output. now the executor pings the socket every 30s while a task runs, resetting
the idle timer.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AUTOMATION_CONTEXT.md and SIDECAR.md described the multi-tenant scope
model, the seed/ tree, the marketplace, and a single sidecar owning the
queue — all superseded. Fix the stale doc pointers in opencode.json and
CLAUDE.md, and point at TODO.md as the source of truth on direction.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
proc.kill() only signalled the direct child (bash), leaving a running ffmpeg
grandchild orphaned and still encoding. now killTree() walks /proc, SIGTERMs the
whole subtree (bash + ffmpeg), then SIGKILLs any straggler after 2s.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
when a folder has mixed audio/subtitle layouts, the mismatch banner now offers a
toggle: convert the matching group (pick tracks, skip the rest) OR convert every
video keeping all audio + all subtitles (no picking, nothing skipped). the second
mode hides the pickers and clears the track-selection inputs so run.sh keeps
everything. only shown for tasks with track pickers (Convert Video).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
selecting several files/folders and running a task now runs it on the whole
selection instead of just the right-clicked item. reuses the folder + include
mechanism: the run targets the current folder scoped to the selected files
(and everything under selected folders). track-picker tasks scope their probe
to the selection too.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
the video used max-w/max-h so a low-res source stayed at native size surrounded
by black. switch to w-full h-full object-contain so it scales up to fill the
player (windowed and fullscreen) while preserving aspect ratio.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
renders each subtitle as a keep checkbox + editable label field, seeded from the
file's current tracks. reuses the existing probe / folder-grouping / include
plumbing, so it works on a single file or a whole season.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
converting a folder now recursively probes every video and groups episodes by
track layout (audio language+channels, subtitle language). the pickers are
driven off the largest matching group; episodes with a different layout are
listed as skipped and converted separately. adds a /probe-folder endpoint and
passes the majority file list to run.sh as INPUT_INCLUDE.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
convert video's task modal now probes a single file on open, lists its audio
and subtitle tracks as inline checkboxes, and passes the selection to the
script. audio defaults to all kept, subtitles to none; a "none" sentinel keeps
empty (folder mode) meaning keep-all.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add /file-browser/audio-tracks (list) and raw?audio=N (serve the video with
a chosen audio track via a cached -c copy remux, byte-range seekable). The
player shows an always-visible selector outside the controls; switching
reloads the source and restores the playhead. Works in Chromium, which
doesn't expose the audioTracks API.
Temp remux file keeps the real extension (a .tmp suffix breaks ffmpeg's
muxer selection); concurrent requests for a track share one remux.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add /file-browser/subtitles (list text subtitle tracks) and /subtitle-vtt
(extract one as WebVTT on demand); the VideoRenderer fetches the list,
renders <track> elements, and shows a CC selector to switch/turn off subs.
Track labels prefer title, then handler_name (mp4), then a real language,
falling back to Track N — so untagged tracks aren't shown as "und".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New POST /api/rescan re-runs the boot item setup (ensureItemDirs +
ensureToolLoader) and returns live item counts; the header button calls it
and invalidates the item query caches so the UI refetches from disk.
Also fix ensure-tool-loader to write into OFFICER_ITEMS_DIR/extensions (the
runtime read path) instead of the now-unread DATA_PATH/extensions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the marketplace service dependency and the native/global/user
scope tiers with a single external directory ($OFFICER_ITEMS_DIR) holding
skills, tools, tasks, processes and extensions as plain files.
- tasks move from Postgres to TASK.md files (new file-backed task layer);
task editing now works, which the DB path never supported
- skills/tools/processes collapse into one shared file router (single dir)
- remove the marketplace client (sync-marketplace/sync-version) and the
boot-time sync; pi-bridge/pi-manager/sandbox point at the flat store
- drop the dead tasks + vestigial skills/tools/processes/extensions +
item_chats tables (migration 0004)
- one-time migration script exports DB tasks and consolidates disk items
Migration verified: all 6 tasks round-trip through the runtime parser
identically to their DB rows (pipeline steps, triggers, script impls and
agentic bodies all intact).
NOTE: not yet functionally tested end-to-end — every item (each task mode,
tool, skill, extension) still needs to be run/exercised in the app before
this is trusted. To be done manually.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Navigation is now session-local state that always starts at home. The
unscoped browser still mirrors its folder into files/currentPath so the
Create Dashboard flow keeps defaulting a new dashboard's cwd to it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>