-
-
Notifications
You must be signed in to change notification settings - Fork 452
Expand file tree
/
Copy pathsetup.py
More file actions
386 lines (324 loc) · 15.9 KB
/
Copy pathsetup.py
File metadata and controls
386 lines (324 loc) · 15.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
#!/usr/bin/env python3
"""
Flow Kit — AI Tool Setup
Generates AI tool configs from skills/fk-*.md (single source of truth).
Usage:
python setup.py # Interactive: pick your AI tool(s)
python setup.py --tool claude # Generate for Claude Code
python setup.py --tool codex # Generate for Codex
python setup.py --tool all # Generate for all tools
python setup.py sync # Re-sync all previously selected tools
python setup.py clean # Remove all generated tool configs
The Gemini CLI target was removed in v1.3.1. The Gemini CLI is retired, and
its replacement `agy` (Antigravity) reads none of what that target produced —
verified against agy 1.2.7: a project's `.gemini/commands/*.toml` is not
expanded, `.claude/commands/*.md` is not either, and `GEMINI.md`, `AGENTS.md`
and `CLAUDE.md` are all absent from a print-mode run's context even inside a
trusted folder. agy's own commands come from its plugin system
(`agy plugin import`), which imports extensions rather than command files.
`agy` is still a first-class part of Flow Kit — as one of the three AI CLIs
that can run video review. That is configured in `agent/providers.json` via
`/fk-change-provider` or the dashboard, not here: this script generates
per-project command files for the CLI you *drive* Flow Kit from.
"""
import argparse
import json
import sys
from datetime import datetime, timezone
from pathlib import Path
ROOT = Path(__file__).parent
SKILLS_DIR = ROOT / "skills"
CLAUDE_COMMANDS_DIR = ROOT / ".claude" / "commands"
AGENTS_MD = ROOT / "AGENTS.md"
STATE_FILE = ROOT / ".fk-setup.json"
# Nothing generates these any more; `clean` still removes them so an install
# from before v1.3.1 does not keep stale files for a CLI that no longer exists.
LEGACY_GEMINI_COMMANDS_DIR = ROOT / ".gemini" / "commands" / "fk"
LEGACY_GEMINI_MD = ROOT / "GEMINI.md"
def discover_skills():
"""Scan skills/fk-*.md and extract name + first-line description."""
if not SKILLS_DIR.exists():
print(f"ERROR: skills/ directory not found at {SKILLS_DIR}")
return []
skills = []
for path in sorted(SKILLS_DIR.glob("fk-*.md")):
name = path.stem[len("fk-"):] # strip "fk-" prefix
description = ""
try:
first_line = path.read_text(encoding="utf-8").splitlines()[0].strip()
# Strip leading markdown heading markers
description = first_line.lstrip("#").strip()
except (IndexError, OSError):
description = f"{name} skill"
skills.append({"name": name, "description": description, "path": str(path)})
return skills
def generate_claude(skills):
"""Create .claude/commands/fk-<name>.md stubs."""
CLAUDE_COMMANDS_DIR.mkdir(parents=True, exist_ok=True)
count = 0
for skill in skills:
name = skill["name"]
desc = skill["description"]
dest = CLAUDE_COMMANDS_DIR / f"fk-{name}.md"
content = (
f"{desc}\n"
f"<!-- AUTO-GENERATED by setup.py — do not edit. Source: skills/ -->\n"
f"Read and follow the instructions in skills/fk-{name}.md\n\n"
f"Arguments: $ARGUMENTS\n"
)
dest.write_text(content, encoding="utf-8")
count += 1
print(f"Generated {count} Claude commands in .claude/commands/")
return count
## ── Inline content for AGENTS.md ──
# Inlined so a non-Claude tool sees the rules directly instead of following a
# "read CLAUDE.md" pointer.
#
# This block is the ONLY source for the generated file. AGENTS.md says
# "do not edit" and was edited anyway: rules 14-16 and pipeline steps 0 and 7.5
# lived only in the generated artifact, so `setup.py sync` would have deleted
# them. They are here now. Anything added to AGENTS.md by hand is lost on the
# next sync — put it here instead.
_CRITICAL_RULES = """\
## Critical Rules (MUST follow)
1. **Media ID is always UUID** — format `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`. Never use `CAMS...` / base64 strings.
2. **Scene prompts = ACTION only** — never describe character appearance. Reference images handle visual consistency via `imageInputs`.
3. **All reference images must exist before scene images** — verify every entity has `media_id` before generating scene images.
4. **No throwaway scripts** — NEVER write Python, shell, or any script file to loop over API requests. Use `POST /api/requests/batch` to submit all requests at once, then poll `GET /api/requests/batch-status`. The server throttles automatically.
5. **Locations use landscape, characters use portrait** — reference image orientation depends on entity type.
6. **UUID extraction** — if a response gives `CAMS...` instead of UUID, extract UUID from the `fifeUrl` in the response URL: `/image/{UUID}?...`.
7. **Cascade on regen** — regenerating an image auto-clears downstream video + upscale.
8. **REGENERATE vs GENERATE** — `GENERATE_*` skips if already COMPLETED. `REGENERATE_*` always runs (clears + regenerates).
9. **Image Material required** — every project needs a `material` field (e.g. `realistic`, `3d_pixar`, `anime`). List available: `GET /api/materials`.
10. **Server handles throttling** — worker enforces max 5 concurrent requests + 10s cooldown. Submit ALL requests via `/batch`; do NOT manually stagger or loop.
11. **Video prompts use sub-clip timing** — structure 8s video as time segments: `0-3s: [action]. 3-6s: [action]. 6-8s: [action].`
12. **Character dialogue in sub-clips** — embed speech in quotes: `Luna says "Goodnight."` Max 10-15 words per character per 2-3s segment.
13. **Scenes are mutable** — use `PATCH /api/scenes/{sid}` to update `prompt`, `video_prompt`, `narrator_text`, `character_names` after creation. Don't delete and recreate — patch instead.
14. **Fact-check before scripting** — ALWAYS research events via web search before writing project stories, scene prompts, or narrator text. Facts (events, dates, names, operations, outcomes) MUST match real sources. Editorial opinion and analysis are allowed but must be framed as such. Never invent events, operation names, or statistics.
15. **Real-people bypass** — when characters are based on real famous people (politicians, celebrities, military leaders), NEVER use their real name as entity `name` or anywhere in `description`, `image_prompt`, `prompt`, or `video_prompt`. AI image generators reject known public figures. Instead: (a) use a **role-based alias** as entity name (e.g. "Tổng Tư Lệnh" not "Trump", "Thủ Tướng Sắt" not "Netanyahu"), (b) describe **physical appearance only** — distinctive hair, face shape, build, clothing style — without naming who it is, (c) `narrator_text` may use real titles/roles for storytelling but real names never flow into image/video generation. Keep a `real_reference` mapping in the project plan file (`.omc/research/`) for internal tracking.
16. **Review before upscale** — ALWAYS run `/fk-review-video` (light mode) after video generation, before upscaling. Scenes scoring < 7.5 get `video_prompt` updated from review errors, then regen video. Max 2 review-regen cycles.
"""
_PIPELINE_OVERVIEW = """\
## Pipeline Order
```
0. Research /fk-research "topic" (fact-check via web search, save to .omc/research/)
1. Health check GET /health → extension_connected: true
2. Create project POST /api/projects (with entities + material, story from research)
3. Create video POST /api/videos
4. Create scenes POST /api/scenes (with character_names, chain_type)
5. Gen ref images POST /api/requests/batch → poll /batch-status?project_id=<PID>
Wait for done=true, verify all entities have media_id
6. Gen scene images POST /api/requests/batch → poll /batch-status?video_id=<VID>
Wait for done=true, verify image_media_id = UUID
7. Gen videos POST /api/requests/batch → poll /batch-status?video_id=<VID>
Wait for done=true (videos take 2-5 min each)
7.5 Review videos POST /api/videos/{vid}/review?mode=light (AI vision quality check)
Pass: score >= 7.5 | Fail: update video_prompt → regen → re-review (max 2 cycles)
8. (Optional) 4K POST /api/requests/batch (TIER_TWO only)
9. (Optional) TTS Create voice template → POST /api/videos/{vid}/narrate
10. Concat ffmpeg normalize + concat
```
"""
_BATCH_API = """\
## Batch API
Submit N requests at once (server throttles automatically — max 5 concurrent, 10s cooldown):
```bash
curl -X POST http://127.0.0.1:8100/api/requests/batch \\
-H "Content-Type: application/json" \\
-d '{"requests": [{"type": "...", "scene_id": "...", "project_id": "...", "video_id": "...", "orientation": "VERTICAL"}, ...]}'
```
Poll aggregate status:
```bash
curl -s "http://127.0.0.1:8100/api/requests/batch-status?video_id=<VID>&type=GENERATE_IMAGE"
# Returns: {"total": 40, "pending": 30, "processing": 5, "completed": 5, "failed": 0, "done": false}
# When "done": true → all requests have left the queue (completed or failed)
# When "all_succeeded": true → every request completed successfully
```
For full API reference, workflow recipes, and video prompt guidelines, see `CLAUDE.md`.
"""
def _agents_md_body(skills, title):
"""Build the AGENTS.md body with the critical rules inlined."""
rows = "\n".join(
f"| `/fk-{s['name']}` | {s['description']} |" for s in skills
)
return (
f"<!-- AUTO-GENERATED by setup.py — do not edit. Source: skills/ -->\n"
f"# Flow Kit — {title}\n\n"
f"Base URL: `http://127.0.0.1:8100`\n\n"
f"## Pre-flight\n\n"
f"Before ANY workflow:\n"
f"```bash\n"
f"curl -s http://127.0.0.1:8100/health\n"
f"# Must return: {{\"extension_connected\": true}}\n"
f"```\n\n"
f"{_CRITICAL_RULES}\n"
f"{_PIPELINE_OVERVIEW}\n"
f"{_BATCH_API}\n"
f"## Skills\n\n"
f"This project has reusable skills in `skills/`. When the user says `/fk-<name>`, "
f"read `skills/fk-<name>.md` and follow the instructions inside.\n\n"
f"| Skill | Purpose |\n"
f"|-------|---------|\n"
f"{rows}\n"
)
def generate_codex(skills):
"""Generate AGENTS.md for Codex CLI."""
content = _agents_md_body(skills, "Codex CLI Instructions")
AGENTS_MD.write_text(content, encoding="utf-8")
print(f"Generated AGENTS.md ({len(skills)} skills)")
return len(skills)
def save_state(tools, skill_count):
state = {
"tools": tools,
"last_sync": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"skill_count": skill_count,
}
STATE_FILE.write_text(json.dumps(state, indent=2), encoding="utf-8")
def load_state():
if not STATE_FILE.exists():
return None
try:
return json.loads(STATE_FILE.read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError):
return None
TOOLS = {
"claude": generate_claude,
"codex": generate_codex,
}
def run_generators(tools, skills):
"""Run the appropriate generators for the given tool list."""
for tool in tools:
if tool in TOOLS:
TOOLS[tool](skills)
else:
# Reachable from a .fk-setup.json written before v1.3.1, which
# records "gemini". Skipping beats crashing a sync on a target
# that no longer exists; `clean` removes what it left behind.
print(f"Skipping '{tool}' — no longer a supported target. "
f"Run 'python setup.py clean' to remove its leftover files.")
def do_interactive(skills):
"""Interactive mode: prompt user to pick tools."""
print("\nFlow Kit — AI Tool Setup\n")
print("Which AI tool do you drive this project from?")
print(" [1] Claude Code (.claude/commands/)")
print(" [2] Codex CLI (AGENTS.md)")
print(" [3] Both")
print()
print(" (agy / Antigravity reads none of these — it is configured as a")
print(" video-review provider instead, via /fk-change-provider.)")
print()
raw = input("Select (comma-separated, e.g. 1,2): ").strip()
if not raw:
print("No selection made. Exiting.")
return
mapping = {"1": "claude", "2": "codex", "3": "all"}
choices = [c.strip() for c in raw.split(",")]
tools = []
for c in choices:
if c not in mapping:
print(f"Invalid choice: {c}")
continue
val = mapping[c]
if val == "all":
tools = list(TOOLS)
break
if val not in tools:
tools.append(val)
if not tools:
print("No valid tools selected. Exiting.")
return
run_generators(tools, skills)
save_state(tools, len(skills))
print(f"\nSetup complete. State saved to .fk-setup.json")
def do_tool(tool, skills):
"""Non-interactive: generate for a specific tool or all."""
if tool == "all":
tools = list(TOOLS)
else:
tools = [tool]
run_generators(tools, skills)
save_state(tools, len(skills))
print(f"\nSetup complete. State saved to .fk-setup.json")
def do_sync(skills):
"""Re-sync all previously selected tools."""
state = load_state()
if not state:
print("No previous setup found. Run 'python setup.py' first.")
sys.exit(1)
tools = state.get("tools", [])
if not tools:
print("No tools recorded in .fk-setup.json.")
sys.exit(1)
print(f"Re-syncing for tools: {', '.join(tools)}")
run_generators(tools, skills)
save_state(tools, len(skills))
print(f"\nSync complete. {len(skills)} skills processed.")
def do_clean():
"""Remove all generated tool configs."""
removed = []
# Claude commands
if CLAUDE_COMMANDS_DIR.exists():
for f in CLAUDE_COMMANDS_DIR.glob("fk-*.md"):
f.unlink()
removed.append(str(f))
# Whatever the retired Gemini target left behind. Removed here even
# though nothing writes it any more — an install from before v1.3.1 has
# these files, and `clean` is the only thing that will ever take them away.
if LEGACY_GEMINI_COMMANDS_DIR.exists():
for f in LEGACY_GEMINI_COMMANDS_DIR.glob("*.toml"):
f.unlink()
removed.append(str(f))
try:
LEGACY_GEMINI_COMMANDS_DIR.rmdir()
LEGACY_GEMINI_COMMANDS_DIR.parent.rmdir() # .gemini/commands
LEGACY_GEMINI_COMMANDS_DIR.parent.parent.rmdir() # .gemini
except OSError:
pass # not empty (agy keeps its own state here), leave it
# AGENTS.md, plus the legacy GEMINI.md
for md in [AGENTS_MD, LEGACY_GEMINI_MD]:
if md.exists():
first_line = md.read_text(encoding="utf-8").splitlines()[0]
if "AUTO-GENERATED by setup.py" in first_line:
md.unlink()
removed.append(str(md))
# State file
if STATE_FILE.exists():
STATE_FILE.unlink()
removed.append(str(STATE_FILE))
if removed:
print(f"Removed {len(removed)} generated files.")
else:
print("Nothing to clean.")
def main():
parser = argparse.ArgumentParser(
description="Flow Kit — AI Tool Setup",
add_help=True,
)
parser.add_argument(
"subcommand",
nargs="?",
choices=["sync", "clean"],
help="sync: re-sync previously selected tools | clean: remove all generated configs",
)
parser.add_argument(
"--tool",
choices=[*TOOLS, "all"],
help="Generate configs for a specific tool (non-interactive)",
)
args = parser.parse_args()
if args.subcommand == "clean":
do_clean()
return
# All other subcommands need skill discovery
skills = discover_skills()
if not skills and args.subcommand != "clean":
print("No skills found in skills/fk-*.md — nothing to generate.")
sys.exit(1)
if args.subcommand == "sync":
do_sync(skills)
elif args.tool:
do_tool(args.tool, skills)
else:
do_interactive(skills)
if __name__ == "__main__":
main()