@1agh/maude 0.43.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/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 +396 -46
- package/apps/studio/api.ts +395 -2
- 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/_transcribe.mjs +419 -0
- package/apps/studio/bin/_transcribe.test.mjs +80 -0
- 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/photo-bg-remove.sh +119 -21
- package/apps/studio/bin/transcribe.sh +39 -0
- package/apps/studio/canvas-edit.ts +197 -2
- package/apps/studio/canvas-lib.tsx +115 -6
- package/apps/studio/client/app.jsx +1954 -685
- package/apps/studio/client/generate-dialog.jsx +352 -0
- package/apps/studio/client/github.js +14 -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/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/SettingsPanel.jsx +828 -0
- package/apps/studio/client/panels/SetupChecklist.jsx +223 -0
- package/apps/studio/client/photo-knobs.jsx +167 -172
- package/apps/studio/client/styles/3-shell-maude.css +334 -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/config.schema.json +70 -0
- package/apps/studio/context-menu.tsx +11 -2
- package/apps/studio/context.ts +16 -0
- package/apps/studio/design-setup-readiness.ts +115 -0
- package/apps/studio/dist/client.bundle.js +2097 -2088
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/draw/palette.ts +34 -0
- package/apps/studio/footage/schema.test.ts +92 -0
- package/apps/studio/footage/schema.ts +179 -1
- 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/service.ts +3 -1
- package/apps/studio/http.ts +867 -9
- package/apps/studio/media/intro.mp4 +0 -0
- package/apps/studio/media-commit-chain.ts +119 -0
- package/apps/studio/paths.ts +10 -0
- package/apps/studio/readiness.ts +108 -20
- package/apps/studio/scaffold-design.ts +1145 -0
- package/apps/studio/server.ts +13 -1
- package/apps/studio/test/_helpers.ts +15 -2
- package/apps/studio/test/acp-bridge.test.ts +34 -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/canvas-origin-gate.test.ts +37 -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/element-structural-edit.test.ts +108 -0
- package/apps/studio/test/fixtures/fake-claude-auth.mjs +13 -0
- package/apps/studio/test/generate-route.test.ts +106 -0
- 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/media-commit-chain.test.ts +210 -0
- package/apps/studio/test/photo-bg-remove-validation.test.ts +71 -0
- package/apps/studio/test/readiness.test.ts +13 -11
- package/apps/studio/test/scaffold-design.test.ts +122 -0
- package/apps/studio/test/tour-overlay.test.tsx +16 -0
- package/apps/studio/tool-palette.tsx +11 -6
- package/apps/studio/ui-prefs.ts +130 -0
- package/apps/studio/use-canvas-media-drop.tsx +4 -2
- package/apps/studio/use-chrome-visibility.tsx +8 -2
- package/apps/studio/whats-new.json +96 -0
- package/apps/studio/ws.ts +5 -0
- package/cli/bin/maude.mjs +6 -6
- package/cli/commands/design.mjs +33 -0
- package/cli/lib/gitignore-block.mjs +1 -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 +12 -10
- package/plugins/design/dependencies.json +18 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# import-asset.sh — hardened local-file SVG/PDF ingestion. Thin shim over
|
|
3
|
+
# _import-asset.mjs; reached via `maude design import-asset` (DDR-062), never
|
|
4
|
+
# a raw bin path. See _import-asset.mjs + DDR-167 for the full security
|
|
5
|
+
# rationale. Requires bun (not the node/bun fallback fetch-asset.sh uses) —
|
|
6
|
+
# _import-asset.mjs imports the `.ts` SVGO engine module directly, same
|
|
7
|
+
# constraint as svg-optimize.sh.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# import-asset.sh <local-path> --root <repo> [--design-root .design]
|
|
11
|
+
# [--kind svg|pdf|raster] [--json]
|
|
12
|
+
#
|
|
13
|
+
# stdout on success = one reference path per line (e.g. /assets/a44d3d60.svg).
|
|
14
|
+
# PDF import is not yet available (DDR-167 addendum) — the verb accepts a
|
|
15
|
+
# .pdf input and fails loud naming why, rather than silently rejecting it as
|
|
16
|
+
# an unknown flag. `raster` (PNG/JPEG, magic-byte sniffed) content-addresses
|
|
17
|
+
# a local image as-is — added for DDR-174/T15's vision-reconstruction source.
|
|
18
|
+
#
|
|
19
|
+
# Exit: 0 ok · 1 not-yet-available/other · 2 usage · 3 sanitize/validation
|
|
20
|
+
# reject · 4 read/parse error · 5 unsupported media type ·
|
|
21
|
+
# 6 write/containment error.
|
|
22
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
23
|
+
|
|
24
|
+
case "$1" in
|
|
25
|
+
--help|-h) sed -n '2,17p' "$0" | sed 's/^# \?//'; exit 0 ;;
|
|
26
|
+
esac
|
|
27
|
+
|
|
28
|
+
if ! command -v bun >/dev/null 2>&1; then
|
|
29
|
+
echo "import-asset.sh: bun is required (hard dependency — see plugins/design/dependencies.json)." >&2
|
|
30
|
+
echo " Install: curl -fsSL https://bun.sh/install | bash" >&2
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
exec bun run "$SCRIPT_DIR/_import-asset.mjs" "$@"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# import-brand.sh — brand-file typed-cue extraction (DDR-173). Thin shim over
|
|
3
|
+
# _import-brand.mjs; reached via `maude design import-brand` (DDR-062), never
|
|
4
|
+
# a raw bin path. Operates ONLY on an already-DDR-167-sanitized SVG asset
|
|
5
|
+
# (as produced by `maude design import-asset`) — never re-reads or
|
|
6
|
+
# re-sanitizes the original brand file (DDR-173 Decision 2: no parallel,
|
|
7
|
+
# ungated read path). Requires bun — _import-brand.mjs imports
|
|
8
|
+
# _import-tokens.mjs's `.mjs` grammar reuse and happy-dom directly.
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# import-brand.sh <sanitized-svg-path> --root <repo> [--design-root .design] [--json]
|
|
12
|
+
#
|
|
13
|
+
# stdout on success = the typed payload `{ palette, fonts, logoRef, logoRasterRef }`.
|
|
14
|
+
#
|
|
15
|
+
# Exit: 0 ok · 1 other · 2 usage · 3 validation/hardening reject ·
|
|
16
|
+
# 4 read/parse error · 6 write/containment error.
|
|
17
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
18
|
+
|
|
19
|
+
case "$1" in
|
|
20
|
+
--help|-h) sed -n '2,15p' "$0" | sed 's/^# \?//'; exit 0 ;;
|
|
21
|
+
esac
|
|
22
|
+
|
|
23
|
+
if ! command -v bun >/dev/null 2>&1; then
|
|
24
|
+
echo "import-brand.sh: bun is required (hard dependency — see plugins/design/dependencies.json)." >&2
|
|
25
|
+
echo " Install: curl -fsSL https://bun.sh/install | bash" >&2
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
exec bun run "$SCRIPT_DIR/_import-brand.mjs" "$@"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# import-tokens.sh — token-file import (W3C design-tokens / Style-Dictionary
|
|
3
|
+
# JSON / raw CSS custom properties -> the DS CSS-variable contract). Thin
|
|
4
|
+
# shim over _import-tokens.mjs; reached via `maude design import-tokens`
|
|
5
|
+
# (DDR-062), never a raw bin path. See _import-tokens.mjs + DDR-172 for the
|
|
6
|
+
# full mapping contract + security rationale. Requires bun — _import-tokens.mjs
|
|
7
|
+
# imports the `.ts` palette/paths modules directly, same constraint as
|
|
8
|
+
# import-asset.sh.
|
|
9
|
+
#
|
|
10
|
+
# All args are forwarded quoted ("$@") — never unquoted or eval-expanded
|
|
11
|
+
# (DDR-172 Decision 8 wrapper-hygiene requirement; the charset/shape
|
|
12
|
+
# validation itself happens in _import-tokens.mjs, not here).
|
|
13
|
+
#
|
|
14
|
+
# Usage:
|
|
15
|
+
# import-tokens.sh <token-file> --root <repo> [--design-root .design]
|
|
16
|
+
# [--ds <name> | --new-ds <name>] [--theme <name>]
|
|
17
|
+
# [--force-insert] [--json]
|
|
18
|
+
#
|
|
19
|
+
# Exit: 0 ok · 1 other · 2 usage · 3 validation/mapping reject ·
|
|
20
|
+
# 4 read/parse error · 5 unsupported format · 6 write/containment error.
|
|
21
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
22
|
+
|
|
23
|
+
case "$1" in
|
|
24
|
+
--help|-h) sed -n '2,16p' "$0" | sed 's/^# \?//'; exit 0 ;;
|
|
25
|
+
esac
|
|
26
|
+
|
|
27
|
+
if ! command -v bun >/dev/null 2>&1; then
|
|
28
|
+
echo "import-tokens.sh: bun is required (hard dependency — see plugins/design/dependencies.json)." >&2
|
|
29
|
+
echo " Install: curl -fsSL https://bun.sh/install | bash" >&2
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
exec bun run "$SCRIPT_DIR/_import-tokens.mjs" "$@"
|
|
@@ -16,6 +16,19 @@
|
|
|
16
16
|
#
|
|
17
17
|
# Reached via `maude design photo-bg-remove` (never a raw bin path — DDR-062).
|
|
18
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
|
+
#
|
|
19
32
|
# Usage:
|
|
20
33
|
# photo-bg-remove.sh --asset <assets/<sha8>.<ext>> [--root <repo>]
|
|
21
34
|
# [--slug <name>] [--timeout <secs>]
|
|
@@ -29,11 +42,16 @@
|
|
|
29
42
|
# Stderr: progress / diagnostics.
|
|
30
43
|
# Exit: 0 success / 1 missing input, server, or agent-browser / 2 bad args
|
|
31
44
|
# / 3 drive/navigation failed / 4 the harness itself reported an error
|
|
32
|
-
# / 5 timed out waiting for the ML
|
|
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
|
|
33
47
|
|
|
34
48
|
set -uo pipefail
|
|
35
49
|
|
|
36
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]+$'
|
|
37
55
|
|
|
38
56
|
while [ $# -gt 0 ]; do
|
|
39
57
|
case "$1" in
|
|
@@ -42,7 +60,7 @@ while [ $# -gt 0 ]; do
|
|
|
42
60
|
--root) REPO="$2"; shift 2 ;;
|
|
43
61
|
--timeout) TIMEOUT="$2"; shift 2 ;;
|
|
44
62
|
--help|-h)
|
|
45
|
-
sed -n '2,
|
|
63
|
+
sed -n '2,39p' "$0" | sed 's/^# \?//'
|
|
46
64
|
exit 0
|
|
47
65
|
;;
|
|
48
66
|
*)
|
|
@@ -55,14 +73,42 @@ done
|
|
|
55
73
|
[ -n "$ASSET" ] || { echo "photo-bg-remove.sh: --asset <assets/<sha8>.<ext>> required" >&2; exit 2; }
|
|
56
74
|
# Strip a leading designRoot-relative slash/prefix if the caller passed one.
|
|
57
75
|
ASSET="${ASSET#/}"
|
|
58
|
-
case "$ASSET" in
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
62
90
|
|
|
63
91
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
64
92
|
[ -n "$SLUG" ] || SLUG=$(basename "$ASSET" | sed -E 's/\.[^.]+$//')
|
|
65
|
-
|
|
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-$$"
|
|
66
112
|
|
|
67
113
|
# ---------- resolve repo + design root + port ----------
|
|
68
114
|
if [ -z "$REPO" ]; then
|
|
@@ -97,29 +143,69 @@ if [ -z "${AGENT_BROWSER_EXECUTABLE_PATH:-}" ]; then
|
|
|
97
143
|
[ -n "$BROWSER_PATH" ] && export AGENT_BROWSER_EXECUTABLE_PATH="$BROWSER_PATH"
|
|
98
144
|
fi
|
|
99
145
|
|
|
100
|
-
# ----------
|
|
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.
|
|
101
154
|
PHOTO_DIR="$DESIGN_ROOT/_photo"
|
|
102
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).
|
|
103
181
|
HARNESS_TSX="$PHOTO_DIR/$SLUG.bgremove.tsx"
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
|
113
203
|
echo "→ harness canvas: ${HARNESS_TSX#$REPO/}" >&2
|
|
114
204
|
|
|
115
205
|
REL="_photo/$SLUG.bgremove.tsx"
|
|
116
206
|
URL="http://localhost:$PORT/_canvas-shell.html?canvas=$REL"
|
|
117
|
-
SESSION="photo-bgremove-$SLUG-$$"
|
|
118
207
|
SEL="[data-photo-bgremove-status]"
|
|
119
208
|
|
|
120
|
-
cleanup() { "$AB" --session "$SESSION" close >/dev/null 2>&1 || true; }
|
|
121
|
-
trap cleanup EXIT
|
|
122
|
-
|
|
123
209
|
echo "→ driving $URL (session $SESSION)" >&2
|
|
124
210
|
if ! "$AB" --session "$SESSION" open "$URL" >&2; then
|
|
125
211
|
echo "photo-bg-remove.sh: agent-browser failed to open the harness canvas" >&2
|
|
@@ -154,7 +240,19 @@ fi
|
|
|
154
240
|
|
|
155
241
|
RESULT="$("$AB" --session "$SESSION" get attr "$SEL" data-photo-bgremove-result 2>/dev/null | tr -d '"')"
|
|
156
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
|
|
157
251
|
|
|
158
252
|
echo "→ background removed → $RESULT" >&2
|
|
159
253
|
# Last stdout line = the new matte asset path, for $(maude design photo-bg-remove ...) capture.
|
|
160
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,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" "$@"
|
|
@@ -3406,8 +3406,14 @@ function collectJsxByTag(program: AnyNode, tagName: string): AnyNode[] {
|
|
|
3406
3406
|
* `width="430"` and break the numeric prop. Overwrites an existing `{expr}` or
|
|
3407
3407
|
* `"literal"`, or inserts the attribute after the tag name if missing.
|
|
3408
3408
|
*/
|
|
3409
|
-
function writeNumericAttr(
|
|
3410
|
-
|
|
3409
|
+
function writeNumericAttr(
|
|
3410
|
+
s: MagicString,
|
|
3411
|
+
opening: AnyNode,
|
|
3412
|
+
name: string,
|
|
3413
|
+
value: number,
|
|
3414
|
+
min = 1
|
|
3415
|
+
): void {
|
|
3416
|
+
const num = Math.max(min, Math.round(value));
|
|
3411
3417
|
const attr = findAttribute(opening, name);
|
|
3412
3418
|
if (attr) {
|
|
3413
3419
|
const v = attr.value;
|
|
@@ -3494,6 +3500,195 @@ export async function resizeArtboard(
|
|
|
3494
3500
|
});
|
|
3495
3501
|
}
|
|
3496
3502
|
|
|
3503
|
+
/**
|
|
3504
|
+
* Write/clear a BARE JSX boolean attribute (`<DCArtboard fixed>`, not
|
|
3505
|
+
* `fixed={true}` or a string). `value=false` reuses `removeStringAttr`'s
|
|
3506
|
+
* generic span-plus-leading-whitespace removal (it doesn't care whether the
|
|
3507
|
+
* attribute it's removing has a value) — only the `true` insert path is new.
|
|
3508
|
+
*/
|
|
3509
|
+
function writeBooleanAttr(
|
|
3510
|
+
s: MagicString,
|
|
3511
|
+
opening: AnyNode,
|
|
3512
|
+
name: string,
|
|
3513
|
+
value: boolean,
|
|
3514
|
+
source: string
|
|
3515
|
+
): void {
|
|
3516
|
+
if (!value) {
|
|
3517
|
+
removeStringAttr(s, opening, name, source);
|
|
3518
|
+
return;
|
|
3519
|
+
}
|
|
3520
|
+
if (findAttribute(opening, name)) return; // already present in some form
|
|
3521
|
+
const insertAt: number | undefined = opening?.name?.end;
|
|
3522
|
+
if (typeof insertAt !== 'number') throw new Error('Opening element has no resolvable name range');
|
|
3523
|
+
s.appendLeft(insertAt, ` ${name}`);
|
|
3524
|
+
}
|
|
3525
|
+
|
|
3526
|
+
/**
|
|
3527
|
+
* Toggle an artboard's height sizing mode (Hug ⇄ Fixed CSS-panel control).
|
|
3528
|
+
* `fixed=true` adds the bare `fixed` prop and, when `freezeHeight` is given
|
|
3529
|
+
* (the board's current measured height), also writes it as the exact
|
|
3530
|
+
* `height` — so pinning to Fixed doesn't snap the box back to whatever the
|
|
3531
|
+
* JSX `height` floor happened to be. `fixed=false` removes the `fixed` prop;
|
|
3532
|
+
* `height` is left as-is (it resumes being the hug floor). Same id-prop
|
|
3533
|
+
* addressing as applyResizeArtboard (DDR-027 — an artboard frame carries no
|
|
3534
|
+
* data-cd-id). Pure; reparse-gated.
|
|
3535
|
+
*/
|
|
3536
|
+
export function applySetArtboardHug(
|
|
3537
|
+
canvasAbsPath: string,
|
|
3538
|
+
source: string,
|
|
3539
|
+
artboardId: string,
|
|
3540
|
+
fixed: boolean,
|
|
3541
|
+
freezeHeight?: number
|
|
3542
|
+
): { source: string } {
|
|
3543
|
+
const parsed = parseSync(canvasAbsPath, source, { sourceType: 'module' });
|
|
3544
|
+
if (parsed.errors && parsed.errors.length > 0) {
|
|
3545
|
+
throw new CanvasEditError(
|
|
3546
|
+
`oxc-parser failed on ${canvasAbsPath}: ${parsed.errors[0]?.message ?? 'unknown'}`,
|
|
3547
|
+
{ canvas: canvasAbsPath, id: artboardId }
|
|
3548
|
+
);
|
|
3549
|
+
}
|
|
3550
|
+
const artboards = collectJsxByTag(parsed.program, 'DCArtboard');
|
|
3551
|
+
const target = artboards.find((a) => getStringAttr(a.openingElement, 'id') === artboardId);
|
|
3552
|
+
if (!target) {
|
|
3553
|
+
throw new CanvasEditError(`<DCArtboard id="${artboardId}"> not found in ${canvasAbsPath}`, {
|
|
3554
|
+
canvas: canvasAbsPath,
|
|
3555
|
+
id: artboardId,
|
|
3556
|
+
});
|
|
3557
|
+
}
|
|
3558
|
+
const s = new MagicString(source);
|
|
3559
|
+
writeBooleanAttr(s, target.openingElement, 'fixed', fixed, source);
|
|
3560
|
+
if (fixed && typeof freezeHeight === 'number') {
|
|
3561
|
+
writeNumericAttr(s, target.openingElement, 'height', freezeHeight);
|
|
3562
|
+
}
|
|
3563
|
+
const out = s.toString();
|
|
3564
|
+
const check = parseSync(canvasAbsPath, out, { sourceType: 'module' });
|
|
3565
|
+
if (check.errors && check.errors.length > 0) {
|
|
3566
|
+
throw new CanvasEditError(
|
|
3567
|
+
`artboard hug-toggle produced invalid source (${check.errors[0]?.message ?? 'parse error'})`,
|
|
3568
|
+
{ canvas: canvasAbsPath, id: artboardId }
|
|
3569
|
+
);
|
|
3570
|
+
}
|
|
3571
|
+
return { source: out };
|
|
3572
|
+
}
|
|
3573
|
+
|
|
3574
|
+
/** Set an artboard's Hug/Fixed mode on disk (atomic write + cross-process lock). */
|
|
3575
|
+
export async function setArtboardHug(
|
|
3576
|
+
canvasAbsPath: string,
|
|
3577
|
+
artboardId: string,
|
|
3578
|
+
fixed: boolean,
|
|
3579
|
+
freezeHeight?: number
|
|
3580
|
+
): Promise<{ source: string }> {
|
|
3581
|
+
return withLock(canvasAbsPath, async () => {
|
|
3582
|
+
const file = Bun.file(canvasAbsPath);
|
|
3583
|
+
if (!(await file.exists())) {
|
|
3584
|
+
throw new CanvasEditError(`Canvas not found: ${canvasAbsPath}`, {
|
|
3585
|
+
canvas: canvasAbsPath,
|
|
3586
|
+
id: artboardId,
|
|
3587
|
+
});
|
|
3588
|
+
}
|
|
3589
|
+
const source = await file.text();
|
|
3590
|
+
const next = applySetArtboardHug(canvasAbsPath, source, artboardId, fixed, freezeHeight);
|
|
3591
|
+
if (next.source === source) return next;
|
|
3592
|
+
const tmp = `${canvasAbsPath}.tmp.${Math.random().toString(36).slice(2, 10)}`;
|
|
3593
|
+
await Bun.write(tmp, next.source);
|
|
3594
|
+
const { rename } = await import('node:fs/promises');
|
|
3595
|
+
await rename(tmp, canvasAbsPath);
|
|
3596
|
+
return next;
|
|
3597
|
+
});
|
|
3598
|
+
}
|
|
3599
|
+
|
|
3600
|
+
/** Patch shape for {@link applySetArtboardStyle} — `null` resets/removes that
|
|
3601
|
+
* prop back to the engine default; `undefined`/absent leaves it untouched. */
|
|
3602
|
+
export interface ArtboardStylePatch {
|
|
3603
|
+
background?: string | null;
|
|
3604
|
+
padding?: number | null;
|
|
3605
|
+
layout?: string | null;
|
|
3606
|
+
gap?: number | null;
|
|
3607
|
+
}
|
|
3608
|
+
|
|
3609
|
+
/**
|
|
3610
|
+
* Write the artboard "more settings" props (background / padding / layout /
|
|
3611
|
+
* gap) — applied to `.dc-artboard-body` by DCArtboard, not the frame chrome.
|
|
3612
|
+
* String props (background/layout) reuse `editStringAttr`/`removeStringAttr`;
|
|
3613
|
+
* numeric props (padding/gap) reuse `writeNumericAttr` with a 0 floor (unlike
|
|
3614
|
+
* width/height, 0 padding/gap is a normal value, not a degenerate one). Same
|
|
3615
|
+
* id-prop addressing + reparse-gate as applyResizeArtboard/applySetArtboardHug.
|
|
3616
|
+
*/
|
|
3617
|
+
export function applySetArtboardStyle(
|
|
3618
|
+
canvasAbsPath: string,
|
|
3619
|
+
source: string,
|
|
3620
|
+
artboardId: string,
|
|
3621
|
+
patch: ArtboardStylePatch
|
|
3622
|
+
): { source: string } {
|
|
3623
|
+
const parsed = parseSync(canvasAbsPath, source, { sourceType: 'module' });
|
|
3624
|
+
if (parsed.errors && parsed.errors.length > 0) {
|
|
3625
|
+
throw new CanvasEditError(
|
|
3626
|
+
`oxc-parser failed on ${canvasAbsPath}: ${parsed.errors[0]?.message ?? 'unknown'}`,
|
|
3627
|
+
{ canvas: canvasAbsPath, id: artboardId }
|
|
3628
|
+
);
|
|
3629
|
+
}
|
|
3630
|
+
const artboards = collectJsxByTag(parsed.program, 'DCArtboard');
|
|
3631
|
+
const target = artboards.find((a) => getStringAttr(a.openingElement, 'id') === artboardId);
|
|
3632
|
+
if (!target) {
|
|
3633
|
+
throw new CanvasEditError(`<DCArtboard id="${artboardId}"> not found in ${canvasAbsPath}`, {
|
|
3634
|
+
canvas: canvasAbsPath,
|
|
3635
|
+
id: artboardId,
|
|
3636
|
+
});
|
|
3637
|
+
}
|
|
3638
|
+
const s = new MagicString(source);
|
|
3639
|
+
const opening = target.openingElement;
|
|
3640
|
+
if ('background' in patch) {
|
|
3641
|
+
if (patch.background == null) removeStringAttr(s, opening, 'background', source);
|
|
3642
|
+
else editStringAttr(s, opening, 'background', patch.background, canvasAbsPath, artboardId);
|
|
3643
|
+
}
|
|
3644
|
+
if ('layout' in patch) {
|
|
3645
|
+
if (patch.layout == null) removeStringAttr(s, opening, 'layout', source);
|
|
3646
|
+
else editStringAttr(s, opening, 'layout', patch.layout, canvasAbsPath, artboardId);
|
|
3647
|
+
}
|
|
3648
|
+
if ('padding' in patch) {
|
|
3649
|
+
if (patch.padding == null) removeStringAttr(s, opening, 'padding', source);
|
|
3650
|
+
else writeNumericAttr(s, opening, 'padding', patch.padding, 0);
|
|
3651
|
+
}
|
|
3652
|
+
if ('gap' in patch) {
|
|
3653
|
+
if (patch.gap == null) removeStringAttr(s, opening, 'gap', source);
|
|
3654
|
+
else writeNumericAttr(s, opening, 'gap', patch.gap, 0);
|
|
3655
|
+
}
|
|
3656
|
+
const out = s.toString();
|
|
3657
|
+
const check = parseSync(canvasAbsPath, out, { sourceType: 'module' });
|
|
3658
|
+
if (check.errors && check.errors.length > 0) {
|
|
3659
|
+
throw new CanvasEditError(
|
|
3660
|
+
`artboard style edit produced invalid source (${check.errors[0]?.message ?? 'parse error'})`,
|
|
3661
|
+
{ canvas: canvasAbsPath, id: artboardId }
|
|
3662
|
+
);
|
|
3663
|
+
}
|
|
3664
|
+
return { source: out };
|
|
3665
|
+
}
|
|
3666
|
+
|
|
3667
|
+
/** Set an artboard's style props on disk (atomic write + cross-process lock). */
|
|
3668
|
+
export async function setArtboardStyle(
|
|
3669
|
+
canvasAbsPath: string,
|
|
3670
|
+
artboardId: string,
|
|
3671
|
+
patch: ArtboardStylePatch
|
|
3672
|
+
): Promise<{ source: string }> {
|
|
3673
|
+
return withLock(canvasAbsPath, async () => {
|
|
3674
|
+
const file = Bun.file(canvasAbsPath);
|
|
3675
|
+
if (!(await file.exists())) {
|
|
3676
|
+
throw new CanvasEditError(`Canvas not found: ${canvasAbsPath}`, {
|
|
3677
|
+
canvas: canvasAbsPath,
|
|
3678
|
+
id: artboardId,
|
|
3679
|
+
});
|
|
3680
|
+
}
|
|
3681
|
+
const source = await file.text();
|
|
3682
|
+
const next = applySetArtboardStyle(canvasAbsPath, source, artboardId, patch);
|
|
3683
|
+
if (next.source === source) return next;
|
|
3684
|
+
const tmp = `${canvasAbsPath}.tmp.${Math.random().toString(36).slice(2, 10)}`;
|
|
3685
|
+
await Bun.write(tmp, next.source);
|
|
3686
|
+
const { rename } = await import('node:fs/promises');
|
|
3687
|
+
await rename(tmp, canvasAbsPath);
|
|
3688
|
+
return next;
|
|
3689
|
+
});
|
|
3690
|
+
}
|
|
3691
|
+
|
|
3497
3692
|
/**
|
|
3498
3693
|
* Delete the `<DCArtboard id="…">` whose `id` prop equals `artboardId` — the
|
|
3499
3694
|
* artboard counterpart of applyDeleteElement (an artboard is addressed by its id
|