@1agh/maude 0.42.0 → 0.44.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/apps/studio/.ai/cache/_stats.json +7 -0
- package/apps/studio/acp/bootstrap-brief.ts +12 -2
- package/apps/studio/acp/bridge.ts +44 -10
- package/apps/studio/acp/env.ts +23 -0
- package/apps/studio/acp/login-state.ts +263 -0
- package/apps/studio/acp/plugin-bootstrap.ts +31 -25
- package/apps/studio/acp/probe.ts +152 -1
- package/apps/studio/annotations-layer.tsx +1451 -324
- package/apps/studio/annotations-model.ts +58 -0
- package/apps/studio/api.ts +568 -24
- package/apps/studio/bin/_audio-search.mjs +140 -0
- package/apps/studio/bin/_import-asset-pdf-worker.mjs +34 -0
- package/apps/studio/bin/_import-asset.mjs +815 -0
- package/apps/studio/bin/_import-brand.mjs +635 -0
- package/apps/studio/bin/_import-tokens-alias-resolver.mjs +95 -0
- package/apps/studio/bin/_import-tokens.mjs +1201 -0
- package/apps/studio/bin/_ingest-footage.mjs +386 -0
- package/apps/studio/bin/_probe-footage-playwright.mjs +269 -0
- package/apps/studio/bin/_transcribe.mjs +419 -0
- package/apps/studio/bin/_transcribe.test.mjs +80 -0
- package/apps/studio/bin/_video-playwright.mjs +141 -71
- package/apps/studio/bin/audio-search.sh +28 -0
- package/apps/studio/bin/generate.sh +154 -0
- package/apps/studio/bin/import-asset.sh +34 -0
- package/apps/studio/bin/import-brand.sh +29 -0
- package/apps/studio/bin/import-tokens.sh +33 -0
- package/apps/studio/bin/ingest-footage.sh +27 -0
- package/apps/studio/bin/photo-adjust.sh +163 -0
- package/apps/studio/bin/photo-bg-remove.sh +258 -0
- package/apps/studio/bin/probe-footage.sh +28 -0
- package/apps/studio/bin/read-annotations.mjs +125 -2
- package/apps/studio/bin/transcribe.sh +39 -0
- package/apps/studio/canvas-edit.ts +656 -12
- package/apps/studio/canvas-lib.tsx +637 -10
- package/apps/studio/canvas-pipeline.ts +65 -7
- package/apps/studio/canvas-shell.tsx +156 -16
- package/apps/studio/client/app.jsx +2385 -699
- package/apps/studio/client/export-center.jsx +35 -1
- package/apps/studio/client/generate-dialog.jsx +352 -0
- package/apps/studio/client/github.js +18 -0
- package/apps/studio/client/inspector-controls.jsx +781 -0
- package/apps/studio/client/panels/BrandUploadPanel.jsx +233 -0
- package/apps/studio/client/panels/ChatPanel.jsx +15 -33
- package/apps/studio/client/panels/GitPanel.jsx +8 -0
- package/apps/studio/client/panels/IntroVideoDialog.jsx +44 -0
- package/apps/studio/client/panels/OnboardingWizard.jsx +12 -0
- package/apps/studio/client/panels/ReadinessList.jsx +227 -7
- package/apps/studio/client/panels/RepoBranchSwitcher.jsx +133 -21
- package/apps/studio/client/panels/SettingsPanel.jsx +828 -0
- package/apps/studio/client/panels/SetupChecklist.jsx +223 -0
- package/apps/studio/client/panels/StickerPicker.jsx +160 -0
- package/apps/studio/client/panels/TimelinePanel.jsx +15 -2
- package/apps/studio/client/photo-knobs.jsx +432 -0
- package/apps/studio/client/styles/3-shell-maude.css +376 -28
- package/apps/studio/client/styles/4-components.css +223 -0
- package/apps/studio/client/styles/6-acp-chat.css +86 -0
- package/apps/studio/client/tour/quick-setup-tour.js +39 -0
- package/apps/studio/commands/annotation-strokes-command.ts +13 -3
- package/apps/studio/commands/edit-source-command.ts +31 -4
- package/apps/studio/config.schema.json +70 -0
- package/apps/studio/context-menu.tsx +42 -8
- package/apps/studio/context.ts +16 -0
- package/apps/studio/design-setup-readiness.ts +115 -0
- package/apps/studio/dist/client.bundle.js +5572 -21
- package/apps/studio/dist/comment-mount.js +2 -2
- package/apps/studio/dist/runtime/.min-sizes.json +1 -0
- package/apps/studio/dist/runtime/@imgly_background-removal.js +5543 -0
- package/apps/studio/dist/runtime/pixi-js.js +519 -519
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/dom-selection.ts +25 -0
- package/apps/studio/draw/palette.ts +34 -0
- package/apps/studio/exporters/jobs.ts +47 -5
- package/apps/studio/exporters/video.ts +24 -4
- package/apps/studio/footage/schema.test.ts +287 -0
- package/apps/studio/footage/schema.ts +662 -0
- package/apps/studio/footage-store.ts +235 -0
- package/apps/studio/fs-watch.ts +10 -0
- package/apps/studio/generation/adapters/elevenlabs.ts +370 -0
- package/apps/studio/generation/adapters/gemini.ts +542 -0
- package/apps/studio/generation/adapters/groq.test.ts +80 -0
- package/apps/studio/generation/adapters/groq.ts +247 -0
- package/apps/studio/generation/audio-library.test.ts +95 -0
- package/apps/studio/generation/audio-library.ts +156 -0
- package/apps/studio/generation/captions.test.ts +78 -0
- package/apps/studio/generation/captions.ts +132 -0
- package/apps/studio/generation/download.ts +76 -0
- package/apps/studio/generation/elevenlabs.test.ts +223 -0
- package/apps/studio/generation/gemini.test.ts +350 -0
- package/apps/studio/generation/jobs.test.ts +128 -0
- package/apps/studio/generation/jobs.ts +243 -0
- package/apps/studio/generation/keys.test.ts +75 -0
- package/apps/studio/generation/keys.ts +162 -0
- package/apps/studio/generation/prefs.test.ts +95 -0
- package/apps/studio/generation/prefs.ts +82 -0
- package/apps/studio/generation/registry.ts +88 -0
- package/apps/studio/generation/types.test.ts +123 -0
- package/apps/studio/generation/types.ts +266 -0
- package/apps/studio/generation/whisper-models.test.ts +152 -0
- package/apps/studio/generation/whisper-models.ts +257 -0
- package/apps/studio/generation/whisper-spike-results.md +61 -0
- package/apps/studio/git/endpoints.ts +68 -7
- package/apps/studio/git/service.ts +199 -21
- package/apps/studio/github/service.ts +67 -0
- package/apps/studio/handoff.ts +3 -2
- package/apps/studio/http.ts +1039 -22
- package/apps/studio/input-router.tsx +10 -0
- package/apps/studio/inspect.ts +53 -1
- package/apps/studio/media/intro.mp4 +0 -0
- package/apps/studio/media-commit-chain.ts +119 -0
- package/apps/studio/paths.ts +20 -0
- package/apps/studio/photo/filters.ts +285 -0
- package/apps/studio/photo/pipeline.ts +607 -0
- package/apps/studio/photo/schema.ts +367 -0
- package/apps/studio/photo-store.ts +154 -0
- package/apps/studio/readiness.ts +108 -20
- package/apps/studio/runtime-bundle.ts +29 -6
- package/apps/studio/scaffold-design.ts +1145 -0
- package/apps/studio/server.ts +18 -2
- package/apps/studio/stickers/figjam-doodle/manifest.json +104 -0
- package/apps/studio/stickers/figjam-doodle/slice1.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice10.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice11.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice12.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice13.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice14.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice15.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice16.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice17.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice18.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice19.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice2.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice20.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice21.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice22.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice23.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice24.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice3.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice4.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice5.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice6.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice7.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice8.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice9.png +0 -0
- package/apps/studio/stickers/life-style/best.png +0 -0
- package/apps/studio/stickers/life-style/come-on.png +0 -0
- package/apps/studio/stickers/life-style/cut.png +0 -0
- package/apps/studio/stickers/life-style/detox.png +0 -0
- package/apps/studio/stickers/life-style/just.png +0 -0
- package/apps/studio/stickers/life-style/manifest.json +68 -0
- package/apps/studio/stickers/life-style/mirror.png +0 -0
- package/apps/studio/stickers/life-style/nice.png +0 -0
- package/apps/studio/stickers/life-style/objects.png +0 -0
- package/apps/studio/stickers/life-style/ok.png +0 -0
- package/apps/studio/stickers/life-style/queen.png +0 -0
- package/apps/studio/stickers/life-style/star.png +0 -0
- package/apps/studio/stickers/life-style/sun.png +0 -0
- package/apps/studio/stickers/life-style/water.png +0 -0
- package/apps/studio/stickers/life-style/yeah.png +0 -0
- package/apps/studio/stickers/life-style/you-can.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/check-the-deets.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/cut-it.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/dope.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/fresh.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/hot.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/idea.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/keep-exploring.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/killed-it.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/love-it.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/manifest.json +88 -0
- package/apps/studio/stickers/opposing-thoughts/not-sure.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/ok.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/pure-gold.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/save-for-later.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/steal-this.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/take-a-peek.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/this-or-that.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/thoughts.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/vibes.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/winner.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/wip.png +0 -0
- package/apps/studio/stickers/project-status/group-100.png +0 -0
- package/apps/studio/stickers/project-status/group-101.png +0 -0
- package/apps/studio/stickers/project-status/group-102.png +0 -0
- package/apps/studio/stickers/project-status/group-103.png +0 -0
- package/apps/studio/stickers/project-status/group-104.png +0 -0
- package/apps/studio/stickers/project-status/group-105.png +0 -0
- package/apps/studio/stickers/project-status/group-106.png +0 -0
- package/apps/studio/stickers/project-status/group-107.png +0 -0
- package/apps/studio/stickers/project-status/group-108.png +0 -0
- package/apps/studio/stickers/project-status/group-109.png +0 -0
- package/apps/studio/stickers/project-status/group-110.png +0 -0
- package/apps/studio/stickers/project-status/group-111.png +0 -0
- package/apps/studio/stickers/project-status/group-112.png +0 -0
- package/apps/studio/stickers/project-status/group-113.png +0 -0
- package/apps/studio/stickers/project-status/group-114.png +0 -0
- package/apps/studio/stickers/project-status/group-115.png +0 -0
- package/apps/studio/stickers/project-status/group-117.png +0 -0
- package/apps/studio/stickers/project-status/group-118.png +0 -0
- package/apps/studio/stickers/project-status/group-119.png +0 -0
- package/apps/studio/stickers/project-status/group-120.png +0 -0
- package/apps/studio/stickers/project-status/group-121.png +0 -0
- package/apps/studio/stickers/project-status/group-122.png +0 -0
- package/apps/studio/stickers/project-status/group-41.png +0 -0
- package/apps/studio/stickers/project-status/group-42.png +0 -0
- package/apps/studio/stickers/project-status/group-43.png +0 -0
- package/apps/studio/stickers/project-status/group-44.png +0 -0
- package/apps/studio/stickers/project-status/group-45.png +0 -0
- package/apps/studio/stickers/project-status/group-46.png +0 -0
- package/apps/studio/stickers/project-status/group-47.png +0 -0
- package/apps/studio/stickers/project-status/group-48.png +0 -0
- package/apps/studio/stickers/project-status/group-49.png +0 -0
- package/apps/studio/stickers/project-status/group-50.png +0 -0
- package/apps/studio/stickers/project-status/group-51.png +0 -0
- package/apps/studio/stickers/project-status/group-52.png +0 -0
- package/apps/studio/stickers/project-status/group-53.png +0 -0
- package/apps/studio/stickers/project-status/group-54.png +0 -0
- package/apps/studio/stickers/project-status/group-55.png +0 -0
- package/apps/studio/stickers/project-status/group-56.png +0 -0
- package/apps/studio/stickers/project-status/group-57.png +0 -0
- package/apps/studio/stickers/project-status/group-58.png +0 -0
- package/apps/studio/stickers/project-status/group-59.png +0 -0
- package/apps/studio/stickers/project-status/group-60.png +0 -0
- package/apps/studio/stickers/project-status/group-61.png +0 -0
- package/apps/studio/stickers/project-status/group-62.png +0 -0
- package/apps/studio/stickers/project-status/group-63.png +0 -0
- package/apps/studio/stickers/project-status/group-64.png +0 -0
- package/apps/studio/stickers/project-status/group-65.png +0 -0
- package/apps/studio/stickers/project-status/group-66.png +0 -0
- package/apps/studio/stickers/project-status/group-67.png +0 -0
- package/apps/studio/stickers/project-status/group-68.png +0 -0
- package/apps/studio/stickers/project-status/group-69.png +0 -0
- package/apps/studio/stickers/project-status/group-70.png +0 -0
- package/apps/studio/stickers/project-status/group-71.png +0 -0
- package/apps/studio/stickers/project-status/group-72.png +0 -0
- package/apps/studio/stickers/project-status/group-73.png +0 -0
- package/apps/studio/stickers/project-status/group-74.png +0 -0
- package/apps/studio/stickers/project-status/group-75.png +0 -0
- package/apps/studio/stickers/project-status/group-76.png +0 -0
- package/apps/studio/stickers/project-status/group-77.png +0 -0
- package/apps/studio/stickers/project-status/group-78.png +0 -0
- package/apps/studio/stickers/project-status/group-79.png +0 -0
- package/apps/studio/stickers/project-status/group-80.png +0 -0
- package/apps/studio/stickers/project-status/group-81.png +0 -0
- package/apps/studio/stickers/project-status/group-82.png +0 -0
- package/apps/studio/stickers/project-status/group-83.png +0 -0
- package/apps/studio/stickers/project-status/group-84.png +0 -0
- package/apps/studio/stickers/project-status/group-85.png +0 -0
- package/apps/studio/stickers/project-status/group-86.png +0 -0
- package/apps/studio/stickers/project-status/group-87.png +0 -0
- package/apps/studio/stickers/project-status/group-88.png +0 -0
- package/apps/studio/stickers/project-status/group-89.png +0 -0
- package/apps/studio/stickers/project-status/group-90.png +0 -0
- package/apps/studio/stickers/project-status/group-91.png +0 -0
- package/apps/studio/stickers/project-status/group-92.png +0 -0
- package/apps/studio/stickers/project-status/group-93.png +0 -0
- package/apps/studio/stickers/project-status/group-94.png +0 -0
- package/apps/studio/stickers/project-status/group-96.png +0 -0
- package/apps/studio/stickers/project-status/group-97.png +0 -0
- package/apps/studio/stickers/project-status/group-98.png +0 -0
- package/apps/studio/stickers/project-status/group-99.png +0 -0
- package/apps/studio/stickers/project-status/manifest.json +328 -0
- package/apps/studio/test/_helpers.ts +15 -2
- package/apps/studio/test/acp-bootstrap-brief.test.ts +11 -0
- package/apps/studio/test/acp-bridge.test.ts +34 -1
- package/apps/studio/test/acp-commands.test.ts +2 -1
- package/apps/studio/test/acp-env.test.ts +30 -0
- package/apps/studio/test/acp-plugin-bootstrap.test.ts +23 -57
- package/apps/studio/test/acp-session-plugins.test.ts +72 -1
- package/apps/studio/test/annotation-strokes-command.test.ts +17 -0
- package/apps/studio/test/annotations-layer.test.ts +174 -0
- package/apps/studio/test/asset-api.test.ts +3 -3
- package/apps/studio/test/canvas-edit.test.ts +17 -6
- package/apps/studio/test/canvas-origin-gate.test.ts +50 -0
- package/apps/studio/test/csp-canvas-shell.test.ts +11 -0
- package/apps/studio/test/csrf-write-guard.test.ts +23 -1
- package/apps/studio/test/design-setup-readiness.test.ts +155 -0
- package/apps/studio/test/dynamic-text-edit.test.ts +162 -0
- package/apps/studio/test/edit-source-command.test.ts +23 -0
- package/apps/studio/test/element-structural-edit.test.ts +154 -0
- package/apps/studio/test/fixtures/fake-claude-auth.mjs +13 -0
- package/apps/studio/test/footage-store.test.ts +100 -0
- package/apps/studio/test/generate-route.test.ts +106 -0
- package/apps/studio/test/git-branches.test.ts +97 -0
- package/apps/studio/test/github-api.test.ts +56 -1
- package/apps/studio/test/import-asset-browser.test.ts +64 -0
- package/apps/studio/test/import-asset-route.test.ts +71 -0
- package/apps/studio/test/import-asset.test.ts +399 -0
- package/apps/studio/test/import-brand-browser.test.ts +88 -0
- package/apps/studio/test/import-brand-route.test.ts +120 -0
- package/apps/studio/test/import-brand.test.ts +206 -0
- package/apps/studio/test/import-tokens.test.ts +722 -0
- package/apps/studio/test/input-router.test.ts +33 -0
- package/apps/studio/test/inspect-script-syntax.test.ts +53 -0
- package/apps/studio/test/media-commit-chain.test.ts +210 -0
- package/apps/studio/test/photo-bg-remove-validation.test.ts +71 -0
- package/apps/studio/test/photo-canvas-bundle.test.ts +61 -0
- package/apps/studio/test/photo-edit-api.test.ts +201 -0
- package/apps/studio/test/photo-filters.test.ts +149 -0
- package/apps/studio/test/photo-pipeline.test.ts +106 -0
- package/apps/studio/test/photo-taxonomy.test.ts +96 -0
- package/apps/studio/test/read-annotations.test.ts +164 -0
- package/apps/studio/test/readiness.test.ts +13 -11
- package/apps/studio/test/scaffold-design.test.ts +122 -0
- package/apps/studio/test/shell-importmap.test.ts +49 -0
- package/apps/studio/test/text-editability-stamp.test.ts +131 -0
- package/apps/studio/test/timeline-parse.test.ts +57 -0
- package/apps/studio/test/tool-palette-insert-anchor.test.ts +5 -3
- package/apps/studio/test/tour-overlay.test.tsx +16 -0
- package/apps/studio/test/undo-stack.test.ts +33 -0
- package/apps/studio/test/video-asset.test.ts +5 -5
- package/apps/studio/test/video-render-bridge.test.ts +23 -1
- package/apps/studio/text-caret.ts +239 -0
- package/apps/studio/tool-palette.tsx +49 -21
- package/apps/studio/ui-prefs.ts +130 -0
- package/apps/studio/undo-stack.ts +94 -2
- package/apps/studio/use-annotation-resize.tsx +4 -4
- package/apps/studio/use-canvas-media-drop.tsx +40 -1
- package/apps/studio/use-chrome-visibility.tsx +8 -2
- package/apps/studio/use-selection-set.tsx +21 -0
- package/apps/studio/video-comp.tsx +48 -7
- package/apps/studio/whats-new.json +149 -0
- package/apps/studio/ws.ts +5 -0
- package/cli/bin/maude.mjs +6 -6
- package/cli/commands/design.mjs +67 -3
- package/cli/lib/gitignore-block.mjs +2 -0
- package/cli/lib/pkg-root.mjs +107 -0
- package/cli/lib/pkg-root.test.mjs +79 -0
- package/cli/lib/reconstruct-toolset.test.mjs +194 -0
- package/cli/lib/update-check.mjs +8 -0
- package/package.json +13 -11
- package/plugins/design/dependencies.json +18 -0
- package/plugins/design/templates/_shell.html +1 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# photo-adjust.sh — feature-photo-editor (Stage G, Task 19). Headless PARAMETRIC
|
|
3
|
+
# photo edit: assemble a PhotoEdit from flags and PUT it to the running dev
|
|
4
|
+
# server's /_api/photo-edit route so an agent (or a human) can tune a photo
|
|
5
|
+
# without opening a browser or clicking a slider.
|
|
6
|
+
#
|
|
7
|
+
# Deliberately the THIN, NON-BROWSER sibling of photo-bg-remove.sh. Background
|
|
8
|
+
# removal needs a browser round-trip because @imgly runs the ML model client-
|
|
9
|
+
# side; parametric edits are pure JSON, so a direct curl to the already-validated
|
|
10
|
+
# route (the photo-store cap stack — validatePhotoEdit + sha8 + containment +
|
|
11
|
+
# size cap — is the gate) is the whole job. DO NOT "fix" this asymmetry by making
|
|
12
|
+
# this verb heavier: there is no inference step here, so a harness canvas +
|
|
13
|
+
# agent-browser would be pure overhead.
|
|
14
|
+
#
|
|
15
|
+
# Reached via `maude design photo-adjust` (never a raw bin path — DDR-062).
|
|
16
|
+
#
|
|
17
|
+
# Usage:
|
|
18
|
+
# photo-adjust.sh --asset <assets/<sha8>.<ext> | <sha8>> [--root <repo>]
|
|
19
|
+
# [--brightness N] [--contrast N] [--saturation N] [--exposure N]
|
|
20
|
+
# [--hue N] [--sepia N] [--grayscale N] [--invert N]
|
|
21
|
+
# [--duotone "#aabbcc,#ddeeff[,intensity]"]
|
|
22
|
+
# [--grain "amount[,size]"]
|
|
23
|
+
# [--pattern "type[,scale,opacity,blend]"]
|
|
24
|
+
# [--mask "preset[,strength]"]
|
|
25
|
+
# [--replace] (overwrite the sidecar instead of merging onto it)
|
|
26
|
+
# [--reset] (clear the sidecar to neutral / unedited)
|
|
27
|
+
#
|
|
28
|
+
# Adjustment ranges (normalized, 0 = neutral): brightness/contrast/saturation/
|
|
29
|
+
# exposure ∈ −1..1, hue ∈ −180..180 (deg), sepia/grayscale/invert ∈ 0..1.
|
|
30
|
+
# Merges onto the existing sidecar by default (successive calls accumulate).
|
|
31
|
+
#
|
|
32
|
+
# Requires a running dev server (caller runs `maude design server-up` first);
|
|
33
|
+
# reads the port from <designRoot>/_server.json.
|
|
34
|
+
#
|
|
35
|
+
# Stdout (last line): the sidecar path `assets/<sha8>.photo.json` (for $(...)).
|
|
36
|
+
# Stderr: progress / diagnostics.
|
|
37
|
+
# Exit: 0 ok / 1 server or asset problem / 2 bad args / 3 server rejected edit.
|
|
38
|
+
|
|
39
|
+
set -euo pipefail
|
|
40
|
+
|
|
41
|
+
ASSET="" REPO="" REPLACE=0 RESET=0
|
|
42
|
+
BRIGHTNESS="" CONTRAST="" SATURATION="" EXPOSURE="" HUE="" SEPIA="" GRAYSCALE="" INVERT=""
|
|
43
|
+
DUOTONE="" GRAIN="" PATTERN="" MASK=""
|
|
44
|
+
|
|
45
|
+
while [ $# -gt 0 ]; do
|
|
46
|
+
case "$1" in
|
|
47
|
+
--asset) ASSET="$2"; shift 2 ;;
|
|
48
|
+
--root) REPO="$2"; shift 2 ;;
|
|
49
|
+
--brightness) BRIGHTNESS="$2"; shift 2 ;;
|
|
50
|
+
--contrast) CONTRAST="$2"; shift 2 ;;
|
|
51
|
+
--saturation) SATURATION="$2"; shift 2 ;;
|
|
52
|
+
--exposure) EXPOSURE="$2"; shift 2 ;;
|
|
53
|
+
--hue) HUE="$2"; shift 2 ;;
|
|
54
|
+
--sepia) SEPIA="$2"; shift 2 ;;
|
|
55
|
+
--grayscale) GRAYSCALE="$2"; shift 2 ;;
|
|
56
|
+
--invert) INVERT="$2"; shift 2 ;;
|
|
57
|
+
--duotone) DUOTONE="$2"; shift 2 ;;
|
|
58
|
+
--grain) GRAIN="$2"; shift 2 ;;
|
|
59
|
+
--pattern) PATTERN="$2"; shift 2 ;;
|
|
60
|
+
--mask) MASK="$2"; shift 2 ;;
|
|
61
|
+
--replace) REPLACE=1; shift ;;
|
|
62
|
+
--reset) RESET=1; shift ;;
|
|
63
|
+
--help|-h) sed -n '2,39p' "$0" | sed 's/^# \?//'; exit 0 ;;
|
|
64
|
+
*) echo "photo-adjust.sh: unknown arg '$1'" >&2; exit 2 ;;
|
|
65
|
+
esac
|
|
66
|
+
done
|
|
67
|
+
|
|
68
|
+
[ -n "$ASSET" ] || { echo "photo-adjust.sh: --asset is required" >&2; exit 2; }
|
|
69
|
+
command -v jq >/dev/null 2>&1 || { echo "photo-adjust.sh: jq is required" >&2; exit 1; }
|
|
70
|
+
|
|
71
|
+
if [ -z "$REPO" ]; then
|
|
72
|
+
REPO="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
|
73
|
+
fi
|
|
74
|
+
DESIGN_ROOT="$REPO/.design"
|
|
75
|
+
[ -d "$DESIGN_ROOT" ] || { echo "photo-adjust.sh: no .design/ under $REPO" >&2; exit 1; }
|
|
76
|
+
|
|
77
|
+
STATE="$DESIGN_ROOT/_server.json"
|
|
78
|
+
[ -f "$STATE" ] || { echo "photo-adjust.sh: no _server.json (start the dev server first)" >&2; exit 1; }
|
|
79
|
+
PORT=$(jq -r '.port // empty' "$STATE" 2>/dev/null)
|
|
80
|
+
[ -n "$PORT" ] || { echo "photo-adjust.sh: could not read port from $STATE" >&2; exit 1; }
|
|
81
|
+
|
|
82
|
+
BASE_URL="http://127.0.0.1:$PORT/_api/photo-edit"
|
|
83
|
+
# URL-encode the asset param minimally (slashes are safe as a query value here).
|
|
84
|
+
ENC_ASSET=$(printf '%s' "$ASSET" | jq -sRr @uri)
|
|
85
|
+
URL="$BASE_URL?asset=$ENC_ASSET"
|
|
86
|
+
|
|
87
|
+
# --reset short-circuits: write a neutral sidecar.
|
|
88
|
+
if [ "$RESET" -eq 1 ]; then
|
|
89
|
+
RESP=$(curl -s -X PUT -H 'Content-Type: application/json' -d '{}' "$URL")
|
|
90
|
+
OK=$(printf '%s' "$RESP" | jq -r '.ok // false' 2>/dev/null || echo false)
|
|
91
|
+
[ "$OK" = "true" ] || { echo "photo-adjust.sh: reset rejected: $RESP" >&2; exit 3; }
|
|
92
|
+
printf '%s\n' "$(printf '%s' "$RESP" | jq -r '.path')"
|
|
93
|
+
exit 0
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# Assemble the patch from set flags (unset flags are omitted entirely).
|
|
97
|
+
PATCH=$(jq -n \
|
|
98
|
+
--arg brightness "$BRIGHTNESS" --arg contrast "$CONTRAST" --arg saturation "$SATURATION" \
|
|
99
|
+
--arg exposure "$EXPOSURE" --arg hue "$HUE" --arg sepia "$SEPIA" \
|
|
100
|
+
--arg grayscale "$GRAYSCALE" --arg invert "$INVERT" \
|
|
101
|
+
--arg duotone "$DUOTONE" --arg grain "$GRAIN" --arg pattern "$PATTERN" --arg mask "$MASK" '
|
|
102
|
+
def n($s): if $s == "" then null else ($s | tonumber) end;
|
|
103
|
+
def adj:
|
|
104
|
+
{ brightness: n($brightness), contrast: n($contrast), saturation: n($saturation),
|
|
105
|
+
exposure: n($exposure), hue: n($hue), sepia: n($sepia),
|
|
106
|
+
grayscale: n($grayscale), invert: n($invert) }
|
|
107
|
+
| with_entries(select(.value != null));
|
|
108
|
+
def duo:
|
|
109
|
+
if $duotone == "" then null
|
|
110
|
+
else ($duotone | split(",")) as $p
|
|
111
|
+
| { enabled: true, colorA: $p[0], colorB: $p[1] }
|
|
112
|
+
+ (if ($p | length) > 2 then { intensity: ($p[2] | tonumber) } else {} end)
|
|
113
|
+
end;
|
|
114
|
+
def gr:
|
|
115
|
+
if $grain == "" then null
|
|
116
|
+
else ($grain | split(",")) as $p
|
|
117
|
+
| { enabled: true, amount: ($p[0] | tonumber) }
|
|
118
|
+
+ (if ($p | length) > 1 then { size: ($p[1] | tonumber) } else {} end)
|
|
119
|
+
end;
|
|
120
|
+
def pat:
|
|
121
|
+
if $pattern == "" then null
|
|
122
|
+
else ($pattern | split(",")) as $p
|
|
123
|
+
| { enabled: true, type: $p[0] }
|
|
124
|
+
+ (if ($p | length) > 1 then { scale: ($p[1] | tonumber) } else {} end)
|
|
125
|
+
+ (if ($p | length) > 2 then { opacity: ($p[2] | tonumber) } else {} end)
|
|
126
|
+
+ (if ($p | length) > 3 then { blend: $p[3] } else {} end)
|
|
127
|
+
end;
|
|
128
|
+
def msk:
|
|
129
|
+
if $mask == "" then null
|
|
130
|
+
else ($mask | split(",")) as $p
|
|
131
|
+
| { preset: $p[0] }
|
|
132
|
+
+ (if ($p | length) > 1 then { strength: ($p[1] | tonumber) } else {} end)
|
|
133
|
+
end;
|
|
134
|
+
({}
|
|
135
|
+
+ (adj as $a | if ($a | length) > 0 then { adjustments: $a } else {} end)
|
|
136
|
+
+ (duo as $d | if $d then { duotone: $d } else {} end)
|
|
137
|
+
+ (gr as $g | if $g then { grain: $g } else {} end)
|
|
138
|
+
+ (pat as $p | if $p then { pattern: $p } else {} end)
|
|
139
|
+
+ (msk as $m | if $m then { mask: $m } else {} end))
|
|
140
|
+
')
|
|
141
|
+
|
|
142
|
+
if [ "$(printf '%s' "$PATCH" | jq -r 'keys | length')" = "0" ]; then
|
|
143
|
+
echo "photo-adjust.sh: no edit flags given (nothing to do)" >&2
|
|
144
|
+
exit 2
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
if [ "$REPLACE" -eq 1 ]; then
|
|
148
|
+
BODY="$PATCH"
|
|
149
|
+
else
|
|
150
|
+
# Merge onto the current sidecar (deep object merge via jq's `*`), dropping the
|
|
151
|
+
# server-stamped `version` (it's re-stamped on write).
|
|
152
|
+
BASE=$(curl -s "$URL" 2>/dev/null || echo '{}')
|
|
153
|
+
printf '%s' "$BASE" | jq -e . >/dev/null 2>&1 || BASE='{}'
|
|
154
|
+
BODY=$(jq -n --argjson base "$BASE" --argjson patch "$PATCH" '($base | del(.version)) * $patch')
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
RESP=$(curl -s -X PUT -H 'Content-Type: application/json' -d "$BODY" "$URL")
|
|
158
|
+
OK=$(printf '%s' "$RESP" | jq -r '.ok // false' 2>/dev/null || echo false)
|
|
159
|
+
if [ "$OK" != "true" ]; then
|
|
160
|
+
echo "photo-adjust.sh: server rejected the edit: $RESP" >&2
|
|
161
|
+
exit 3
|
|
162
|
+
fi
|
|
163
|
+
printf '%s\n' "$(printf '%s' "$RESP" | jq -r '.path')"
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# photo-bg-remove.sh — feature-photo-editor (Stage G, Task 18). Headless
|
|
3
|
+
# background removal: mirrors draw-proof.sh's structure (throwaway harness
|
|
4
|
+
# canvas + agent-browser drive) but the "proof" here isn't a screenshot ladder
|
|
5
|
+
# — it's the client-side @imgly ML pass actually running and reporting back
|
|
6
|
+
# through DOM attributes agent-browser polls, since there's no other channel
|
|
7
|
+
# to cross the process boundary out of a headless browser.
|
|
8
|
+
#
|
|
9
|
+
# Design note (the decision this task was originally deferred on): the harness
|
|
10
|
+
# canvas runs inside the split-origin (DDR-054) canvas iframe, but BOTH
|
|
11
|
+
# `/_api/asset` and `/_api/photo-edit` are already canvas-safe routes (see
|
|
12
|
+
# their CANVAS_SAFE_API comments in http.ts) — so the harness component
|
|
13
|
+
# (`PhotoBgRemoveHarness`, canvas-lib.tsx) posts the matte and the updated
|
|
14
|
+
# PhotoEdit sidecar directly from the canvas origin. No cross-origin relay,
|
|
15
|
+
# no base64-through-a-DOM-attribute round-trip needed.
|
|
16
|
+
#
|
|
17
|
+
# Reached via `maude design photo-bg-remove` (never a raw bin path — DDR-062).
|
|
18
|
+
#
|
|
19
|
+
# Security hardening (fix-photo-editor-followup-debt, Stage A): `--asset` is
|
|
20
|
+
# validated against the strict `assets/<sha8>.<ext>` shape (mirrors
|
|
21
|
+
# photo-store.ts's SHA_RE) rather than a loose glob — a permissive check
|
|
22
|
+
# previously let a crafted `--asset` value splice unescaped content into the
|
|
23
|
+
# generated harness `.tsx`. The harness-file write is symlink-safe (write to a
|
|
24
|
+
# freshly O_EXCL-created tmp path, then atomic `mv` into place — `mv`/rename(2)
|
|
25
|
+
# replaces a destination symlink's OWN dirent rather than following it, so even
|
|
26
|
+
# a pre-planted symlink at the harness path can't redirect the write into a
|
|
27
|
+
# real reviewed canvas file). The stdout deliverable is shape-validated before
|
|
28
|
+
# print. `--slug` failure hard-fails instead of downgrading to an unsanitized
|
|
29
|
+
# fallback. Concurrent invocations against the SAME asset serialize via a
|
|
30
|
+
# `mkdir`-based lock (portable — `flock` isn't available on macOS by default).
|
|
31
|
+
#
|
|
32
|
+
# Usage:
|
|
33
|
+
# photo-bg-remove.sh --asset <assets/<sha8>.<ext>> [--root <repo>]
|
|
34
|
+
# [--slug <name>] [--timeout <secs>]
|
|
35
|
+
#
|
|
36
|
+
# Requires a running dev server (caller runs `maude design server-up` first);
|
|
37
|
+
# reads the port from <designRoot>/_server.json. Requires agent-browser (the
|
|
38
|
+
# ML pass needs a real WASM/WebGPU-capable browser driving real DOM state —
|
|
39
|
+
# there is no non-browser path, unlike photo-adjust.sh's plain curl).
|
|
40
|
+
#
|
|
41
|
+
# Stdout (last line): the new matte asset path `assets/<sha8>.png` (for $(...)).
|
|
42
|
+
# Stderr: progress / diagnostics.
|
|
43
|
+
# Exit: 0 success / 1 missing input, server, or agent-browser / 2 bad args
|
|
44
|
+
# / 3 drive/navigation failed / 4 the harness itself reported an error
|
|
45
|
+
# (or produced a malformed result) / 5 timed out waiting for the ML
|
|
46
|
+
# pass to finish / 6 another run is already in progress for this asset
|
|
47
|
+
|
|
48
|
+
set -uo pipefail
|
|
49
|
+
|
|
50
|
+
ASSET="" SLUG="" REPO="" TIMEOUT=150
|
|
51
|
+
# Mirrors photo-store.ts's SHA_RE shape: lowercase hex sha8 (8-64 chars),
|
|
52
|
+
# case-insensitive extension. Shared between --asset validation (Task 1) and
|
|
53
|
+
# the stdout-deliverable validation (Task 3) so the two checks can't drift.
|
|
54
|
+
ASSET_SHAPE_RE='^assets/[0-9a-f]{8,64}\.[a-zA-Z0-9]+$'
|
|
55
|
+
|
|
56
|
+
while [ $# -gt 0 ]; do
|
|
57
|
+
case "$1" in
|
|
58
|
+
--asset) ASSET="$2"; shift 2 ;;
|
|
59
|
+
--slug) SLUG="$2"; shift 2 ;;
|
|
60
|
+
--root) REPO="$2"; shift 2 ;;
|
|
61
|
+
--timeout) TIMEOUT="$2"; shift 2 ;;
|
|
62
|
+
--help|-h)
|
|
63
|
+
sed -n '2,39p' "$0" | sed 's/^# \?//'
|
|
64
|
+
exit 0
|
|
65
|
+
;;
|
|
66
|
+
*)
|
|
67
|
+
echo "photo-bg-remove.sh: unknown arg '$1' (try --help)" >&2
|
|
68
|
+
exit 2
|
|
69
|
+
;;
|
|
70
|
+
esac
|
|
71
|
+
done
|
|
72
|
+
|
|
73
|
+
[ -n "$ASSET" ] || { echo "photo-bg-remove.sh: --asset <assets/<sha8>.<ext>> required" >&2; exit 2; }
|
|
74
|
+
# Strip a leading designRoot-relative slash/prefix if the caller passed one.
|
|
75
|
+
ASSET="${ASSET#/}"
|
|
76
|
+
# Strict shape check (Task 1) — replaces the old `case "$ASSET" in assets/*)`
|
|
77
|
+
# glob, which only checked the PREFIX and let anything after it (quotes,
|
|
78
|
+
# backticks, `../`, JSX-breaking sequences) ride unescaped into the generated
|
|
79
|
+
# harness .tsx below. `[[ =~ ]]` against a character-class regex is the bash
|
|
80
|
+
# equivalent of photo-store.ts's `SHA_RE` hex check — `..`, `/`, quotes, and
|
|
81
|
+
# any non-hex/non-alnum content can never match, so nothing but a clean
|
|
82
|
+
# `assets/<sha8>.<ext>` reference survives validation. Captures the bare sha8
|
|
83
|
+
# for reuse as a filesystem-safe lock key (Task 6).
|
|
84
|
+
if [[ "$ASSET" =~ ^assets/([0-9a-f]{8,64})\.[a-zA-Z0-9]+$ ]]; then
|
|
85
|
+
ASSET_KEY="${BASH_REMATCH[1]}"
|
|
86
|
+
else
|
|
87
|
+
echo "photo-bg-remove.sh: --asset must look like assets/<sha8>.<ext> (lowercase hex, 8-64 chars), got '$ASSET'" >&2
|
|
88
|
+
exit 2
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
92
|
+
[ -n "$SLUG" ] || SLUG=$(basename "$ASSET" | sed -E 's/\.[^.]+$//')
|
|
93
|
+
# Hard-fail (Task 4) instead of downgrading to a lowercased-but-unstripped
|
|
94
|
+
# fallback on slug.sh failure — a safety subprocess's failure must never
|
|
95
|
+
# silently continue with a less-safe code path. This repo has documented
|
|
96
|
+
# history (DDR-045) of sibling-script resolution breaking specifically in
|
|
97
|
+
# packaged/compiled distributions, so this failure mode is realistic.
|
|
98
|
+
# (SLUG_INPUT is captured separately so a failed substitution — which still
|
|
99
|
+
# assigns its, here empty, stdout to $SLUG — doesn't blank the diagnostic.)
|
|
100
|
+
SLUG_INPUT="$SLUG"
|
|
101
|
+
if ! SLUG="$(bash "$SCRIPT_DIR/slug.sh" "$SLUG_INPUT" 2>/dev/null)"; then
|
|
102
|
+
echo "photo-bg-remove.sh: slug.sh failed to normalize slug '$SLUG_INPUT' — refusing to fall back to an unsanitized value" >&2
|
|
103
|
+
exit 1
|
|
104
|
+
fi
|
|
105
|
+
# Assigned this early (not just before the agent-browser open call below) so
|
|
106
|
+
# it's always bound by the time `trap cleanup EXIT` is installed — a security
|
|
107
|
+
# review of this plan found that `cleanup()` unconditionally reads $SESSION,
|
|
108
|
+
# and under `set -u` an exit on one of the harness-write failure paths (before
|
|
109
|
+
# SESSION used to be assigned) would abort mid-cleanup on the unbound
|
|
110
|
+
# variable, skipping the `rmdir "$LOCK_DIR"` below it and leaking the lock.
|
|
111
|
+
SESSION="photo-bgremove-$SLUG-$$"
|
|
112
|
+
|
|
113
|
+
# ---------- resolve repo + design root + port ----------
|
|
114
|
+
if [ -z "$REPO" ]; then
|
|
115
|
+
REPO="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
|
116
|
+
fi
|
|
117
|
+
DESIGN_ROOT="$REPO/.design"
|
|
118
|
+
[ -d "$DESIGN_ROOT" ] || { echo "photo-bg-remove.sh: no .design/ under $REPO" >&2; exit 1; }
|
|
119
|
+
[ -f "$DESIGN_ROOT/$ASSET" ] || { echo "photo-bg-remove.sh: asset not found: $DESIGN_ROOT/$ASSET" >&2; exit 1; }
|
|
120
|
+
|
|
121
|
+
STATE="$DESIGN_ROOT/_server.json"
|
|
122
|
+
[ -f "$STATE" ] || { echo "photo-bg-remove.sh: $STATE missing — run \`maude design server-up\` first" >&2; exit 1; }
|
|
123
|
+
if command -v jq >/dev/null 2>&1; then
|
|
124
|
+
PORT=$(jq -r .port "$STATE" 2>/dev/null)
|
|
125
|
+
else
|
|
126
|
+
PORT=$(sed -nE 's/.*"port"[[:space:]]*:[[:space:]]*([0-9]+).*/\1/p' "$STATE" | head -n1)
|
|
127
|
+
fi
|
|
128
|
+
[ -n "$PORT" ] || { echo "photo-bg-remove.sh: no port in $STATE" >&2; exit 1; }
|
|
129
|
+
|
|
130
|
+
# ---------- resolve agent-browser (hard dependency — see header) ----------
|
|
131
|
+
# Mirrors screenshot.sh's MAUDE_AGENT_BROWSER override (DDR-144 attacker-F4 — an
|
|
132
|
+
# explicit single-binary pointer, never a PATH prepend a same-user attacker
|
|
133
|
+
# could shadow) and its ensure-browser.sh chrome resolution.
|
|
134
|
+
AB="${MAUDE_AGENT_BROWSER:-agent-browser}"
|
|
135
|
+
command -v "$AB" >/dev/null 2>&1 || {
|
|
136
|
+
echo "photo-bg-remove.sh: agent-browser not found (required — the ML pass needs a real WASM/WebGPU browser, there is no non-browser fallback here)" >&2
|
|
137
|
+
exit 1
|
|
138
|
+
}
|
|
139
|
+
if [ -z "${AGENT_BROWSER_EXECUTABLE_PATH:-}" ]; then
|
|
140
|
+
ES_FLAGS="--quiet"
|
|
141
|
+
[ -z "${MAUDE_DEV_SERVER_ROOT:-}" ] && ES_FLAGS="--quiet --no-download"
|
|
142
|
+
BROWSER_PATH="$(bash "$SCRIPT_DIR/ensure-browser.sh" $ES_FLAGS 2>/dev/null)"
|
|
143
|
+
[ -n "$BROWSER_PATH" ] && export AGENT_BROWSER_EXECUTABLE_PATH="$BROWSER_PATH"
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
# ---------- per-asset concurrency guard (Task 6) ----------
|
|
147
|
+
# `mkdir` is atomic on every POSIX filesystem and needs no extra binary —
|
|
148
|
+
# `flock` isn't available on macOS by default, so this is the portable choice.
|
|
149
|
+
# Keyed on the asset's sha8 (already validated hex — safe as a path segment),
|
|
150
|
+
# NOT global, so two different assets still run concurrently. Fail-fast rather
|
|
151
|
+
# than queue/wait: a full job queue is out of scope for this backstop, whose
|
|
152
|
+
# only job is refusing to let a runaway loop spawn unbounded concurrent
|
|
153
|
+
# browser+WASM processes against the same asset.
|
|
154
|
+
PHOTO_DIR="$DESIGN_ROOT/_photo"
|
|
155
|
+
mkdir -p "$PHOTO_DIR"
|
|
156
|
+
LOCK_DIR="$PHOTO_DIR/.lock.$ASSET_KEY"
|
|
157
|
+
TMP_HARNESS=""
|
|
158
|
+
if ! mkdir "$LOCK_DIR" 2>/dev/null; then
|
|
159
|
+
echo "photo-bg-remove.sh: another photo-bg-remove run is already in progress for asset '$ASSET' — refusing to run concurrently against the same asset (lock: ${LOCK_DIR#$REPO/})" >&2
|
|
160
|
+
exit 6
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
cleanup() {
|
|
164
|
+
"$AB" --session "$SESSION" close >/dev/null 2>&1 || true
|
|
165
|
+
[ -n "$TMP_HARNESS" ] && rm -f "$TMP_HARNESS" 2>/dev/null
|
|
166
|
+
rmdir "$LOCK_DIR" 2>/dev/null || true
|
|
167
|
+
}
|
|
168
|
+
trap cleanup EXIT
|
|
169
|
+
|
|
170
|
+
# ---------- generate the harness canvas (symlink-safe — Task 2) ----------
|
|
171
|
+
# Write to a freshly O_CREAT|O_EXCL-created tmp path in the SAME directory
|
|
172
|
+
# (same filesystem, so the rename below is atomic), then `mv` into place.
|
|
173
|
+
# `mv`/rename(2) replaces the destination NAME, never dereferences it — so
|
|
174
|
+
# even a pre-planted symlink at $HARNESS_TSX gets its dirent swapped out
|
|
175
|
+
# rather than followed, unlike the bare `{ ... } > "$HARNESS_TSX"` truncating
|
|
176
|
+
# redirect this replaces (which WOULD follow a symlink and could splice
|
|
177
|
+
# attacker-controlled JSX into whatever real file it pointed at). `set -C`
|
|
178
|
+
# (noclobber) makes bash open the tmp path with O_EXCL, so a pre-existing tmp
|
|
179
|
+
# path (including a dangling symlink) fails the write instead of following it
|
|
180
|
+
# — the bash equivalent of sync/atomic-write.ts's Node `wx` flag (DDR-054 §2c).
|
|
181
|
+
HARNESS_TSX="$PHOTO_DIR/$SLUG.bgremove.tsx"
|
|
182
|
+
TMP_HARNESS="$PHOTO_DIR/.$SLUG.bgremove.tsx.tmp.$$.$RANDOM$RANDOM"
|
|
183
|
+
if ! (
|
|
184
|
+
set -C
|
|
185
|
+
{
|
|
186
|
+
echo "import { PhotoBgRemoveHarness } from '@maude/canvas-lib';"
|
|
187
|
+
echo
|
|
188
|
+
echo "// AUTO-GENERATED by \`maude design photo-bg-remove\` — headless bg-removal proof"
|
|
189
|
+
echo "// harness. Lives under _photo/ (gitignored); safe to delete + regenerate."
|
|
190
|
+
echo "export default function PhotoBgRemoveCanvas() {"
|
|
191
|
+
printf ' return <PhotoBgRemoveHarness source=%s />;\n' "\"$ASSET\""
|
|
192
|
+
echo "}"
|
|
193
|
+
} > "$TMP_HARNESS"
|
|
194
|
+
); then
|
|
195
|
+
echo "photo-bg-remove.sh: failed to create harness scratch file (possible pre-existing path at $TMP_HARNESS)" >&2
|
|
196
|
+
exit 1
|
|
197
|
+
fi
|
|
198
|
+
if ! mv "$TMP_HARNESS" "$HARNESS_TSX"; then
|
|
199
|
+
echo "photo-bg-remove.sh: atomic rename of harness file failed" >&2
|
|
200
|
+
exit 1
|
|
201
|
+
fi
|
|
202
|
+
TMP_HARNESS="" # renamed away — nothing left for cleanup() to remove
|
|
203
|
+
echo "→ harness canvas: ${HARNESS_TSX#$REPO/}" >&2
|
|
204
|
+
|
|
205
|
+
REL="_photo/$SLUG.bgremove.tsx"
|
|
206
|
+
URL="http://localhost:$PORT/_canvas-shell.html?canvas=$REL"
|
|
207
|
+
SEL="[data-photo-bgremove-status]"
|
|
208
|
+
|
|
209
|
+
echo "→ driving $URL (session $SESSION)" >&2
|
|
210
|
+
if ! "$AB" --session "$SESSION" open "$URL" >&2; then
|
|
211
|
+
echo "photo-bg-remove.sh: agent-browser failed to open the harness canvas" >&2
|
|
212
|
+
exit 3
|
|
213
|
+
fi
|
|
214
|
+
|
|
215
|
+
# ---------- poll the harness's status attribute ----------
|
|
216
|
+
# The ML pass includes a first-use ~40 MB model-weight fetch (Task 11/12) —
|
|
217
|
+
# this can dominate the wait far more than inference itself, hence the
|
|
218
|
+
# generous default timeout.
|
|
219
|
+
ELAPSED=0
|
|
220
|
+
STATUS="pending"
|
|
221
|
+
while [ "$ELAPSED" -lt "$TIMEOUT" ]; do
|
|
222
|
+
STATUS="$("$AB" --session "$SESSION" get attr "$SEL" data-photo-bgremove-status 2>/dev/null | tr -d '"')"
|
|
223
|
+
case "$STATUS" in
|
|
224
|
+
done|error) break ;;
|
|
225
|
+
esac
|
|
226
|
+
sleep 2
|
|
227
|
+
ELAPSED=$((ELAPSED + 2))
|
|
228
|
+
done
|
|
229
|
+
|
|
230
|
+
if [ "$STATUS" = "error" ]; then
|
|
231
|
+
ERR="$("$AB" --session "$SESSION" get attr "$SEL" data-photo-bgremove-error 2>/dev/null | tr -d '"')"
|
|
232
|
+
echo "photo-bg-remove.sh: harness reported an error: ${ERR:-unknown}" >&2
|
|
233
|
+
exit 4
|
|
234
|
+
fi
|
|
235
|
+
|
|
236
|
+
if [ "$STATUS" != "done" ]; then
|
|
237
|
+
echo "photo-bg-remove.sh: timed out after ${TIMEOUT}s waiting for the ML pass (still '$STATUS')" >&2
|
|
238
|
+
exit 5
|
|
239
|
+
fi
|
|
240
|
+
|
|
241
|
+
RESULT="$("$AB" --session "$SESSION" get attr "$SEL" data-photo-bgremove-result 2>/dev/null | tr -d '"')"
|
|
242
|
+
[ -n "$RESULT" ] || { echo "photo-bg-remove.sh: harness reported done but no result asset" >&2; exit 4; }
|
|
243
|
+
# Shape-validate the deliverable (Task 3) before it's ever trusted as far as
|
|
244
|
+
# stdout — the harness runs inside the (untrusted, DDR-054) canvas origin, so
|
|
245
|
+
# a malformed/crafted `data-photo-bgremove-result` value is treated exactly
|
|
246
|
+
# like the empty-result case above rather than printed verbatim.
|
|
247
|
+
if [[ ! "$RESULT" =~ $ASSET_SHAPE_RE ]]; then
|
|
248
|
+
echo "photo-bg-remove.sh: harness reported a malformed result asset '$RESULT'" >&2
|
|
249
|
+
exit 4
|
|
250
|
+
fi
|
|
251
|
+
|
|
252
|
+
echo "→ background removed → $RESULT" >&2
|
|
253
|
+
# Last stdout line = the new matte asset path, for $(maude design photo-bg-remove ...) capture.
|
|
254
|
+
printf '%s\n' "$RESULT"
|
|
255
|
+
|
|
256
|
+
# Clean up the harness scratch file on the SUCCESS path only (Task 5) — left
|
|
257
|
+
# in place on any error exit above, where it's useful for debugging.
|
|
258
|
+
rm -f "$HARNESS_TSX"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# probe-footage.sh — thin shim over _probe-footage-playwright.mjs; reached via
|
|
3
|
+
# `maude design probe-footage` (DDR-062), never a raw bin path. Extracts keyframe
|
|
4
|
+
# PNGs from one clip in headless Chromium for the footage-analyst's vision pass
|
|
5
|
+
# (feature-footage-analysis-director, Task 4). See the .mjs for the rationale.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# probe-footage.sh <assets/<sha8>.<ext>> --root <repo> [--design-root .design]
|
|
9
|
+
# [--frames N] [--out-dir DIR]
|
|
10
|
+
#
|
|
11
|
+
# stdout on success = a JSON manifest { asset, durationSec, width, height, frames }.
|
|
12
|
+
# Exit: 0 ok · 2 usage · 3 no browser · 4 decode/probe error · 1 other.
|
|
13
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
14
|
+
|
|
15
|
+
case "$1" in
|
|
16
|
+
--help|-h) sed -n '2,14p' "$0" | sed 's/^# \?//'; exit 0 ;;
|
|
17
|
+
esac
|
|
18
|
+
|
|
19
|
+
# The probe imports `playwright` (Chromium). Prefer node (the runtime the other
|
|
20
|
+
# playwright shims run under); fall back to bun in a dev tree.
|
|
21
|
+
if command -v node >/dev/null 2>&1; then
|
|
22
|
+
exec node "$SCRIPT_DIR/_probe-footage-playwright.mjs" "$@"
|
|
23
|
+
elif command -v bun >/dev/null 2>&1; then
|
|
24
|
+
exec bun run "$SCRIPT_DIR/_probe-footage-playwright.mjs" "$@"
|
|
25
|
+
else
|
|
26
|
+
echo "probe-footage.sh: node or bun is required." >&2
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
@@ -105,8 +105,15 @@ Args:
|
|
|
105
105
|
--json No-op (JSON is the only output form).
|
|
106
106
|
|
|
107
107
|
FigJam v3 additive fields per annotation: z (render order), groupIds (deepest →
|
|
108
|
-
shallowest), author ("ai" for annotate-verb strokes),
|
|
109
|
-
bound arrows.
|
|
108
|
+
shallowest), author ("ai" for annotate-verb strokes), authorName/authorId
|
|
109
|
+
(human-drawn stickies), and from/to host ids on bound arrows.
|
|
110
|
+
|
|
111
|
+
Section annotations additionally carry members: an array of every OTHER
|
|
112
|
+
annotation whose center falls inside the section's rect, in SPATIAL reading
|
|
113
|
+
order (top-to-bottom, left-to-right — not paint order), each as
|
|
114
|
+
{ id, tool, order, x, y, w, h, href? }. Answers "what's in this section, and
|
|
115
|
+
in what order" for prompts like "make a video / Instagram carousel from
|
|
116
|
+
this section" without hand-computing containment.
|
|
110
117
|
|
|
111
118
|
Emits JSON to stdout. A missing annotation file emits [] (exit 0).`;
|
|
112
119
|
|
|
@@ -395,6 +402,31 @@ function readUnknown(tool, attrs, inner) {
|
|
|
395
402
|
// provenance, render order (z = document position). Applied uniformly after
|
|
396
403
|
// the per-tool extraction; arrows additionally surface bound endpoints as
|
|
397
404
|
// `from`/`to` host ids so a bound diagram reads back as a GRAPH.
|
|
405
|
+
const AUTHOR_NAME_MAX_LEN = 64;
|
|
406
|
+
|
|
407
|
+
// Phase 3 (whiteboard-improvements) — mirrors annotations-model.ts's
|
|
408
|
+
// sanitizeAuthorName (same code-point ranges: control chars, bidi
|
|
409
|
+
// overrides/isolates, zero-width chars, BOM) so a peer-synced
|
|
410
|
+
// `.annotations.svg`'s authorName/authorId reads identically through
|
|
411
|
+
// either surface. Every string this CLI returns is already treated as DATA
|
|
412
|
+
// by the whiteboard skill's trust model — this additionally strips the
|
|
413
|
+
// characters that could visually spoof an author name.
|
|
414
|
+
function sanitizeAuthorName(raw) {
|
|
415
|
+
let cleaned = '';
|
|
416
|
+
for (const ch of raw) {
|
|
417
|
+
const cp = ch.codePointAt(0) ?? 0;
|
|
418
|
+
const unsafe =
|
|
419
|
+
cp <= 0x1f ||
|
|
420
|
+
(cp >= 0x7f && cp <= 0x9f) ||
|
|
421
|
+
(cp >= 0x200b && cp <= 0x200f) ||
|
|
422
|
+
(cp >= 0x202a && cp <= 0x202e) ||
|
|
423
|
+
(cp >= 0x2066 && cp <= 0x2069) ||
|
|
424
|
+
cp === 0xfeff;
|
|
425
|
+
if (!unsafe) cleaned += ch;
|
|
426
|
+
}
|
|
427
|
+
return cleaned.trim().slice(0, AUTHOR_NAME_MAX_LEN);
|
|
428
|
+
}
|
|
429
|
+
|
|
398
430
|
function withShared(o, attrs, z) {
|
|
399
431
|
o.z = z;
|
|
400
432
|
const g = attr(attrs, 'data-group-ids');
|
|
@@ -403,6 +435,16 @@ function withShared(o, attrs, z) {
|
|
|
403
435
|
if (ids.length) o.groupIds = ids;
|
|
404
436
|
}
|
|
405
437
|
if (attr(attrs, 'data-author') === 'ai') o.author = 'ai';
|
|
438
|
+
const authorName = attr(attrs, 'data-author-name');
|
|
439
|
+
if (authorName) {
|
|
440
|
+
const cleaned = sanitizeAuthorName(decodeEntities(authorName));
|
|
441
|
+
if (cleaned) o.authorName = cleaned;
|
|
442
|
+
}
|
|
443
|
+
const authorId = attr(attrs, 'data-author-id');
|
|
444
|
+
if (authorId) {
|
|
445
|
+
const cleanedId = sanitizeAuthorName(decodeEntities(authorId));
|
|
446
|
+
if (cleanedId) o.authorId = cleanedId;
|
|
447
|
+
}
|
|
406
448
|
if (o.tool === 'arrow') {
|
|
407
449
|
const sb = parseBind(attr(attrs, 'data-start-bind'));
|
|
408
450
|
if (sb) o.from = sb.hostId;
|
|
@@ -615,6 +657,85 @@ function anchorToElement(ann, elements) {
|
|
|
615
657
|
};
|
|
616
658
|
}
|
|
617
659
|
|
|
660
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
661
|
+
// Section membership + reading order (Phase 5, feature-whiteboard-
|
|
662
|
+
// annotation-improvements). A common flow: the user drops several media/
|
|
663
|
+
// stickies INTO a section, then asks "make a video from this" / "turn this
|
|
664
|
+
// into an Instagram carousel" — the agent needs to know exactly which
|
|
665
|
+
// annotations live in a given section, and in what visual order, without
|
|
666
|
+
// hand-computing containment or guessing from raw x/y. Mirrors
|
|
667
|
+
// `findElement`'s smallest-containing-rect convention (annotation CENTER
|
|
668
|
+
// inside a rect), applied against SECTION rects instead of DOM element
|
|
669
|
+
// rects.
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Spatial reading order — top-to-bottom, then left-to-right — NOT `z`
|
|
673
|
+
* (paint/document order): a user can drop stickies in any order they like,
|
|
674
|
+
* and assembling a carousel/video in PAINT order would visibly scramble it.
|
|
675
|
+
* Rows are bucketed by a Y-tolerance band (half the smallest member's
|
|
676
|
+
* height, floored at 16 units) so items a few units off from each other
|
|
677
|
+
* still read as "the same row"; `z` only breaks a true tie (same band, same
|
|
678
|
+
* x) between two members.
|
|
679
|
+
*/
|
|
680
|
+
function attachSectionMembers(annotations) {
|
|
681
|
+
const sections = annotations.filter((a) => a.tool === 'section' && a.id);
|
|
682
|
+
if (!sections.length) return annotations;
|
|
683
|
+
|
|
684
|
+
const centerOf = (a) => {
|
|
685
|
+
if (a.x == null || a.y == null) return null;
|
|
686
|
+
return { cx: a.x + (a.w || 0) / 2, cy: a.y + (a.h || 0) / 2 };
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
const membersBySection = new Map();
|
|
690
|
+
for (const section of sections) {
|
|
691
|
+
const sx1 = section.x;
|
|
692
|
+
const sy1 = section.y;
|
|
693
|
+
const sx2 = section.x + (section.w || 0);
|
|
694
|
+
const sy2 = section.y + (section.h || 0);
|
|
695
|
+
const candidates = [];
|
|
696
|
+
for (const a of annotations) {
|
|
697
|
+
if (a.tool === 'section' || !a.id) continue;
|
|
698
|
+
const c = centerOf(a);
|
|
699
|
+
if (!c) continue;
|
|
700
|
+
if (c.cx < sx1 || c.cx > sx2 || c.cy < sy1 || c.cy > sy2) continue;
|
|
701
|
+
candidates.push({ ann: a, c });
|
|
702
|
+
}
|
|
703
|
+
if (!candidates.length) {
|
|
704
|
+
membersBySection.set(section.id, []);
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
const heights = candidates.map((m) => m.ann.h || 0).filter((h) => h > 0);
|
|
708
|
+
const minHeight = heights.length ? Math.min(...heights) : 40;
|
|
709
|
+
const band = Math.max(16, minHeight / 2);
|
|
710
|
+
candidates.sort((m1, m2) => {
|
|
711
|
+
const dy = m1.c.cy - m2.c.cy;
|
|
712
|
+
if (Math.abs(dy) > band) return dy;
|
|
713
|
+
const dx = m1.c.cx - m2.c.cx;
|
|
714
|
+
if (dx !== 0) return dx;
|
|
715
|
+
return (m1.ann.z ?? 0) - (m2.ann.z ?? 0);
|
|
716
|
+
});
|
|
717
|
+
membersBySection.set(
|
|
718
|
+
section.id,
|
|
719
|
+
candidates.map((m, i) => ({
|
|
720
|
+
id: m.ann.id,
|
|
721
|
+
tool: m.ann.tool,
|
|
722
|
+
order: i,
|
|
723
|
+
x: m.ann.x,
|
|
724
|
+
y: m.ann.y,
|
|
725
|
+
w: m.ann.w,
|
|
726
|
+
h: m.ann.h,
|
|
727
|
+
...(m.ann.href ? { href: m.ann.href } : null),
|
|
728
|
+
}))
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
return annotations.map((a) =>
|
|
733
|
+
a.tool === 'section' && membersBySection.has(a.id)
|
|
734
|
+
? { ...a, members: membersBySection.get(a.id) }
|
|
735
|
+
: a
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
|
|
618
739
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
619
740
|
// Main
|
|
620
741
|
|
|
@@ -674,6 +795,7 @@ function main() {
|
|
|
674
795
|
if (artboards.length) {
|
|
675
796
|
annotations = annotations.map((a) => anchorToArtboard(a, artboards));
|
|
676
797
|
}
|
|
798
|
+
annotations = attachSectionMembers(annotations);
|
|
677
799
|
|
|
678
800
|
if (args.graph) {
|
|
679
801
|
process.stdout.write(`${JSON.stringify({ annotations, graph: buildGraph(annotations) })}\n`);
|
|
@@ -689,6 +811,7 @@ function main() {
|
|
|
689
811
|
// canvas-rects manifest without a second implementation.
|
|
690
812
|
export {
|
|
691
813
|
anchorToElement,
|
|
814
|
+
attachSectionMembers,
|
|
692
815
|
buildGraph,
|
|
693
816
|
fileSlug,
|
|
694
817
|
findElement,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# transcribe.sh — feature-ai-media-generation (Phase 2, DDR-164). Local, no-key
|
|
3
|
+
# speech-to-text for automatic subtitles: run whisper.cpp on an audio/video file
|
|
4
|
+
# and emit SRT/VTT through the shared captions.ts reflow. The DEFAULT subtitle
|
|
5
|
+
# path — free, offline, private, word-timestamped — with cloud STT (ElevenLabs
|
|
6
|
+
# Scribe / Groq Whisper) as the fallback when whisper.cpp isn't installed.
|
|
7
|
+
#
|
|
8
|
+
# Reached via `maude design transcribe` (never a raw bin path — DDR-062). No dev
|
|
9
|
+
# server needed; the work is a local whisper.cpp spawn (mirrors probe-footage).
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# transcribe.sh --source <assets/x.mp4|file> [--root <repo>]
|
|
13
|
+
# [--provider whisper|elevenlabs|groq]
|
|
14
|
+
# [--format srt|vtt|both] [--model <id>] [--whisper <bin>]
|
|
15
|
+
# [--lang <code>] [--segments]
|
|
16
|
+
#
|
|
17
|
+
# The engine is an EXPLICIT user choice (Task 2.6, DDR-164), never an automatic
|
|
18
|
+
# fallback: --provider wins, else the project's generation.transcription.provider
|
|
19
|
+
# config, else local whisper — and it SAYS which it chose. A chosen cloud engine
|
|
20
|
+
# (elevenlabs / groq) needs the dev server running (the key is resolved
|
|
21
|
+
# server-side) and an assets/<sha8> source; whisper is local, free, no key.
|
|
22
|
+
#
|
|
23
|
+
# whisper.cpp is a SOFT dependency (plugins/design/dependencies.json). A ggml
|
|
24
|
+
# model is required (--model / $MAUDE_WHISPER_MODEL); whisper.cpp ships no default
|
|
25
|
+
# and does not auto-download. See _transcribe.mjs for the download pointer.
|
|
26
|
+
#
|
|
27
|
+
# Stdout (last line): the produced .srt path (for $(...) capture).
|
|
28
|
+
# Exit: 0 ok / 1 dependency-or-input problem / 3 transcription failed.
|
|
29
|
+
|
|
30
|
+
set -euo pipefail
|
|
31
|
+
|
|
32
|
+
if ! command -v bun >/dev/null 2>&1; then
|
|
33
|
+
echo "transcribe.sh: bun is required (hard dependency — see plugins/design/dependencies.json)." >&2
|
|
34
|
+
echo " Install: curl -fsSL https://bun.sh/install | bash" >&2
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
39
|
+
exec bun run "$SCRIPT_DIR/_transcribe.mjs" "$@"
|