From ec46992b79b0bf4724ce47efc1dbed804e6d3dd8 Mon Sep 17 00:00:00 2001 From: piccoloman Date: Fri, 11 Sep 2026 15:32:24 -0400 Subject: [PATCH] bootstrapped run 0.3.7 --- .agents/skills/pr-screenshot/SKILL.md | 68 +++++++++++++ .agents/skills/verify-local/SKILL.md | 25 +++++ .claude/skills/pr-screenshot/SKILL.md | 68 +++++++++++++ .claude/skills/verify-local/SKILL.md | 25 +++++ .codepress/dev-server/Dockerfile.web | 20 ++++ .codepress/dev-server/recipe.json | 22 +++++ .codepress/dev-server/vite.config.ts | 23 +++++ .codepress/factory.json | 9 ++ .codepress/verify-local/recipe.json | 133 ++++++++++++++++++++++++++ .codex/hooks.json | 46 +++++++++ 10 files changed, 439 insertions(+) create mode 100644 .agents/skills/pr-screenshot/SKILL.md create mode 100644 .agents/skills/verify-local/SKILL.md create mode 100644 .claude/skills/pr-screenshot/SKILL.md create mode 100644 .claude/skills/verify-local/SKILL.md create mode 100644 .codepress/dev-server/Dockerfile.web create mode 100644 .codepress/dev-server/recipe.json create mode 100644 .codepress/dev-server/vite.config.ts create mode 100644 .codepress/factory.json create mode 100644 .codepress/verify-local/recipe.json create mode 100644 .codex/hooks.json diff --git a/.agents/skills/pr-screenshot/SKILL.md b/.agents/skills/pr-screenshot/SKILL.md new file mode 100644 index 000000000..21149c62f --- /dev/null +++ b/.agents/skills/pr-screenshot/SKILL.md @@ -0,0 +1,68 @@ +--- +name: pr-screenshot +description: Capture FreeCut frontend PR preview screenshots and full-motion videos using its existing Playwright setup. +user_invocable: true +codepress_generated: true +--- + +# FreeCut PR previews + +Use for visible changes in `src/features/**/*.tsx`, `src/components/**/*.tsx`, `src/routes/**/*.tsx`, `src/**/*.css`, and `packages/freecut-editor/src/**`. Skip documentation, infrastructure and test-only changes without visual effects. This is PR-preview evidence; it does not prove ship-gate frontend behavior or hosted readiness. During Factory init capture locally only: no commits, pushes, PR edits or asset publication. + +## Map changes to captures + +| Changed surface | Page/state | +| --- | --- | +| Workspace gate, branding | `/projects` before workspace selection | +| Projects | `/projects`, `/projects/new` with disposable OPFS workspace | +| Editor, timeline, media, preview, effects, export, settings | `/editor/` reached by creating a disposable project; open the changed panel | +| Published host surface | `/tests/browser/host-responsive.html` existing preview fixture | +| Docs, changelog | `/docs`, `/changelog` | + +Read `git diff origin/codepress-main...HEAD --name-only` and map every affected visible area. Static layout needs screenshots; interactions/animation/dragging need full-motion WebM, optionally a screenshot too. Capture meaningful final state and nearby context, not a loading shell. For responsive changes add 390x844 to desktop 1440x1100. Use locator visibility and bounding boxes before capturing; avoid excessively long full-page images. + +## Runtime and fixtures + +This root owns npm/package-lock.json and Playwright (`playwright/test`, not a new @playwright/test install). Node 22+ is recommended. Run `npm ci --no-audit --no-fund` when dependencies need setup. Existing `playwright.mobile.config.ts` uses system Chrome, a single worker and real Vite at 127.0.0.1:4178 with `npm run dev -- --port 4178 --strictPort`. Other configs cover caption/layout refresh. Reuse their infrastructure and selectors, preserve all customer config. If Chrome is absent, use the installed Playwright chromium runtime; install with `npm exec -- playwright install chromium` from this root if needed and authorized. No application backend or login exists; no credentials or auth bypass is needed. + +For capture-only specs, create an owned temporary `tests/browser/_codepress-pr-preview.spec.ts` and a temporary config importing the mobile config, overriding `testMatch` to that filename, `testDir` to this checkout's tests/browser, outputDir to an owned /tmp directory, `reuseExistingServer: false`, and viewport to 1440x1100. Keep `channel: chrome` if available, otherwise override channel to undefined for installed Chromium. Require the port to be free; never kill a user's server. Run `npm exec -- playwright test --config --workers=1`. Vite must return HTTP 200 within its bounded config timeout. Preserve COOP/COEP headers. In a hosted runner use only the connected CodePress MicroVM lifecycle; never start customer servers on its control plane or claim local capture provisioned a VM. + +## Capture spec + +Adapt the real OPFS fixture from `tests/browser/responsive-editor.spec.ts`: + +```typescript +import { test, expect } from 'playwright/test' + +test('editor preview', async ({ page }) => { + await page.goto('/projects', { waitUntil: 'networkidle' }) + await page.evaluate(async () => { + const root = await navigator.storage.getDirectory() + const modulePath = '/src/infrastructure/storage/handles-db.ts' + const handles = await import(/* @vite-ignore */ modulePath) + await handles.saveWorkspaceHandleRecord(root) + }) + await page.reload() + await page.getByRole('link', { name: 'New Project' }).click() + await page.getByRole('textbox', { name: /Project Name/ }).fill('PR Preview') + await page.getByRole('button', { name: 'Create Project' }).click() + await page.waitForURL(/\/editor\//) + await expect(page.locator('[role="application"]')).toBeVisible() + await expect(page.getByRole('button', { name: 'Add track', exact: true })).toBeVisible() + await page.screenshot({ path: '/tmp/pr-screenshots/editor.png', fullPage: false }) +}) +``` + +Create `/tmp/pr-screenshots` before running. Use a fresh browser context so OPFS contains synthetic projects only. For changed media workflows reuse repository-generated fixtures rather than customer files. Preview fixtures are labeled as such; do not substitute them for real behavior QA. Collect page errors, console errors and failed requests and explain failures. Open the screenshot and confirm relevant UI is legible and the file exceeds 10KB; file existence alone is insufficient. + +For video add `test.use({ video: { mode: 'on', size: { width: 1440, height: 1100 } } })`; perform the actual changed interaction, assert its final state and keep it visible briefly. Close the page/context to finalize the recording and copy `await page.video().path()` to the owned capture directory. Preserve the full original WebM. Trim loading pre-roll in a separate copy if useful; never convert to GIF. + +For a later authorized visual PR, before/after is the default: render the same spec at the merge-base with `origin/codepress-main` in a separate temporary worktree, with separately installed dependencies, isolated output and only required safe synthetic fixtures. Harvest each run before the next; do not overwrite fresh output with stale baseline captures. Missing base state or incompatible fixtures yields an explained after-only preview. Stamp the Demo section with the captured SHA. Init is after-only bootstrap and does not create worktrees or delivery commits. + +## Attach only during authorized delivery + +Inspect every image/video for credentials, personal/customer content and confidential data before publication. Recapture with synthetic data when needed. Local Terminal delivery requires GitHub CLI v2.99.0+ and `gh pr edit --help` showing `--attach`. Preserve the current PR body via `gh pr view --json body --jq .body` into an owned body file; reference images by exact local path and videos as a standalone `![](path)` paragraph. Attach every file using repeatable `--attach` flags with `gh pr edit --body-file ` (or authorized create), preserving full video. A nonzero exit can mean partial publication: re-read the live body, retry only unresolved attachments once, or remove unresolved local references and update the cleaned body. Re-read to prove no local references survive. If cleanup cannot be done, report the PR URL and exact blocker. No GIF or Release-upload fallback. + +In CodePress cloud delivery use `upload_pr_asset` for each image and full WebM because GitHub App tokens cannot use the CLI attachment uploader; embed returned HTTPS image URLs and plain full-video links. If unavailable, report publication BLOCKED. Factory delivery owners follow `codepress factory skill` for guarded PR operations; this skill does not authorize delivery or bypass those gates. + +Always close owned browser contexts and servers in finally, terminate/reap owned child process groups, and prove their listeners are gone. Remove only temporary specs/configs created by this run. Retain captures until review/publication is complete; never remove customer files. For init hash this skill and safe Codex mirror before smoke and confirm unchanged afterward, recording local image path, dimensions, observations and cleanup in the stage report. diff --git a/.agents/skills/verify-local/SKILL.md b/.agents/skills/verify-local/SKILL.md new file mode 100644 index 000000000..8aac6bc69 --- /dev/null +++ b/.agents/skills/verify-local/SKILL.md @@ -0,0 +1,25 @@ +--- +name: verify-local +description: Verify FreeCut browser editor changes using its repository-owned local contract. +codepress_generated: true +--- + +# FreeCut local verification + +Read `codepress factory skill` and `codepress factory bootstrap --local --json` for the current workflow. Consume `.codepress/verify-local/recipe.json`; every declared setup step, service and check is required. This file contains project-specific guidance; release-owned prompts and helpers stay in the CLI. For delivery (not onboarding smoke), the owner follows `codepress factory skill falsify-diff` and `codepress factory skill falsifier`, including CLI snapshot create, validate and cleanup, and independent pre-QA falsification before independent committed-head QA. Never treat setup smoke as exact-head delivery evidence. + +Before selecting probes, capture HEAD and complete dirty status, inventory changed paths, define observable claims and named failure cases, and resolve verification graph edges (explicitly [] when absent). Write a Diff Trigger Inventory and Verification Contract with one evidence class and probe per claim. Tests and static checks supplement live UI/HTTP evidence and cannot replace it. + +## Execution context + +On a developer device use Node 22+ and npm (packageManager pins npm 11.8.0). Run recipe setup commands from this root, then fixtures (none), required frontend, bounded health probe, all required checks in order, and cleanup in a finally path. Do not use watch mode for checks. Recipe checks cover focused blob URL ownership tests (`npm run test:run -- src/infrastructure/browser/blob-url-manager.test.ts`), `npm run lint`, `npm run check` (type-aware lint/typecheck), `npm run build`, `npm run test:preview-sync`, `npm run test:editor-hardening`, `npm run build:editor-surface`, `npm run test:editor-surface:consumer`, `npm run test:publish-editor-surface-guards`, and `npm run headless:test:node`. Broader behavior may require `npm run test:run`, `npm run verify`, browser suites (`test:responsive`, `test:layout-refresh`), or portable headless/media checks; inspect package.json and the relevant config first. Stress/coverage/perf modes are supplementary targeted probes, not substitutes for required checks. Do not publish packages. + +The frontend is Vite on strict port 5173: `npm run dev -- --host 127.0.0.1`; require HTTP 200 at `/` within 60 seconds and inspect logs. Preserve the COOP same-origin and COEP require-corp headers needed by browser workers. Reuse only a healthy handle whose checkout ownership is known; never kill a preexisting user's server to free the port. Track owned process groups, terminate descendants, reap them, and prove no owned process or listener survives on every exit. A port conflict is a concrete blocker. + +FreeCut has no application backend, database, login or server-side fixture service. Workspace files, OPFS, WebGPU and WebCodecs are browser-local. Use a disposable workspace and repository-supported browser/headless fixtures, never customer media. Modern Chromium is required; GPU-dependent claims require a real supported adapter. Do not call software-only checks GPU proof. For an added backend in a future diff, discover and declare its native local command, test database, supported fixtures, and bounded readiness probe first; backend verification is a first-class local path. + +On a CodePress cloud runner, every customer frontend/backend process must run inside the CodePress verification-environment MicroVM lifecycle; the runner is only the control plane. Pass only a backend-issued environment/run reference and bounded probe inputs; request only backend-returned immutable manifest check IDs. Never submit repositories, SHAs, commands, argv, ports, manifests or checkout credentials. Native execution is the default; Docker/Compose fails closed unless the selected VM image reports that executor implemented. An unavailable execution plane is BLOCKED; do not hand-start customer servers in the runner. The hosted Live Dev Server uses `.codepress/dev-server/recipe.json` and `start_dev_server` only after connected hosted provisioning; local Vite health is not hosted readiness. + +Route only effects needing deployed infrastructure, unavailable private services, production-like cross-service wiring or external integrations to `.claude/skills/verify-staging/SKILL.md`. If absent or staging was declined, report each unsupported claim with its recovery action; keep locally provable claims local. Never substitute a production target. + +Record commands, exit codes, health observations, UI actions and relevant logs per claim. Stop at concrete missing prerequisites and report BLOCKED honestly; never infer readiness from existing dist files or skills. For onboarding hash the skill, recipe and safe Codex mirror before smoke and confirm identical hashes after smoke. No commits, pushes, PR activity, asset publication, delivery or QA stamps during onboarding. For later authorized UI reports follow the repository pr-screenshot skill and its supported publication route. Use serial probes; no custom batch harness is needed. diff --git a/.claude/skills/pr-screenshot/SKILL.md b/.claude/skills/pr-screenshot/SKILL.md new file mode 100644 index 000000000..21149c62f --- /dev/null +++ b/.claude/skills/pr-screenshot/SKILL.md @@ -0,0 +1,68 @@ +--- +name: pr-screenshot +description: Capture FreeCut frontend PR preview screenshots and full-motion videos using its existing Playwright setup. +user_invocable: true +codepress_generated: true +--- + +# FreeCut PR previews + +Use for visible changes in `src/features/**/*.tsx`, `src/components/**/*.tsx`, `src/routes/**/*.tsx`, `src/**/*.css`, and `packages/freecut-editor/src/**`. Skip documentation, infrastructure and test-only changes without visual effects. This is PR-preview evidence; it does not prove ship-gate frontend behavior or hosted readiness. During Factory init capture locally only: no commits, pushes, PR edits or asset publication. + +## Map changes to captures + +| Changed surface | Page/state | +| --- | --- | +| Workspace gate, branding | `/projects` before workspace selection | +| Projects | `/projects`, `/projects/new` with disposable OPFS workspace | +| Editor, timeline, media, preview, effects, export, settings | `/editor/` reached by creating a disposable project; open the changed panel | +| Published host surface | `/tests/browser/host-responsive.html` existing preview fixture | +| Docs, changelog | `/docs`, `/changelog` | + +Read `git diff origin/codepress-main...HEAD --name-only` and map every affected visible area. Static layout needs screenshots; interactions/animation/dragging need full-motion WebM, optionally a screenshot too. Capture meaningful final state and nearby context, not a loading shell. For responsive changes add 390x844 to desktop 1440x1100. Use locator visibility and bounding boxes before capturing; avoid excessively long full-page images. + +## Runtime and fixtures + +This root owns npm/package-lock.json and Playwright (`playwright/test`, not a new @playwright/test install). Node 22+ is recommended. Run `npm ci --no-audit --no-fund` when dependencies need setup. Existing `playwright.mobile.config.ts` uses system Chrome, a single worker and real Vite at 127.0.0.1:4178 with `npm run dev -- --port 4178 --strictPort`. Other configs cover caption/layout refresh. Reuse their infrastructure and selectors, preserve all customer config. If Chrome is absent, use the installed Playwright chromium runtime; install with `npm exec -- playwright install chromium` from this root if needed and authorized. No application backend or login exists; no credentials or auth bypass is needed. + +For capture-only specs, create an owned temporary `tests/browser/_codepress-pr-preview.spec.ts` and a temporary config importing the mobile config, overriding `testMatch` to that filename, `testDir` to this checkout's tests/browser, outputDir to an owned /tmp directory, `reuseExistingServer: false`, and viewport to 1440x1100. Keep `channel: chrome` if available, otherwise override channel to undefined for installed Chromium. Require the port to be free; never kill a user's server. Run `npm exec -- playwright test --config --workers=1`. Vite must return HTTP 200 within its bounded config timeout. Preserve COOP/COEP headers. In a hosted runner use only the connected CodePress MicroVM lifecycle; never start customer servers on its control plane or claim local capture provisioned a VM. + +## Capture spec + +Adapt the real OPFS fixture from `tests/browser/responsive-editor.spec.ts`: + +```typescript +import { test, expect } from 'playwright/test' + +test('editor preview', async ({ page }) => { + await page.goto('/projects', { waitUntil: 'networkidle' }) + await page.evaluate(async () => { + const root = await navigator.storage.getDirectory() + const modulePath = '/src/infrastructure/storage/handles-db.ts' + const handles = await import(/* @vite-ignore */ modulePath) + await handles.saveWorkspaceHandleRecord(root) + }) + await page.reload() + await page.getByRole('link', { name: 'New Project' }).click() + await page.getByRole('textbox', { name: /Project Name/ }).fill('PR Preview') + await page.getByRole('button', { name: 'Create Project' }).click() + await page.waitForURL(/\/editor\//) + await expect(page.locator('[role="application"]')).toBeVisible() + await expect(page.getByRole('button', { name: 'Add track', exact: true })).toBeVisible() + await page.screenshot({ path: '/tmp/pr-screenshots/editor.png', fullPage: false }) +}) +``` + +Create `/tmp/pr-screenshots` before running. Use a fresh browser context so OPFS contains synthetic projects only. For changed media workflows reuse repository-generated fixtures rather than customer files. Preview fixtures are labeled as such; do not substitute them for real behavior QA. Collect page errors, console errors and failed requests and explain failures. Open the screenshot and confirm relevant UI is legible and the file exceeds 10KB; file existence alone is insufficient. + +For video add `test.use({ video: { mode: 'on', size: { width: 1440, height: 1100 } } })`; perform the actual changed interaction, assert its final state and keep it visible briefly. Close the page/context to finalize the recording and copy `await page.video().path()` to the owned capture directory. Preserve the full original WebM. Trim loading pre-roll in a separate copy if useful; never convert to GIF. + +For a later authorized visual PR, before/after is the default: render the same spec at the merge-base with `origin/codepress-main` in a separate temporary worktree, with separately installed dependencies, isolated output and only required safe synthetic fixtures. Harvest each run before the next; do not overwrite fresh output with stale baseline captures. Missing base state or incompatible fixtures yields an explained after-only preview. Stamp the Demo section with the captured SHA. Init is after-only bootstrap and does not create worktrees or delivery commits. + +## Attach only during authorized delivery + +Inspect every image/video for credentials, personal/customer content and confidential data before publication. Recapture with synthetic data when needed. Local Terminal delivery requires GitHub CLI v2.99.0+ and `gh pr edit --help` showing `--attach`. Preserve the current PR body via `gh pr view --json body --jq .body` into an owned body file; reference images by exact local path and videos as a standalone `![](path)` paragraph. Attach every file using repeatable `--attach` flags with `gh pr edit --body-file ` (or authorized create), preserving full video. A nonzero exit can mean partial publication: re-read the live body, retry only unresolved attachments once, or remove unresolved local references and update the cleaned body. Re-read to prove no local references survive. If cleanup cannot be done, report the PR URL and exact blocker. No GIF or Release-upload fallback. + +In CodePress cloud delivery use `upload_pr_asset` for each image and full WebM because GitHub App tokens cannot use the CLI attachment uploader; embed returned HTTPS image URLs and plain full-video links. If unavailable, report publication BLOCKED. Factory delivery owners follow `codepress factory skill` for guarded PR operations; this skill does not authorize delivery or bypass those gates. + +Always close owned browser contexts and servers in finally, terminate/reap owned child process groups, and prove their listeners are gone. Remove only temporary specs/configs created by this run. Retain captures until review/publication is complete; never remove customer files. For init hash this skill and safe Codex mirror before smoke and confirm unchanged afterward, recording local image path, dimensions, observations and cleanup in the stage report. diff --git a/.claude/skills/verify-local/SKILL.md b/.claude/skills/verify-local/SKILL.md new file mode 100644 index 000000000..8aac6bc69 --- /dev/null +++ b/.claude/skills/verify-local/SKILL.md @@ -0,0 +1,25 @@ +--- +name: verify-local +description: Verify FreeCut browser editor changes using its repository-owned local contract. +codepress_generated: true +--- + +# FreeCut local verification + +Read `codepress factory skill` and `codepress factory bootstrap --local --json` for the current workflow. Consume `.codepress/verify-local/recipe.json`; every declared setup step, service and check is required. This file contains project-specific guidance; release-owned prompts and helpers stay in the CLI. For delivery (not onboarding smoke), the owner follows `codepress factory skill falsify-diff` and `codepress factory skill falsifier`, including CLI snapshot create, validate and cleanup, and independent pre-QA falsification before independent committed-head QA. Never treat setup smoke as exact-head delivery evidence. + +Before selecting probes, capture HEAD and complete dirty status, inventory changed paths, define observable claims and named failure cases, and resolve verification graph edges (explicitly [] when absent). Write a Diff Trigger Inventory and Verification Contract with one evidence class and probe per claim. Tests and static checks supplement live UI/HTTP evidence and cannot replace it. + +## Execution context + +On a developer device use Node 22+ and npm (packageManager pins npm 11.8.0). Run recipe setup commands from this root, then fixtures (none), required frontend, bounded health probe, all required checks in order, and cleanup in a finally path. Do not use watch mode for checks. Recipe checks cover focused blob URL ownership tests (`npm run test:run -- src/infrastructure/browser/blob-url-manager.test.ts`), `npm run lint`, `npm run check` (type-aware lint/typecheck), `npm run build`, `npm run test:preview-sync`, `npm run test:editor-hardening`, `npm run build:editor-surface`, `npm run test:editor-surface:consumer`, `npm run test:publish-editor-surface-guards`, and `npm run headless:test:node`. Broader behavior may require `npm run test:run`, `npm run verify`, browser suites (`test:responsive`, `test:layout-refresh`), or portable headless/media checks; inspect package.json and the relevant config first. Stress/coverage/perf modes are supplementary targeted probes, not substitutes for required checks. Do not publish packages. + +The frontend is Vite on strict port 5173: `npm run dev -- --host 127.0.0.1`; require HTTP 200 at `/` within 60 seconds and inspect logs. Preserve the COOP same-origin and COEP require-corp headers needed by browser workers. Reuse only a healthy handle whose checkout ownership is known; never kill a preexisting user's server to free the port. Track owned process groups, terminate descendants, reap them, and prove no owned process or listener survives on every exit. A port conflict is a concrete blocker. + +FreeCut has no application backend, database, login or server-side fixture service. Workspace files, OPFS, WebGPU and WebCodecs are browser-local. Use a disposable workspace and repository-supported browser/headless fixtures, never customer media. Modern Chromium is required; GPU-dependent claims require a real supported adapter. Do not call software-only checks GPU proof. For an added backend in a future diff, discover and declare its native local command, test database, supported fixtures, and bounded readiness probe first; backend verification is a first-class local path. + +On a CodePress cloud runner, every customer frontend/backend process must run inside the CodePress verification-environment MicroVM lifecycle; the runner is only the control plane. Pass only a backend-issued environment/run reference and bounded probe inputs; request only backend-returned immutable manifest check IDs. Never submit repositories, SHAs, commands, argv, ports, manifests or checkout credentials. Native execution is the default; Docker/Compose fails closed unless the selected VM image reports that executor implemented. An unavailable execution plane is BLOCKED; do not hand-start customer servers in the runner. The hosted Live Dev Server uses `.codepress/dev-server/recipe.json` and `start_dev_server` only after connected hosted provisioning; local Vite health is not hosted readiness. + +Route only effects needing deployed infrastructure, unavailable private services, production-like cross-service wiring or external integrations to `.claude/skills/verify-staging/SKILL.md`. If absent or staging was declined, report each unsupported claim with its recovery action; keep locally provable claims local. Never substitute a production target. + +Record commands, exit codes, health observations, UI actions and relevant logs per claim. Stop at concrete missing prerequisites and report BLOCKED honestly; never infer readiness from existing dist files or skills. For onboarding hash the skill, recipe and safe Codex mirror before smoke and confirm identical hashes after smoke. No commits, pushes, PR activity, asset publication, delivery or QA stamps during onboarding. For later authorized UI reports follow the repository pr-screenshot skill and its supported publication route. Use serial probes; no custom batch harness is needed. diff --git a/.codepress/dev-server/Dockerfile.web b/.codepress/dev-server/Dockerfile.web new file mode 100644 index 000000000..eeea82bc3 --- /dev/null +++ b/.codepress/dev-server/Dockerfile.web @@ -0,0 +1,20 @@ +# Generated by CodePress bootstrap-dev-server. +# Dev-mode image for the "web" frontend (Live Dev Server). +# Edits are preserved, but running /codepress-bootstrap-dev-server again may overwrite them. +# codepress_generated: true +FROM public.ecr.aws/docker/library/node:22-bookworm + +RUN apt-get update \ + && apt-get install -y --no-install-recommends procps \ + && rm -rf /var/lib/apt/lists/* +RUN corepack enable && npm install -g npm@11.8.0 +WORKDIR /app +ENV HOSTNAME=0.0.0.0 \ + CODEPRESS_BIND_HOST=0.0.0.0 \ + PORT=5173 \ + CODEPRESS_HMR_CLIENT_PORT=443 \ + CODEPRESS_HMR_PROTOCOL=wss \ + CODEPRESS_BASE_PATH=/ +ENV PATH="/app/node_modules/.bin:${PATH}" +EXPOSE 5173 +CMD ["sh", "-c", "npm run dev -- --config .codepress/dev-server/vite.config.ts --host \"${CODEPRESS_BIND_HOST:-0.0.0.0}\" --port \"${PORT:-5173}\" --strictPort"] diff --git a/.codepress/dev-server/recipe.json b/.codepress/dev-server/recipe.json new file mode 100644 index 000000000..9ce02c909 --- /dev/null +++ b/.codepress/dev-server/recipe.json @@ -0,0 +1,22 @@ +{ + "schema_version": 1, + "bootstrapped_at": "2026-09-11T20:23:18.516168Z", + "frontends": [ + { + "label": "web", + "description": "FreeCut browser video editor with local workspace files, timeline editing and export.", + "working_dir": "", + "dockerfile_path": ".codepress/dev-server/Dockerfile.web", + "dev_command": "npm run dev -- --config .codepress/dev-server/vite.config.ts --host \"${CODEPRESS_BIND_HOST:-0.0.0.0}\" --port \"${PORT:-5173}\" --strictPort", + "prebuild_command": "", + "prebuild_inputs": [], + "framework": "vite", + "package_manager": "npm", + "install_command": "npm install --no-audit --no-fund", + "dependency_manifests": ["package-lock.json"], + "dev_port": 5173, + "system_packages": ["procps"], + "size": "medium" + } + ] +} diff --git a/.codepress/dev-server/vite.config.ts b/.codepress/dev-server/vite.config.ts new file mode 100644 index 000000000..b77142f41 --- /dev/null +++ b/.codepress/dev-server/vite.config.ts @@ -0,0 +1,23 @@ +// codepress_generated: true +// Explicit preview-only config: preserve the customer's normal Vite configuration. +import { mergeConfig } from 'vite-plus' +import appConfig from '../../vite.config' + +const basePath = process.env.CODEPRESS_BASE_PATH || '/' +const base = `/${basePath.split('/').filter(Boolean).join('/')}`.replace(/\/?$/, '/') +const clientPort = Number(process.env.CODEPRESS_HMR_CLIENT_PORT || '443') +const protocol = process.env.CODEPRESS_HMR_PROTOCOL || 'wss' +if (!Number.isInteger(clientPort) || clientPort < 1 || clientPort > 65535) { + throw new Error('CODEPRESS_HMR_CLIENT_PORT must be an integer from 1 to 65535') +} +if (protocol !== 'ws' && protocol !== 'wss') { + throw new Error('CODEPRESS_HMR_PROTOCOL must be ws or wss') +} + +export default mergeConfig(appConfig, { + base, + server: { + allowedHosts: true, + hmr: { clientPort, protocol }, + }, +}) diff --git a/.codepress/factory.json b/.codepress/factory.json new file mode 100644 index 000000000..5507e6daf --- /dev/null +++ b/.codepress/factory.json @@ -0,0 +1,9 @@ +{ + "capabilities": { + "delivery": { + "mode": "advisory" + } + }, + "managed_by": "codepress-factory-cli", + "schema_version": 3 +} diff --git a/.codepress/verify-local/recipe.json b/.codepress/verify-local/recipe.json new file mode 100644 index 000000000..e0e5c5cf2 --- /dev/null +++ b/.codepress/verify-local/recipe.json @@ -0,0 +1,133 @@ +{ + "schema_version": 1, + "setup_commands": [["npm", "ci", "--no-audit", "--no-fund"]], + "services": [ + { + "id": "frontend", + "executor": "native", + "command": ["npm", "run", "dev", "--", "--host", "127.0.0.1"], + "working_dir": ".", + "required": true, + "port": 5173, + "browser": true, + "env_defaults": { + "VITE_SHOW_DEBUG_PANEL": "false" + }, + "required_bindings": [], + "fixtures": [], + "health": { + "path": "/", + "status_codes": [200], + "timeout_seconds": 60 + } + } + ], + "checks": [ + { + "id": "editor-surface-build", + "executor": "native", + "command": ["npm", "run", "build:editor-surface"], + "working_dir": ".", + "timeout_seconds": 600, + "expected_exit_codes": [0], + "env_defaults": {}, + "required_bindings": [] + }, + { + "id": "focused-test", + "executor": "native", + "command": [ + "npm", + "run", + "test:run", + "--", + "src/infrastructure/browser/blob-url-manager.test.ts" + ], + "working_dir": ".", + "timeout_seconds": 180, + "expected_exit_codes": [0], + "env_defaults": {}, + "required_bindings": [] + }, + { + "id": "lint", + "executor": "native", + "command": ["npm", "run", "lint"], + "working_dir": ".", + "timeout_seconds": 300, + "expected_exit_codes": [0], + "env_defaults": {}, + "required_bindings": [] + }, + { + "id": "typecheck", + "executor": "native", + "command": ["npm", "run", "check"], + "working_dir": ".", + "timeout_seconds": 300, + "expected_exit_codes": [0], + "env_defaults": {}, + "required_bindings": [] + }, + { + "id": "build", + "executor": "native", + "command": ["npm", "run", "build"], + "working_dir": ".", + "timeout_seconds": 600, + "expected_exit_codes": [0], + "env_defaults": {}, + "required_bindings": [] + }, + { + "id": "preview-sync", + "executor": "native", + "command": ["npm", "run", "test:preview-sync"], + "working_dir": ".", + "timeout_seconds": 180, + "expected_exit_codes": [0], + "env_defaults": {}, + "required_bindings": [] + }, + { + "id": "editor-hardening", + "executor": "native", + "command": ["npm", "run", "test:editor-hardening"], + "working_dir": ".", + "timeout_seconds": 300, + "expected_exit_codes": [0], + "env_defaults": {}, + "required_bindings": [] + }, + { + "id": "editor-surface-consumer", + "executor": "native", + "command": ["npm", "run", "test:editor-surface:consumer"], + "working_dir": ".", + "timeout_seconds": 300, + "expected_exit_codes": [0], + "env_defaults": {}, + "required_bindings": [] + }, + { + "id": "publish-guards", + "executor": "native", + "command": ["npm", "run", "test:publish-editor-surface-guards"], + "working_dir": ".", + "timeout_seconds": 180, + "expected_exit_codes": [0], + "env_defaults": {}, + "required_bindings": [] + }, + { + "id": "headless-node", + "executor": "native", + "command": ["npm", "run", "headless:test:node"], + "working_dir": ".", + "timeout_seconds": 180, + "expected_exit_codes": [0], + "env_defaults": {}, + "required_bindings": [] + } + ] +} diff --git a/.codex/hooks.json b/.codex/hooks.json new file mode 100644 index 000000000..f2be245af --- /dev/null +++ b/.codex/hooks.json @@ -0,0 +1,46 @@ +{ + "hooks": { + "UserPromptSubmit": [ + { + "hooks": [ + { + "type": "command", + "command": "/bin/bash -c 'exec 4>&2 2>/dev/null\nrepo_root=\"$(git rev-parse --show-toplevel 2>/dev/null)\" || exit 0;\n[ -x \"${CODEPRESS_HOME:-$HOME/.codepress}/bin/codepress\" ] || exit 0;\n:\nfail_open() { printf '\\''%s\\n'\\'' '\\''CodePress Factory hook unavailable; allowing this action. Run codepress factory update or codepress factory doctor; if the executable is missing, reinstall using https://downloads.codepress.dev/factory/install.sh.'\\'' >&4; }\nwork=$(mktemp -d \"${TMPDIR:-/tmp}/codepress-hook.XXXXXXXX\") || { fail_open; exit 0; }\nset -m || { fail_open; rm -rf \"$work\"; exit 0; }\nchild= timer= output_reader= error_reader=\nkill_tree() {\n kill -KILL -- \"-$1\" 2>/dev/null || :\n}\ncleanup() {\n [ -z \"$timer\" ] || { kill_tree \"$timer\"; wait \"$timer\" 2>/dev/null || :; }\n [ -z \"$child\" ] || { kill_tree \"$child\"; wait \"$child\" 2>/dev/null || :; }\n [ -z \"$output_reader\" ] || { kill_tree \"$output_reader\"; wait \"$output_reader\" 2>/dev/null || :; }\n [ -z \"$error_reader\" ] || { kill_tree \"$error_reader\"; wait \"$error_reader\" 2>/dev/null || :; }\n rm -rf \"$work\"\n}\ntrap cleanup EXIT\ntrap '\\''fail_open; exit 0'\\'' HUP INT TERM\nlimit=${CODEPRESS_FACTORY_HOOK_TIMEOUT_SECONDS:-3}\ncase \"$limit\" in 1|2|3) ;; *) limit=3 ;; esac\nmkfifo \"$work/stdout\" \"$work/stderr\" || { fail_open; exit 0; }\nhead -c 1048577 <\"$work/stdout\" >\"$work/output\" &\noutput_reader=$!\nhead -c 1048577 <\"$work/stderr\" >\"$work/error\" &\nerror_reader=$!\nexec 3<&0\nCODEPRESS_FACTORY_HOOK_FRAME=1 \"${CODEPRESS_HOME:-$HOME/.codepress}/bin/codepress\" factory hook dev-loop-init --provider codex <&3 4>&- >\"$work/stdout\" 2>\"$work/stderr\" &\nchild=$!\n(sleep \"$limit\"; : >\"$work/timeout\"; kill_tree \"$child\"; kill_tree \"$output_reader\"; kill_tree \"$error_reader\") >/dev/null 2>&1 &\ntimer=$!\nwait \"$child\" 2>/dev/null\nstatus=$?\nwait \"$output_reader\" 2>/dev/null || :\noutput_reader=\nwait \"$error_reader\" 2>/dev/null || :\nerror_reader=\nkill_tree \"$child\"\nchild=\nkill_tree \"$timer\"\nwait \"$timer\" 2>/dev/null || :\ntimer=\nvalid=no\nif [ ! -e \"$work/timeout\" ] && [ \"$(wc -c <\"$work/error\")\" -le 1048576 ] && { [ \"$status\" = 0 ] || [ \"$status\" = 2 ]; }; then\n IFS='\\'' '\\'' read -r marker code size extra <\"$work/output\"\n case \"$size\" in '\\'''\\''|*[!0-9]*|0[0-9]*) size=invalid ;; esac\n if [ \"$marker\" = '\\''CODEPRESS_FACTORY_HOOK_V1'\\'' ] && [ \"$code\" = \"$status\" ] && [ -z \"$extra\" ] && [ \"$size\" != invalid ] && [ \"${#size}\" -le 5 ]; then\n header=$(printf '\\''%s %s %s\\n'\\'' \"$marker\" \"$code\" \"$size\" | wc -c)\n total=$(wc -c <\"$work/output\")\n footer=$(tail -c 31 \"$work/output\")\n if [ \"$total\" -eq \"$((header + size + 31))\" ] && [ \"$footer\" = '\\''\nCODEPRESS_FACTORY_HOOK_END_V1'\\'' ]; then\n dd if=\"$work/output\" of=\"$work/body\" bs=1 skip=\"$header\" count=\"$size\" 2>/dev/null && valid=yes\n fi\n fi\nfi\nif [ \"$valid\" = yes ]; then\n cat \"$work/error\" >&4\n cat \"$work/body\" || { fail_open; exit 0; }\n exit \"$status\"\nfi\nhead -c 4096 \"$work/error\" >&4\nfail_open\nexit 0'", + "timeout": 5 + } + ] + } + ], + "PreToolUse": [ + { + "hooks": [ + { + "type": "command", + "command": "/bin/bash -c 'exec 4>&2 2>/dev/null\nrepo_root=\"$(git rev-parse --show-toplevel 2>/dev/null)\" || exit 0;\n[ -x \"${CODEPRESS_HOME:-$HOME/.codepress}/bin/codepress\" ] || exit 0;\n:\nfail_open() { printf '\\''%s\\n'\\'' '\\''CodePress Factory hook unavailable; allowing this action. Run codepress factory update or codepress factory doctor; if the executable is missing, reinstall using https://downloads.codepress.dev/factory/install.sh.'\\'' >&4; }\nwork=$(mktemp -d \"${TMPDIR:-/tmp}/codepress-hook.XXXXXXXX\") || { fail_open; exit 0; }\nset -m || { fail_open; rm -rf \"$work\"; exit 0; }\nchild= timer= output_reader= error_reader=\nkill_tree() {\n kill -KILL -- \"-$1\" 2>/dev/null || :\n}\ncleanup() {\n [ -z \"$timer\" ] || { kill_tree \"$timer\"; wait \"$timer\" 2>/dev/null || :; }\n [ -z \"$child\" ] || { kill_tree \"$child\"; wait \"$child\" 2>/dev/null || :; }\n [ -z \"$output_reader\" ] || { kill_tree \"$output_reader\"; wait \"$output_reader\" 2>/dev/null || :; }\n [ -z \"$error_reader\" ] || { kill_tree \"$error_reader\"; wait \"$error_reader\" 2>/dev/null || :; }\n rm -rf \"$work\"\n}\ntrap cleanup EXIT\ntrap '\\''fail_open; exit 0'\\'' HUP INT TERM\nlimit=${CODEPRESS_FACTORY_HOOK_TIMEOUT_SECONDS:-3}\ncase \"$limit\" in 1|2|3) ;; *) limit=3 ;; esac\nmkfifo \"$work/stdout\" \"$work/stderr\" || { fail_open; exit 0; }\nhead -c 1048577 <\"$work/stdout\" >\"$work/output\" &\noutput_reader=$!\nhead -c 1048577 <\"$work/stderr\" >\"$work/error\" &\nerror_reader=$!\nexec 3<&0\nCODEPRESS_FACTORY_HOOK_FRAME=1 \"${CODEPRESS_HOME:-$HOME/.codepress}/bin/codepress\" factory hook dev-loop-actor --provider codex <&3 4>&- >\"$work/stdout\" 2>\"$work/stderr\" &\nchild=$!\n(sleep \"$limit\"; : >\"$work/timeout\"; kill_tree \"$child\"; kill_tree \"$output_reader\"; kill_tree \"$error_reader\") >/dev/null 2>&1 &\ntimer=$!\nwait \"$child\" 2>/dev/null\nstatus=$?\nwait \"$output_reader\" 2>/dev/null || :\noutput_reader=\nwait \"$error_reader\" 2>/dev/null || :\nerror_reader=\nkill_tree \"$child\"\nchild=\nkill_tree \"$timer\"\nwait \"$timer\" 2>/dev/null || :\ntimer=\nvalid=no\nif [ ! -e \"$work/timeout\" ] && [ \"$(wc -c <\"$work/error\")\" -le 1048576 ] && { [ \"$status\" = 0 ] || [ \"$status\" = 2 ]; }; then\n IFS='\\'' '\\'' read -r marker code size extra <\"$work/output\"\n case \"$size\" in '\\'''\\''|*[!0-9]*|0[0-9]*) size=invalid ;; esac\n if [ \"$marker\" = '\\''CODEPRESS_FACTORY_HOOK_V1'\\'' ] && [ \"$code\" = \"$status\" ] && [ -z \"$extra\" ] && [ \"$size\" != invalid ] && [ \"${#size}\" -le 5 ]; then\n header=$(printf '\\''%s %s %s\\n'\\'' \"$marker\" \"$code\" \"$size\" | wc -c)\n total=$(wc -c <\"$work/output\")\n footer=$(tail -c 31 \"$work/output\")\n if [ \"$total\" -eq \"$((header + size + 31))\" ] && [ \"$footer\" = '\\''\nCODEPRESS_FACTORY_HOOK_END_V1'\\'' ]; then\n dd if=\"$work/output\" of=\"$work/body\" bs=1 skip=\"$header\" count=\"$size\" 2>/dev/null && valid=yes\n fi\n fi\nfi\nif [ \"$valid\" = yes ]; then\n cat \"$work/error\" >&4\n cat \"$work/body\" || { fail_open; exit 0; }\n exit \"$status\"\nfi\nhead -c 4096 \"$work/error\" >&4\nfail_open\nexit 0'", + "timeout": 5 + } + ] + } + ], + "Stop": [ + { + "hooks": [ + { + "type": "command", + "command": "/bin/bash -c 'exec 4>&2 2>/dev/null\nrepo_root=\"$(git rev-parse --show-toplevel 2>/dev/null)\" || exit 0;\n[ -x \"${CODEPRESS_HOME:-$HOME/.codepress}/bin/codepress\" ] || exit 0;\nexport CODEPRESS_RETIRED_HOOK_ALIAS=generate-pr.sh\nfail_open() { printf '\\''%s\\n'\\'' '\\''CodePress Factory hook unavailable; allowing this action. Run codepress factory update or codepress factory doctor; if the executable is missing, reinstall using https://downloads.codepress.dev/factory/install.sh.'\\'' >&4; }\nwork=$(mktemp -d \"${TMPDIR:-/tmp}/codepress-hook.XXXXXXXX\") || { fail_open; exit 0; }\nset -m || { fail_open; rm -rf \"$work\"; exit 0; }\nchild= timer= output_reader= error_reader=\nkill_tree() {\n kill -KILL -- \"-$1\" 2>/dev/null || :\n}\ncleanup() {\n [ -z \"$timer\" ] || { kill_tree \"$timer\"; wait \"$timer\" 2>/dev/null || :; }\n [ -z \"$child\" ] || { kill_tree \"$child\"; wait \"$child\" 2>/dev/null || :; }\n [ -z \"$output_reader\" ] || { kill_tree \"$output_reader\"; wait \"$output_reader\" 2>/dev/null || :; }\n [ -z \"$error_reader\" ] || { kill_tree \"$error_reader\"; wait \"$error_reader\" 2>/dev/null || :; }\n rm -rf \"$work\"\n}\ntrap cleanup EXIT\ntrap '\\''fail_open; exit 0'\\'' HUP INT TERM\nlimit=${CODEPRESS_FACTORY_HOOK_TIMEOUT_SECONDS:-3}\ncase \"$limit\" in 1|2|3) ;; *) limit=3 ;; esac\nmkfifo \"$work/stdout\" \"$work/stderr\" || { fail_open; exit 0; }\nhead -c 1048577 <\"$work/stdout\" >\"$work/output\" &\noutput_reader=$!\nhead -c 1048577 <\"$work/stderr\" >\"$work/error\" &\nerror_reader=$!\nexec 3<&0\nCODEPRESS_FACTORY_HOOK_FRAME=1 \"${CODEPRESS_HOME:-$HOME/.codepress}/bin/codepress\" factory hook qa-before-pr --provider codex <&3 4>&- >\"$work/stdout\" 2>\"$work/stderr\" &\nchild=$!\n(sleep \"$limit\"; : >\"$work/timeout\"; kill_tree \"$child\"; kill_tree \"$output_reader\"; kill_tree \"$error_reader\") >/dev/null 2>&1 &\ntimer=$!\nwait \"$child\" 2>/dev/null\nstatus=$?\nwait \"$output_reader\" 2>/dev/null || :\noutput_reader=\nwait \"$error_reader\" 2>/dev/null || :\nerror_reader=\nkill_tree \"$child\"\nchild=\nkill_tree \"$timer\"\nwait \"$timer\" 2>/dev/null || :\ntimer=\nvalid=no\nif [ ! -e \"$work/timeout\" ] && [ \"$(wc -c <\"$work/error\")\" -le 1048576 ] && { [ \"$status\" = 0 ] || [ \"$status\" = 2 ]; }; then\n IFS='\\'' '\\'' read -r marker code size extra <\"$work/output\"\n case \"$size\" in '\\'''\\''|*[!0-9]*|0[0-9]*) size=invalid ;; esac\n if [ \"$marker\" = '\\''CODEPRESS_FACTORY_HOOK_V1'\\'' ] && [ \"$code\" = \"$status\" ] && [ -z \"$extra\" ] && [ \"$size\" != invalid ] && [ \"${#size}\" -le 5 ]; then\n header=$(printf '\\''%s %s %s\\n'\\'' \"$marker\" \"$code\" \"$size\" | wc -c)\n total=$(wc -c <\"$work/output\")\n footer=$(tail -c 31 \"$work/output\")\n if [ \"$total\" -eq \"$((header + size + 31))\" ] && [ \"$footer\" = '\\''\nCODEPRESS_FACTORY_HOOK_END_V1'\\'' ]; then\n dd if=\"$work/output\" of=\"$work/body\" bs=1 skip=\"$header\" count=\"$size\" 2>/dev/null && valid=yes\n fi\n fi\nfi\nif [ \"$valid\" = yes ]; then\n cat \"$work/error\" >&4\n cat \"$work/body\" || { fail_open; exit 0; }\n exit \"$status\"\nfi\nhead -c 4096 \"$work/error\" >&4\nfail_open\nexit 0'", + "timeout": 15 + } + ] + }, + { + "hooks": [ + { + "type": "command", + "command": "/bin/bash -c 'exec 4>&2 2>/dev/null\nrepo_root=\"$(git rev-parse --show-toplevel 2>/dev/null)\" || exit 0;\n[ -x \"${CODEPRESS_HOME:-$HOME/.codepress}/bin/codepress\" ] || exit 0;\n:\nfail_open() { printf '\\''%s\\n'\\'' '\\''CodePress Factory hook unavailable; allowing this action. Run codepress factory update or codepress factory doctor; if the executable is missing, reinstall using https://downloads.codepress.dev/factory/install.sh.'\\'' >&4; }\nwork=$(mktemp -d \"${TMPDIR:-/tmp}/codepress-hook.XXXXXXXX\") || { fail_open; exit 0; }\nset -m || { fail_open; rm -rf \"$work\"; exit 0; }\nchild= timer= output_reader= error_reader=\nkill_tree() {\n kill -KILL -- \"-$1\" 2>/dev/null || :\n}\ncleanup() {\n [ -z \"$timer\" ] || { kill_tree \"$timer\"; wait \"$timer\" 2>/dev/null || :; }\n [ -z \"$child\" ] || { kill_tree \"$child\"; wait \"$child\" 2>/dev/null || :; }\n [ -z \"$output_reader\" ] || { kill_tree \"$output_reader\"; wait \"$output_reader\" 2>/dev/null || :; }\n [ -z \"$error_reader\" ] || { kill_tree \"$error_reader\"; wait \"$error_reader\" 2>/dev/null || :; }\n rm -rf \"$work\"\n}\ntrap cleanup EXIT\ntrap '\\''fail_open; exit 0'\\'' HUP INT TERM\nlimit=${CODEPRESS_FACTORY_HOOK_TIMEOUT_SECONDS:-3}\ncase \"$limit\" in 1|2|3) ;; *) limit=3 ;; esac\nmkfifo \"$work/stdout\" \"$work/stderr\" || { fail_open; exit 0; }\nhead -c 1048577 <\"$work/stdout\" >\"$work/output\" &\noutput_reader=$!\nhead -c 1048577 <\"$work/stderr\" >\"$work/error\" &\nerror_reader=$!\nexec 3<&0\nCODEPRESS_FACTORY_HOOK_FRAME=1 \"${CODEPRESS_HOME:-$HOME/.codepress}/bin/codepress\" factory hook post-verification --provider codex <&3 4>&- >\"$work/stdout\" 2>\"$work/stderr\" &\nchild=$!\n(sleep \"$limit\"; : >\"$work/timeout\"; kill_tree \"$child\"; kill_tree \"$output_reader\"; kill_tree \"$error_reader\") >/dev/null 2>&1 &\ntimer=$!\nwait \"$child\" 2>/dev/null\nstatus=$?\nwait \"$output_reader\" 2>/dev/null || :\noutput_reader=\nwait \"$error_reader\" 2>/dev/null || :\nerror_reader=\nkill_tree \"$child\"\nchild=\nkill_tree \"$timer\"\nwait \"$timer\" 2>/dev/null || :\ntimer=\nvalid=no\nif [ ! -e \"$work/timeout\" ] && [ \"$(wc -c <\"$work/error\")\" -le 1048576 ] && { [ \"$status\" = 0 ] || [ \"$status\" = 2 ]; }; then\n IFS='\\'' '\\'' read -r marker code size extra <\"$work/output\"\n case \"$size\" in '\\'''\\''|*[!0-9]*|0[0-9]*) size=invalid ;; esac\n if [ \"$marker\" = '\\''CODEPRESS_FACTORY_HOOK_V1'\\'' ] && [ \"$code\" = \"$status\" ] && [ -z \"$extra\" ] && [ \"$size\" != invalid ] && [ \"${#size}\" -le 5 ]; then\n header=$(printf '\\''%s %s %s\\n'\\'' \"$marker\" \"$code\" \"$size\" | wc -c)\n total=$(wc -c <\"$work/output\")\n footer=$(tail -c 31 \"$work/output\")\n if [ \"$total\" -eq \"$((header + size + 31))\" ] && [ \"$footer\" = '\\''\nCODEPRESS_FACTORY_HOOK_END_V1'\\'' ]; then\n dd if=\"$work/output\" of=\"$work/body\" bs=1 skip=\"$header\" count=\"$size\" 2>/dev/null && valid=yes\n fi\n fi\nfi\nif [ \"$valid\" = yes ]; then\n cat \"$work/error\" >&4\n cat \"$work/body\" || { fail_open; exit 0; }\n exit \"$status\"\nfi\nhead -c 4096 \"$work/error\" >&4\nfail_open\nexit 0'", + "timeout": 15 + } + ] + } + ] + } +}