@event4u/agent-config 2.21.0 → 2.24.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/.agent-src/commands/video/from-script.md +123 -0
- package/.agent-src/commands/video/scene.md +92 -0
- package/.agent-src/commands/video/stitch.md +83 -0
- package/.agent-src/commands/video/storyboard.md +95 -0
- package/.agent-src/commands/video.md +59 -0
- package/.agent-src/personas/README.md +3 -0
- package/.agent-src/personas/ai-video-technical-director.md +81 -0
- package/.agent-src/personas/hollywood-director.md +99 -0
- package/.agent-src/personas/pixar-storyboard-artist.md +98 -0
- package/.agent-src/skills/adversarial-review/SKILL.md +2 -1
- package/.agent-src/skills/canvas-design/SKILL.md +11 -6
- package/.agent-src/skills/character-consistency/SKILL.md +120 -0
- package/.agent-src/skills/fe-design/SKILL.md +8 -0
- package/.agent-src/skills/motion-choreographer/SKILL.md +149 -0
- package/.agent-src/skills/pixar-storyteller/SKILL.md +107 -0
- package/.agent-src/skills/prompt-optimizer/SKILL.md +29 -5
- package/.agent-src/skills/react-shadcn-ui/SKILL.md +9 -0
- package/.agent-src/skills/refine-prompt/SKILL.md +57 -0
- package/.agent-src/skills/scene-expander/SKILL.md +122 -0
- package/.agent-src/skills/scene-expander/scene-blueprint.schema.yaml +108 -0
- package/.agent-src/skills/subagent-orchestration/SKILL.md +17 -15
- package/.agent-src/skills/tailwind-engineer/SKILL.md +14 -0
- package/.agent-src/skills/video-director/SKILL.md +113 -0
- package/.agent-src/templates/agent-settings.md +19 -0
- package/.agent-src/templates/agents/agent-project-settings.example.yml +53 -1
- package/.claude-plugin/marketplace.json +11 -1
- package/CHANGELOG.md +88 -138
- package/README.md +4 -4
- package/config/agent-settings.template.yml +28 -0
- package/docs/adrs/caveman/0001-default-off-until-bench.md +2 -2
- package/docs/adrs/cost/0001-hard-stop-hook.md +1 -1
- package/docs/adrs/smoke/0001-per-tier-smoke-scripts.md +2 -2
- package/docs/architecture.md +3 -3
- package/docs/archive/CHANGELOG-pre-2.20.0.md +159 -0
- package/docs/catalog.md +16 -5
- package/docs/contracts/command-clusters.md +1 -0
- package/docs/contracts/compression-default-kill-criterion.md +1 -1
- package/docs/contracts/file-ownership-matrix.json +344 -0
- package/docs/getting-started.md +1 -1
- package/docs/guidelines/prompt-templates.md +166 -0
- package/docs/parity/ruflo.md +3 -3
- package/package.json +1 -1
- package/scripts/ai-video/adapters/gemini-veo.sh +57 -0
- package/scripts/ai-video/adapters/higgsfield.sh +82 -0
- package/scripts/ai-video/adapters/kling.sh +54 -0
- package/scripts/ai-video/adapters/openai-images.sh +52 -0
- package/scripts/ai-video/adapters/sora.sh +54 -0
- package/scripts/ai-video/lib/adapter-common.sh +116 -0
- package/scripts/ai-video/lib/adapter-contract.md +163 -0
- package/scripts/ai-video/lib/fixtures/gemini-veo/result.json +1 -0
- package/scripts/ai-video/lib/fixtures/gemini-veo/scene-0001.mp4 +1 -0
- package/scripts/ai-video/lib/fixtures/higgsfield/result.json +1 -0
- package/scripts/ai-video/lib/fixtures/higgsfield/scene-0001.mp4 +1 -0
- package/scripts/ai-video/lib/fixtures/kling/result.json +1 -0
- package/scripts/ai-video/lib/fixtures/kling/scene-0001.mp4 +1 -0
- package/scripts/ai-video/lib/fixtures/openai-images/result.json +1 -0
- package/scripts/ai-video/lib/fixtures/openai-images/scene-0001.png +3 -0
- package/scripts/ai-video/lib/fixtures/sora/result.json +1 -0
- package/scripts/ai-video/lib/fixtures/sora/scene-0001.mp4 +1 -0
- package/scripts/ai-video/lib/load-config.sh +140 -0
- package/scripts/ai-video/lib/operator-pick.sh +119 -0
- package/scripts/ai-video/lib/parse-blueprint.sh +122 -0
- package/scripts/ai-video/lib/redact.sh +85 -0
- package/scripts/ai-video/lib/validate-deps.sh +132 -0
- package/scripts/ai-video/stitch.sh +154 -0
- package/scripts/ai-video/test-pipeline.sh +169 -0
- package/scripts/schemas/command.schema.json +8 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Adapter Contract (v1)
|
|
2
|
+
|
|
3
|
+
> Single source of truth for every adapter under
|
|
4
|
+
> `scripts/ai-video/adapters/`. Phase 4 Step 1 finalizes any open
|
|
5
|
+
> point in this draft; **breaking changes bump the version pin
|
|
6
|
+
> and require every adapter to be rerun against the fixture set**
|
|
7
|
+
> (`scripts/ai-video/lib/fixtures/`).
|
|
8
|
+
|
|
9
|
+
## Scope
|
|
10
|
+
|
|
11
|
+
Every backend (OpenAI Images, Gemini Veo, Kling, Higgsfield, Sora,
|
|
12
|
+
…) ships as one executable shell entry under
|
|
13
|
+
`scripts/ai-video/adapters/<id>.sh`. The orchestrator never speaks
|
|
14
|
+
to a network API directly — it speaks to one of these scripts.
|
|
15
|
+
|
|
16
|
+
## Capability flag
|
|
17
|
+
|
|
18
|
+
Each adapter declares its audio capability via a top-of-file
|
|
19
|
+
comment and via the `capability` subcommand:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
# capability: audio=native | audio=none
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`audio=native` — the provider returns a muxed MP4 with synchronized
|
|
26
|
+
dialogue / ambient. `audio=none` — video-only output; the
|
|
27
|
+
orchestrator muxes the operator-supplied track at stitch time.
|
|
28
|
+
|
|
29
|
+
A per-model adapter (e.g. Higgsfield) MAY declare `audio=per-model`
|
|
30
|
+
and surface the capability via `capability --model <id>`.
|
|
31
|
+
|
|
32
|
+
## Subcommands
|
|
33
|
+
|
|
34
|
+
Every adapter implements four:
|
|
35
|
+
|
|
36
|
+
| Subcommand | Purpose | I/O |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `submit` | Submit a render job to the backend | stdin JSON · stdout `{job_id}` |
|
|
39
|
+
| `poll <job_id>` | Poll job status | stdout `{status: queued\|running\|done\|failed, progress?}` |
|
|
40
|
+
| `fetch <job_id>` | Download artifacts after `status=done` | stdout `{video_path, audio_path?, audio_embedded}` |
|
|
41
|
+
| `dry-run` | Return a deterministic fixture path without network | stdout same shape as `fetch` |
|
|
42
|
+
|
|
43
|
+
`submit` + `poll` + `fetch` may be collapsed into a single
|
|
44
|
+
`run` for synchronous backends (OpenAI Images) — the wrapper exposes
|
|
45
|
+
the same stdout shape on stdout and an exit code on completion.
|
|
46
|
+
|
|
47
|
+
## Stdin JSON (consumed by `submit` / `run`)
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"prompt": {
|
|
52
|
+
"style": "...",
|
|
53
|
+
"subject": "...",
|
|
54
|
+
"environment": "...",
|
|
55
|
+
"action": "...",
|
|
56
|
+
"camera": "...",
|
|
57
|
+
"lens": "...",
|
|
58
|
+
"lighting": "...",
|
|
59
|
+
"mood": "..."
|
|
60
|
+
},
|
|
61
|
+
"ref_images": ["/abs/path/frame.png", "..."],
|
|
62
|
+
"duration": 4.5,
|
|
63
|
+
"aspect": "16:9",
|
|
64
|
+
"seed": 1234567,
|
|
65
|
+
"audio": {
|
|
66
|
+
"dialogue": ["speaker: \"line\"", "..."],
|
|
67
|
+
"ambient": ["rain on metal", "..."],
|
|
68
|
+
"language": "en",
|
|
69
|
+
"enable_native_audio": true
|
|
70
|
+
},
|
|
71
|
+
"negative": ["centered framing", "..."]
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`ref_images`, `duration`, `aspect`, `seed`, `audio`, `negative` are
|
|
76
|
+
optional. `prompt.*` blocks are mandatory. Unknown top-level keys
|
|
77
|
+
are logged to stderr and ignored.
|
|
78
|
+
|
|
79
|
+
## Stdout JSON (emitted by `fetch` / `run` / `dry-run`)
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"video_path": "/abs/path/scene-0001.mp4",
|
|
84
|
+
"audio_path": "/abs/path/scene-0001.wav",
|
|
85
|
+
"audio_embedded": true
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Semantics:
|
|
90
|
+
|
|
91
|
+
- `audio_embedded: true` — `video_path` is a muxed MP4 with audio;
|
|
92
|
+
`audio_path` is omitted or echoes the same path. Stitcher
|
|
93
|
+
pass-through.
|
|
94
|
+
- `audio_embedded: false` with `audio_path` — separate track to mux
|
|
95
|
+
at stitch time via `ffmpeg`.
|
|
96
|
+
- `audio_embedded: false` without `audio_path` — video-only;
|
|
97
|
+
operator supplies the audio bed at stitch time.
|
|
98
|
+
|
|
99
|
+
## Error contract
|
|
100
|
+
|
|
101
|
+
Adapter failure: **non-zero exit code** AND a JSON file at
|
|
102
|
+
`<project>/scenes/<scene_id>/error.json`:
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"adapter": "gemini-veo",
|
|
107
|
+
"subcommand": "fetch",
|
|
108
|
+
"job_id": "...",
|
|
109
|
+
"exit_code": 7,
|
|
110
|
+
"stderr_tail": "...",
|
|
111
|
+
"retryable": true,
|
|
112
|
+
"user_action": "regenerate-prompt | retry | skip | abort"
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`retryable: false` means a prompt or input change is required —
|
|
117
|
+
the orchestrator MUST NOT auto-retry. `retryable: true` does **not**
|
|
118
|
+
authorize auto-retry either; the orchestrator surfaces a single
|
|
119
|
+
numbered-options block (retry · regenerate prompt · skip · abort)
|
|
120
|
+
per `non-destructive-by-default` and waits.
|
|
121
|
+
|
|
122
|
+
## Dry-run
|
|
123
|
+
|
|
124
|
+
`AIV_DRYRUN=true` (set by command default; see Phase 5 Step 6) OR
|
|
125
|
+
the explicit `dry-run` subcommand:
|
|
126
|
+
|
|
127
|
+
- No network call.
|
|
128
|
+
- Stdout JSON points at a deterministic fixture under
|
|
129
|
+
`scripts/ai-video/lib/fixtures/<adapter-id>/`.
|
|
130
|
+
- Exit code 0.
|
|
131
|
+
|
|
132
|
+
Fixtures are committed and cover one happy path per adapter.
|
|
133
|
+
Phase 6 golden runs assert byte-identical stdout under
|
|
134
|
+
`AIV_DRYRUN=true`.
|
|
135
|
+
|
|
136
|
+
## Logging & redaction
|
|
137
|
+
|
|
138
|
+
- **stderr only** for log output. Stdout is reserved for the
|
|
139
|
+
contract JSON.
|
|
140
|
+
- Every adapter MUST source `scripts/ai-video/lib/redact.sh` and
|
|
141
|
+
pipe network responses through `aiv_redact_stream` before any
|
|
142
|
+
`>&2` write. API keys, bearer tokens, and operator-registered
|
|
143
|
+
secrets are masked.
|
|
144
|
+
|
|
145
|
+
## Strict-mode shell
|
|
146
|
+
|
|
147
|
+
Every adapter:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
#!/usr/bin/env bash
|
|
151
|
+
set -euo pipefail
|
|
152
|
+
. "$(dirname "$0")/../lib/redact.sh"
|
|
153
|
+
. "$(dirname "$0")/../lib/load-config.sh"
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`shellcheck` clean (Phase 4 Step 9).
|
|
157
|
+
|
|
158
|
+
## Versioning
|
|
159
|
+
|
|
160
|
+
This contract is `v1`. Backward-incompatible changes (renamed
|
|
161
|
+
field, removed subcommand, changed stdout shape) bump to `v2` and
|
|
162
|
+
require an `adapter-contract` migration note plus a rerun of every
|
|
163
|
+
adapter against the fixture set.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"video_path":"scripts/ai-video/lib/fixtures/gemini-veo/scene-0001.mp4","audio_embedded":true}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
FIXTURE-gemini-veo-mp4
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"video_path":"scripts/ai-video/lib/fixtures/higgsfield/scene-0001.mp4","audio_embedded":false}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
FIXTURE-higgsfield-mp4
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"video_path":"scripts/ai-video/lib/fixtures/kling/scene-0001.mp4","audio_embedded":false}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
FIXTURE-kling-mp4
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"video_path":"scripts/ai-video/lib/fixtures/openai-images/scene-0001.png","audio_embedded":false}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"video_path":"scripts/ai-video/lib/fixtures/sora/scene-0001.mp4","audio_embedded":true}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
FIXTURE-sora-mp4
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/ai-video/lib/load-config.sh — provider config loader for /video:*.
|
|
3
|
+
#
|
|
4
|
+
# Parses agents/.ai-video.xml with xmllint --xpath and surfaces a single
|
|
5
|
+
# provider's settings as AIV_KEY / AIV_ENDPOINT / AIV_MODEL / AIV_DRYRUN
|
|
6
|
+
# env vars to its caller. The key is NEVER echoed — status output is only
|
|
7
|
+
# `present` or `missing`.
|
|
8
|
+
#
|
|
9
|
+
# Usage (sourced):
|
|
10
|
+
#
|
|
11
|
+
# . scripts/ai-video/lib/load-config.sh
|
|
12
|
+
# aiv_load_provider gemini-veo
|
|
13
|
+
# echo "key: $(aiv_key_status)" # → present | missing
|
|
14
|
+
# echo "endpoint: $AIV_ENDPOINT"
|
|
15
|
+
# echo "dryrun: $AIV_DRYRUN"
|
|
16
|
+
#
|
|
17
|
+
# Usage (CLI status check, no key echoed):
|
|
18
|
+
#
|
|
19
|
+
# bash scripts/ai-video/lib/load-config.sh status gemini-veo
|
|
20
|
+
# → provider=gemini-veo key=present dryrun=true model=veo-3.0-generate-001
|
|
21
|
+
#
|
|
22
|
+
# Defaults:
|
|
23
|
+
# - AIV_DRYRUN defaults to `true` when not set in XML
|
|
24
|
+
# - AIV_DRYRUN can be overridden by the AIV_DRYRUN env var set by caller
|
|
25
|
+
# - missing file is non-fatal in `status` mode (prints all-missing line)
|
|
26
|
+
|
|
27
|
+
set -u
|
|
28
|
+
|
|
29
|
+
AIV_CONFIG_PATH="${AIV_CONFIG_PATH:-agents/.ai-video.xml}"
|
|
30
|
+
|
|
31
|
+
_aiv_xpath() {
|
|
32
|
+
# $1 = xpath expression; reads from $AIV_CONFIG_PATH
|
|
33
|
+
# Returns text content or empty string. Never errors on missing path.
|
|
34
|
+
if [ ! -f "${AIV_CONFIG_PATH}" ]; then
|
|
35
|
+
printf ''
|
|
36
|
+
return 0
|
|
37
|
+
fi
|
|
38
|
+
if ! command -v xmllint >/dev/null 2>&1; then
|
|
39
|
+
printf ''
|
|
40
|
+
return 0
|
|
41
|
+
fi
|
|
42
|
+
xmllint --xpath "string(${1})" "${AIV_CONFIG_PATH}" 2>/dev/null || printf ''
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
aiv_default_image_provider() {
|
|
46
|
+
_aiv_xpath '/ai-video/default-image-provider'
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
aiv_default_video_provider() {
|
|
50
|
+
_aiv_xpath '/ai-video/default-video-provider'
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
aiv_load_provider() {
|
|
54
|
+
local pid="${1:-}"
|
|
55
|
+
if [ -z "${pid}" ]; then
|
|
56
|
+
echo "aiv_load_provider: provider id required" >&2
|
|
57
|
+
return 2
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# Resolve from the top-level <provider> blocks OR the <extra> slot.
|
|
61
|
+
local base="(/ai-video/provider[@id='${pid}']|/ai-video/extra/provider[@id='${pid}'])"
|
|
62
|
+
|
|
63
|
+
AIV_PROVIDER_ID="${pid}"
|
|
64
|
+
AIV_KEY="$(_aiv_xpath "${base}/api-key")"
|
|
65
|
+
AIV_ENDPOINT="$(_aiv_xpath "${base}/endpoint")"
|
|
66
|
+
AIV_MODEL="$(_aiv_xpath "${base}/default-model")"
|
|
67
|
+
AIV_KIND="$(_aiv_xpath "${base}/@kind")"
|
|
68
|
+
|
|
69
|
+
local dryrun_xml
|
|
70
|
+
dryrun_xml="$(_aiv_xpath "${base}/dry-run")"
|
|
71
|
+
# Caller-set AIV_DRYRUN takes precedence; otherwise XML; otherwise true.
|
|
72
|
+
if [ -n "${AIV_DRYRUN:-}" ]; then
|
|
73
|
+
: # respect caller
|
|
74
|
+
elif [ -n "${dryrun_xml}" ]; then
|
|
75
|
+
AIV_DRYRUN="${dryrun_xml}"
|
|
76
|
+
else
|
|
77
|
+
AIV_DRYRUN="true"
|
|
78
|
+
fi
|
|
79
|
+
export AIV_DRYRUN
|
|
80
|
+
|
|
81
|
+
# Tuning fields — adapters read these directly when present.
|
|
82
|
+
AIV_TUNING_ASPECT="$(_aiv_xpath "${base}/tuning/aspect")"
|
|
83
|
+
AIV_TUNING_FPS="$(_aiv_xpath "${base}/tuning/fps")"
|
|
84
|
+
AIV_TUNING_MAX_DURATION="$(_aiv_xpath "${base}/tuning/max-duration")"
|
|
85
|
+
AIV_TUNING_AUDIO_NATIVE="$(_aiv_xpath "${base}/tuning/audio-native")"
|
|
86
|
+
AIV_TUNING_PRESET="$(_aiv_xpath "${base}/tuning/preset")"
|
|
87
|
+
AIV_TUNING_QUALITY="$(_aiv_xpath "${base}/tuning/quality")"
|
|
88
|
+
AIV_TUNING_BEST_OF_N="$(_aiv_xpath "${base}/tuning/best-of-n")"
|
|
89
|
+
export AIV_TUNING_ASPECT AIV_TUNING_FPS AIV_TUNING_MAX_DURATION \
|
|
90
|
+
AIV_TUNING_AUDIO_NATIVE AIV_TUNING_PRESET AIV_TUNING_QUALITY \
|
|
91
|
+
AIV_TUNING_BEST_OF_N
|
|
92
|
+
|
|
93
|
+
# Register key with redact.sh if loaded — adapters always source both.
|
|
94
|
+
if command -v aiv_redact_register >/dev/null 2>&1; then
|
|
95
|
+
aiv_redact_register "${AIV_KEY}"
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
return 0
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
aiv_key_status() {
|
|
102
|
+
case "${AIV_KEY:-}" in
|
|
103
|
+
""|"REPLACE-ME"|*"-REPLACE-ME") printf 'missing' ;;
|
|
104
|
+
*) printf 'present' ;;
|
|
105
|
+
esac
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# CLI mode — only `status <provider-id>` is supported; never echoes the key.
|
|
109
|
+
if [ "${BASH_SOURCE[0]:-}" = "${0}" ]; then
|
|
110
|
+
cmd="${1:-}"
|
|
111
|
+
case "${cmd}" in
|
|
112
|
+
status)
|
|
113
|
+
pid="${2:-}"
|
|
114
|
+
if [ -z "${pid}" ]; then
|
|
115
|
+
echo "usage: load-config.sh status <provider-id>" >&2
|
|
116
|
+
exit 2
|
|
117
|
+
fi
|
|
118
|
+
aiv_load_provider "${pid}" >/dev/null
|
|
119
|
+
printf 'provider=%s key=%s dryrun=%s model=%s endpoint=%s kind=%s\n' \
|
|
120
|
+
"${AIV_PROVIDER_ID}" \
|
|
121
|
+
"$(aiv_key_status)" \
|
|
122
|
+
"${AIV_DRYRUN:-true}" \
|
|
123
|
+
"${AIV_MODEL:-}" \
|
|
124
|
+
"${AIV_ENDPOINT:-}" \
|
|
125
|
+
"${AIV_KIND:-}"
|
|
126
|
+
;;
|
|
127
|
+
defaults)
|
|
128
|
+
printf 'default-image-provider=%s\n' "$(aiv_default_image_provider)"
|
|
129
|
+
printf 'default-video-provider=%s\n' "$(aiv_default_video_provider)"
|
|
130
|
+
;;
|
|
131
|
+
"")
|
|
132
|
+
echo "usage: load-config.sh {status <id> | defaults}" >&2
|
|
133
|
+
exit 2
|
|
134
|
+
;;
|
|
135
|
+
*)
|
|
136
|
+
echo "load-config.sh: unknown subcommand '${cmd}'" >&2
|
|
137
|
+
exit 2
|
|
138
|
+
;;
|
|
139
|
+
esac
|
|
140
|
+
fi
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# operator-pick.sh — best-of-N selection checkpoint invoked after image
|
|
3
|
+
# render by /video:from-script and /video:scene. Renders a thumbnail
|
|
4
|
+
# contact-sheet PNG of the candidates under a scene, then waits for the
|
|
5
|
+
# operator to write <project>/scenes/<id>/selection.json.
|
|
6
|
+
#
|
|
7
|
+
# Dry-run mode (AIV_DRYRUN=true, default) auto-picks candidate 1 and
|
|
8
|
+
# writes the same selection.json so smoke tests stay unattended.
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# operator-pick.sh <project-dir> <scene-id>
|
|
12
|
+
#
|
|
13
|
+
# Inputs:
|
|
14
|
+
# <project-dir>/scenes/<scene-id>/candidates/*.png (N>=1 image files)
|
|
15
|
+
#
|
|
16
|
+
# Outputs (stdout, one path per line so callers can capture both):
|
|
17
|
+
# sheet=<project-dir>/scenes/<scene-id>/contact-sheet.png
|
|
18
|
+
# selected=<absolute path to locked image>
|
|
19
|
+
#
|
|
20
|
+
# Exit codes:
|
|
21
|
+
# 0 selection.json present, locked image path emitted
|
|
22
|
+
# 2 missing candidates directory or empty
|
|
23
|
+
# 3 selection.json malformed or names an unknown candidate
|
|
24
|
+
# 4 operator declined (selection.json absent in live mode)
|
|
25
|
+
|
|
26
|
+
set -euo pipefail
|
|
27
|
+
|
|
28
|
+
if [ "$#" -ne 2 ]; then
|
|
29
|
+
printf 'operator-pick: usage: %s <project-dir> <scene-id>\n' "$0" >&2
|
|
30
|
+
exit 2
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
project_dir="$1"
|
|
34
|
+
scene_id="$2"
|
|
35
|
+
scene_dir="${project_dir}/scenes/${scene_id}"
|
|
36
|
+
cand_dir="${scene_dir}/candidates"
|
|
37
|
+
sheet="${scene_dir}/contact-sheet.png"
|
|
38
|
+
sel_file="${scene_dir}/selection.json"
|
|
39
|
+
|
|
40
|
+
if [ ! -d "${cand_dir}" ]; then
|
|
41
|
+
printf 'operator-pick: missing candidate dir: %s\n' "${cand_dir}" >&2
|
|
42
|
+
exit 2
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# Collect candidates in deterministic order (lexical). Portable to
|
|
46
|
+
# macOS bash 3.2 — no `mapfile`.
|
|
47
|
+
candidates=()
|
|
48
|
+
while IFS= read -r _p; do
|
|
49
|
+
candidates+=("${_p}")
|
|
50
|
+
done < <(find "${cand_dir}" -maxdepth 1 -type f -name '*.png' | LC_ALL=C sort)
|
|
51
|
+
if [ "${#candidates[@]}" -eq 0 ]; then
|
|
52
|
+
printf 'operator-pick: no candidate PNGs under %s\n' "${cand_dir}" >&2
|
|
53
|
+
exit 2
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# Build the contact-sheet PNG via ffmpeg. ceil(sqrt(N)) columns.
|
|
57
|
+
n="${#candidates[@]}"
|
|
58
|
+
cols=1
|
|
59
|
+
while [ $((cols * cols)) -lt "${n}" ]; do cols=$((cols + 1)); done
|
|
60
|
+
|
|
61
|
+
# ffmpeg's `tile` filter needs an input concat; use `-pattern_type glob`.
|
|
62
|
+
if command -v ffmpeg >/dev/null 2>&1; then
|
|
63
|
+
ffmpeg -y -loglevel error \
|
|
64
|
+
-pattern_type glob -i "${cand_dir}/*.png" \
|
|
65
|
+
-filter_complex "tile=${cols}x0:padding=8:margin=16" \
|
|
66
|
+
"${sheet}" || {
|
|
67
|
+
printf 'operator-pick: ffmpeg tile failed; falling back to first candidate as sheet\n' >&2
|
|
68
|
+
cp "${candidates[0]}" "${sheet}"
|
|
69
|
+
}
|
|
70
|
+
else
|
|
71
|
+
# ffmpeg absent (e.g. minimal CI). Copy the first candidate as the sheet.
|
|
72
|
+
cp "${candidates[0]}" "${sheet}"
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# Dry-run: auto-select candidate 1 and write selection.json verbatim so
|
|
76
|
+
# downstream resume reads it identically to a real operator pick.
|
|
77
|
+
if [ "${AIV_DRYRUN:-true}" = "true" ]; then
|
|
78
|
+
first="$(basename "${candidates[0]}")"
|
|
79
|
+
cat > "${sel_file}" <<JSON
|
|
80
|
+
{
|
|
81
|
+
"selected": "${first}",
|
|
82
|
+
"reason": "auto-selected by operator-pick.sh (AIV_DRYRUN=true)"
|
|
83
|
+
}
|
|
84
|
+
JSON
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# Wait-loop is not appropriate here — the caller (command file) decides
|
|
88
|
+
# whether to poll or hand back. We assert selection.json exists and
|
|
89
|
+
# resolve it; if missing in live mode, we exit non-zero so the caller
|
|
90
|
+
# can pause and re-invoke.
|
|
91
|
+
if [ ! -f "${sel_file}" ]; then
|
|
92
|
+
printf 'operator-pick: selection.json absent; write %s with {"selected":"<filename>"} and re-run\n' "${sel_file}" >&2
|
|
93
|
+
exit 4
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# Read selection.json (jq required — already a hard dep for adapters).
|
|
97
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
98
|
+
printf 'operator-pick: jq is required\n' >&2
|
|
99
|
+
exit 3
|
|
100
|
+
fi
|
|
101
|
+
selected_name="$(jq -r '.selected // empty' "${sel_file}")"
|
|
102
|
+
if [ -z "${selected_name}" ]; then
|
|
103
|
+
printf 'operator-pick: selection.json missing "selected" field\n' >&2
|
|
104
|
+
exit 3
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
selected_path="${cand_dir}/${selected_name}"
|
|
108
|
+
if [ ! -f "${selected_path}" ]; then
|
|
109
|
+
printf 'operator-pick: selected candidate not found: %s\n' "${selected_path}" >&2
|
|
110
|
+
exit 3
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
# Also write a stable locked.png symlink/copy at the scene root so the
|
|
114
|
+
# motion step can resolve the locked image without re-reading selection.json.
|
|
115
|
+
locked="${scene_dir}/locked.png"
|
|
116
|
+
cp -f "${selected_path}" "${locked}"
|
|
117
|
+
|
|
118
|
+
printf 'sheet=%s\n' "${sheet}"
|
|
119
|
+
printf 'selected=%s\n' "${locked}"
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# parse-blueprint.sh — read a 12-block Cinematic Scene Blueprint
|
|
3
|
+
# from stdin (or a file arg) and emit adapter-contract JSON on
|
|
4
|
+
# stdout. Pure POSIX-compatible bash (no associative arrays, runs
|
|
5
|
+
# on macOS bash 3.2); jq required for JSON safety.
|
|
6
|
+
#
|
|
7
|
+
# Schema: .agent-src.uncompressed/skills/scene-expander/scene-blueprint.schema.yaml
|
|
8
|
+
# Contract: scripts/ai-video/lib/adapter-contract.md
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# parse-blueprint.sh < prompt.txt > blueprint.json
|
|
12
|
+
# parse-blueprint.sh prompt.txt > blueprint.json
|
|
13
|
+
#
|
|
14
|
+
# Exit codes:
|
|
15
|
+
# 0 valid blueprint, JSON emitted on stdout
|
|
16
|
+
# 2 missing required block — name on stderr
|
|
17
|
+
# 3 parse error (unknown block, malformed DURATION, etc.)
|
|
18
|
+
|
|
19
|
+
set -euo pipefail
|
|
20
|
+
|
|
21
|
+
command -v jq >/dev/null 2>&1 || {
|
|
22
|
+
echo "parse-blueprint: jq required" >&2
|
|
23
|
+
exit 3
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
input="${1:-/dev/stdin}"
|
|
27
|
+
[ -r "$input" ] || { echo "parse-blueprint: cannot read $input" >&2; exit 3; }
|
|
28
|
+
|
|
29
|
+
V_STYLE=""; V_SUBJECT=""; V_ENVIRONMENT=""; V_ACTION=""
|
|
30
|
+
V_CAMERA=""; V_LENS=""; V_LIGHTING=""; V_MOOD=""
|
|
31
|
+
V_DIALOGUE=""; V_AMBIENT_SOUND=""; V_DURATION=""; V_NEGATIVE=""
|
|
32
|
+
|
|
33
|
+
append_to() {
|
|
34
|
+
local name="$1"; local line="$2"
|
|
35
|
+
local cur; eval "cur=\${$name}"
|
|
36
|
+
if [ -z "$cur" ]; then
|
|
37
|
+
eval "$name=\$line"
|
|
38
|
+
else
|
|
39
|
+
eval "$name=\"\${$name}
|
|
40
|
+
\$line\""
|
|
41
|
+
fi
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
set_var() {
|
|
45
|
+
eval "$1=\$2"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
current=""
|
|
49
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
50
|
+
stripped="$(printf '%s' "$line" | sed -E 's/^[[:space:]]*//; s/^\*\*//; s/\*\*[[:space:]]*$//')"
|
|
51
|
+
label="$(printf '%s' "$stripped" | sed -nE 's/^([A-Z][A-Z ]+)[[:space:]]*:.*$/\1/p')"
|
|
52
|
+
if [ -n "$label" ]; then
|
|
53
|
+
key="$(printf '%s' "$label" | tr ' ' '_' | tr '[:lower:]' '[:upper:]')"
|
|
54
|
+
case "$key" in
|
|
55
|
+
STYLE|SUBJECT|ENVIRONMENT|ACTION|CAMERA|LENS|LIGHTING|MOOD|DIALOGUE|AMBIENT_SOUND|DURATION|NEGATIVE)
|
|
56
|
+
current="V_$key"
|
|
57
|
+
rest="$(printf '%s' "$stripped" | sed -E "s/^${label}[[:space:]]*:[[:space:]]*//")"
|
|
58
|
+
set_var "$current" "$rest"
|
|
59
|
+
continue
|
|
60
|
+
;;
|
|
61
|
+
esac
|
|
62
|
+
fi
|
|
63
|
+
if [ -n "$current" ] && [ -n "$line" ]; then
|
|
64
|
+
append_to "$current" "$line"
|
|
65
|
+
fi
|
|
66
|
+
done < "$input"
|
|
67
|
+
|
|
68
|
+
for short in STYLE SUBJECT ENVIRONMENT ACTION CAMERA LENS LIGHTING MOOD DURATION NEGATIVE; do
|
|
69
|
+
eval "v=\$V_$short"
|
|
70
|
+
if [ -z "$v" ]; then
|
|
71
|
+
echo "parse-blueprint: missing required block: $short" >&2
|
|
72
|
+
exit 2
|
|
73
|
+
fi
|
|
74
|
+
done
|
|
75
|
+
|
|
76
|
+
if ! printf '%s' "$V_DURATION" | grep -Eq '^[0-9]+(\.[0-9])?$'; then
|
|
77
|
+
echo "parse-blueprint: DURATION not numeric: $V_DURATION" >&2
|
|
78
|
+
exit 3
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
negative_json="$(printf '%s' "$V_NEGATIVE" | tr ',\n' '\n\n' | sed '/^[[:space:]]*$/d' | sed -E 's/^[[:space:]]*//; s/[[:space:]]*$//' | jq -R . | jq -s .)"
|
|
82
|
+
|
|
83
|
+
to_array_or_null() {
|
|
84
|
+
if [ -z "$1" ]; then
|
|
85
|
+
printf '%s' "null"
|
|
86
|
+
else
|
|
87
|
+
printf '%s\n' "$1" | sed '/^[[:space:]]*$/d' | jq -R . | jq -s .
|
|
88
|
+
fi
|
|
89
|
+
}
|
|
90
|
+
dialogue_json="$(to_array_or_null "$V_DIALOGUE")"
|
|
91
|
+
ambient_json="$(to_array_or_null "$V_AMBIENT_SOUND")"
|
|
92
|
+
|
|
93
|
+
audio_native=false
|
|
94
|
+
if [ "$dialogue_json" != "null" ] || [ "$ambient_json" != "null" ]; then
|
|
95
|
+
audio_native=true
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
jq -n \
|
|
99
|
+
--arg style "$V_STYLE" \
|
|
100
|
+
--arg subject "$V_SUBJECT" \
|
|
101
|
+
--arg environment "$V_ENVIRONMENT" \
|
|
102
|
+
--arg action "$V_ACTION" \
|
|
103
|
+
--arg camera "$V_CAMERA" \
|
|
104
|
+
--arg lens "$V_LENS" \
|
|
105
|
+
--arg lighting "$V_LIGHTING" \
|
|
106
|
+
--arg mood "$V_MOOD" \
|
|
107
|
+
--argjson dialogue "$dialogue_json" \
|
|
108
|
+
--argjson ambient "$ambient_json" \
|
|
109
|
+
--argjson duration "$V_DURATION" \
|
|
110
|
+
--argjson negative "$negative_json" \
|
|
111
|
+
--argjson native "$audio_native" \
|
|
112
|
+
'{
|
|
113
|
+
prompt: {
|
|
114
|
+
style: $style, subject: $subject, environment: $environment,
|
|
115
|
+
action: $action, camera: $camera, lens: $lens,
|
|
116
|
+
lighting: $lighting, mood: $mood
|
|
117
|
+
},
|
|
118
|
+
audio: { dialogue: $dialogue, ambient: $ambient, enable_native_audio: $native },
|
|
119
|
+
duration: $duration,
|
|
120
|
+
negative: $negative,
|
|
121
|
+
requires: { audio_native: $native }
|
|
122
|
+
}'
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/ai-video/lib/redact.sh — secret-scrubbing helper for /video:* adapters.
|
|
3
|
+
#
|
|
4
|
+
# Sourced by every adapter under scripts/ai-video/adapters/*.sh. Provides
|
|
5
|
+
# two helpers:
|
|
6
|
+
#
|
|
7
|
+
# aiv_redact_register <secret> — register a string to scrub (idempotent)
|
|
8
|
+
# aiv_redact <text> — print text with every registered
|
|
9
|
+
# secret replaced by ***REDACTED***
|
|
10
|
+
#
|
|
11
|
+
# Iron Law: an API key must never reach stdout/stderr verbatim. Adapters
|
|
12
|
+
# pipe every network response, curl error, and trace through aiv_redact
|
|
13
|
+
# before printing. Empty / unset values are skipped silently — an unset
|
|
14
|
+
# secret cannot leak.
|
|
15
|
+
#
|
|
16
|
+
# Pure bash, no external dependencies. Safe under `set -euo pipefail`.
|
|
17
|
+
|
|
18
|
+
# Guard against double-source.
|
|
19
|
+
if [ -n "${AIV_REDACT_LOADED:-}" ]; then
|
|
20
|
+
return 0 2>/dev/null || exit 0
|
|
21
|
+
fi
|
|
22
|
+
AIV_REDACT_LOADED=1
|
|
23
|
+
|
|
24
|
+
# Registry of secrets to scrub. Newline-separated; populated by
|
|
25
|
+
# aiv_redact_register. Never echoed.
|
|
26
|
+
AIV_REDACT_SECRETS=""
|
|
27
|
+
|
|
28
|
+
aiv_redact_register() {
|
|
29
|
+
local secret="${1:-}"
|
|
30
|
+
# Treat empty / placeholder values as no-op so adapters can call this
|
|
31
|
+
# unconditionally without leaking the placeholder string.
|
|
32
|
+
case "${secret}" in
|
|
33
|
+
""|"REPLACE-ME"|*"-REPLACE-ME") return 0 ;;
|
|
34
|
+
esac
|
|
35
|
+
# Require a minimum length so single characters do not nuke the log.
|
|
36
|
+
if [ "${#secret}" -lt 8 ]; then
|
|
37
|
+
return 0
|
|
38
|
+
fi
|
|
39
|
+
# Idempotent — skip if already registered.
|
|
40
|
+
case "${AIV_REDACT_SECRETS}" in
|
|
41
|
+
*"${secret}"*) return 0 ;;
|
|
42
|
+
esac
|
|
43
|
+
AIV_REDACT_SECRETS="${AIV_REDACT_SECRETS}${secret}
|
|
44
|
+
"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
aiv_redact() {
|
|
48
|
+
local input
|
|
49
|
+
if [ "$#" -gt 0 ]; then
|
|
50
|
+
input="$*"
|
|
51
|
+
else
|
|
52
|
+
input="$(cat)"
|
|
53
|
+
fi
|
|
54
|
+
if [ -z "${AIV_REDACT_SECRETS}" ]; then
|
|
55
|
+
printf '%s\n' "${input}"
|
|
56
|
+
return 0
|
|
57
|
+
fi
|
|
58
|
+
# Apply replacements one secret at a time using awk so special chars
|
|
59
|
+
# in the secret cannot break a sed expression. Use a here-string to
|
|
60
|
+
# avoid running the loop in a subshell (which would discard mutations
|
|
61
|
+
# to ${input}).
|
|
62
|
+
local secret
|
|
63
|
+
while IFS= read -r secret; do
|
|
64
|
+
[ -z "${secret}" ] && continue
|
|
65
|
+
input="$(printf '%s' "${input}" | awk -v s="${secret}" '
|
|
66
|
+
{
|
|
67
|
+
out = ""
|
|
68
|
+
rest = $0
|
|
69
|
+
while ((i = index(rest, s)) > 0) {
|
|
70
|
+
out = out substr(rest, 1, i - 1) "***REDACTED***"
|
|
71
|
+
rest = substr(rest, i + length(s))
|
|
72
|
+
}
|
|
73
|
+
print out rest
|
|
74
|
+
}')"
|
|
75
|
+
done <<< "${AIV_REDACT_SECRETS}"
|
|
76
|
+
printf '%s\n' "${input}"
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# Convenience wrapper: pipe stdin through aiv_redact line by line so
|
|
80
|
+
# adapters can do `curl … 2>&1 | aiv_redact_stream`.
|
|
81
|
+
aiv_redact_stream() {
|
|
82
|
+
while IFS= read -r line; do
|
|
83
|
+
aiv_redact "${line}"
|
|
84
|
+
done
|
|
85
|
+
}
|