buttercut 0.6.0 → 0.7.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.
- checksums.yaml +4 -4
- data/.claude/settings.local.json +8 -1
- data/.claude/skills +1 -0
- data/CLAUDE.md +1 -308
- data/LICENSE +106 -21
- data/README.md +13 -8
- data/lib/buttercut/version.rb +1 -1
- data/templates/library_template.yaml +14 -3
- data/templates/plan_template.md +1 -1
- data/templates/settings_template.yaml +6 -0
- metadata +5 -42
- data/.claude/scripts/script_extractor.rb +0 -66
- data/.claude/skills/analyze-video/SKILL.md +0 -30
- data/.claude/skills/analyze-video/agent_prompt.md +0 -84
- data/.claude/skills/analyze-video/prepare_visual_script.rb +0 -25
- data/.claude/skills/backup-library/SKILL.md +0 -26
- data/.claude/skills/backup-library/backup_libraries.rb +0 -46
- data/.claude/skills/cut-planner/SKILL.md +0 -74
- data/.claude/skills/release/SKILL.md +0 -214
- data/.claude/skills/roughcut/SKILL.md +0 -65
- data/.claude/skills/roughcut/agent_prompt.md +0 -153
- data/.claude/skills/roughcut/export_to_fcpxml.rb +0 -132
- data/.claude/skills/setup/SKILL.md +0 -47
- data/.claude/skills/setup/advanced-setup.md +0 -141
- data/.claude/skills/setup/simple-setup.md +0 -185
- data/.claude/skills/setup/verify_install.rb +0 -124
- data/.claude/skills/summarize-video/SKILL.md +0 -31
- data/.claude/skills/summarize-video/agent_prompt.md +0 -39
- data/.claude/skills/summarize-video/summary_skeleton.rb +0 -78
- data/.claude/skills/summarize-video/visual_script_extractor.rb +0 -78
- data/.claude/skills/transcribe-audio/SKILL.md +0 -36
- data/.claude/skills/transcribe-audio/agent_prompt.md +0 -53
- data/.claude/skills/transcribe-audio/prepare_audio_script.rb +0 -48
- data/.claude/skills/transcribe-audio/refine_instructions.md +0 -114
- data/.claude/skills/update-buttercut/SKILL.md +0 -54
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: buttercut
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrew Ford
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-05-
|
|
11
|
+
date: 2026-05-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: nokogiri
|
|
@@ -24,20 +24,6 @@ dependencies:
|
|
|
24
24
|
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '1.13'
|
|
27
|
-
- !ruby/object:Gem::Dependency
|
|
28
|
-
name: rubyzip
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - "~>"
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: '2.3'
|
|
34
|
-
type: :runtime
|
|
35
|
-
prerelease: false
|
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - "~>"
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: '2.3'
|
|
41
27
|
- !ruby/object:Gem::Dependency
|
|
42
28
|
name: rspec
|
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -62,32 +48,9 @@ extensions: []
|
|
|
62
48
|
extra_rdoc_files: []
|
|
63
49
|
files:
|
|
64
50
|
- ".claude/commands/worktree.md"
|
|
65
|
-
- ".claude/scripts/script_extractor.rb"
|
|
66
51
|
- ".claude/settings.json"
|
|
67
52
|
- ".claude/settings.local.json"
|
|
68
|
-
- ".claude/skills
|
|
69
|
-
- ".claude/skills/analyze-video/agent_prompt.md"
|
|
70
|
-
- ".claude/skills/analyze-video/prepare_visual_script.rb"
|
|
71
|
-
- ".claude/skills/backup-library/SKILL.md"
|
|
72
|
-
- ".claude/skills/backup-library/backup_libraries.rb"
|
|
73
|
-
- ".claude/skills/cut-planner/SKILL.md"
|
|
74
|
-
- ".claude/skills/release/SKILL.md"
|
|
75
|
-
- ".claude/skills/roughcut/SKILL.md"
|
|
76
|
-
- ".claude/skills/roughcut/agent_prompt.md"
|
|
77
|
-
- ".claude/skills/roughcut/export_to_fcpxml.rb"
|
|
78
|
-
- ".claude/skills/setup/SKILL.md"
|
|
79
|
-
- ".claude/skills/setup/advanced-setup.md"
|
|
80
|
-
- ".claude/skills/setup/simple-setup.md"
|
|
81
|
-
- ".claude/skills/setup/verify_install.rb"
|
|
82
|
-
- ".claude/skills/summarize-video/SKILL.md"
|
|
83
|
-
- ".claude/skills/summarize-video/agent_prompt.md"
|
|
84
|
-
- ".claude/skills/summarize-video/summary_skeleton.rb"
|
|
85
|
-
- ".claude/skills/summarize-video/visual_script_extractor.rb"
|
|
86
|
-
- ".claude/skills/transcribe-audio/SKILL.md"
|
|
87
|
-
- ".claude/skills/transcribe-audio/agent_prompt.md"
|
|
88
|
-
- ".claude/skills/transcribe-audio/prepare_audio_script.rb"
|
|
89
|
-
- ".claude/skills/transcribe-audio/refine_instructions.md"
|
|
90
|
-
- ".claude/skills/update-buttercut/SKILL.md"
|
|
53
|
+
- ".claude/skills"
|
|
91
54
|
- CLAUDE.md
|
|
92
55
|
- LICENSE
|
|
93
56
|
- README.md
|
|
@@ -103,7 +66,7 @@ files:
|
|
|
103
66
|
- templates/settings_template.yaml
|
|
104
67
|
homepage: https://github.com/andrewford/buttercut
|
|
105
68
|
licenses:
|
|
106
|
-
-
|
|
69
|
+
- Nonstandard
|
|
107
70
|
metadata: {}
|
|
108
71
|
post_install_message:
|
|
109
72
|
rdoc_options: []
|
|
@@ -120,7 +83,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
120
83
|
- !ruby/object:Gem::Version
|
|
121
84
|
version: '0'
|
|
122
85
|
requirements: []
|
|
123
|
-
rubygems_version: 3.5.
|
|
86
|
+
rubygems_version: 3.5.22
|
|
124
87
|
signing_key:
|
|
125
88
|
specification_version: 4
|
|
126
89
|
summary: Video Editor XML generator with Agent skills for analyzing video, creating
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# Extract the plain-text script from a WhisperX-style transcript JSON.
|
|
3
|
-
#
|
|
4
|
-
# Usage:
|
|
5
|
-
# ruby .claude/scripts/script_extractor.rb <transcript.json> <output.txt>
|
|
6
|
-
#
|
|
7
|
-
# Output is one segment per paragraph (blank line between), trimmed, suitable
|
|
8
|
-
# for proofreading by a human or a sub-agent without the overhead of the full
|
|
9
|
-
# transcript JSON (word-level timing, scores, etc.).
|
|
10
|
-
|
|
11
|
-
require 'json'
|
|
12
|
-
|
|
13
|
-
class ScriptExtractor
|
|
14
|
-
def self.extract(transcript_path, output_path)
|
|
15
|
-
new(transcript_path, output_path).extract
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def initialize(transcript_path, output_path)
|
|
19
|
-
raise ArgumentError, "transcript_path is required" if transcript_path.nil? || transcript_path.empty?
|
|
20
|
-
raise ArgumentError, "output_path is required" if output_path.nil? || output_path.empty?
|
|
21
|
-
@transcript_path = transcript_path
|
|
22
|
-
@output_path = output_path
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def extract
|
|
26
|
-
write_output(format_script)
|
|
27
|
-
report
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
private
|
|
31
|
-
|
|
32
|
-
attr_reader :transcript_path, :output_path
|
|
33
|
-
|
|
34
|
-
def data
|
|
35
|
-
@data ||= JSON.parse(File.read(transcript_path))
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def segments
|
|
39
|
-
data["segments"] or raise "transcript JSON has no 'segments' key: #{transcript_path}"
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def format_script
|
|
43
|
-
paragraphs = segments.map { |s| s["text"].to_s.strip }.reject(&:empty?)
|
|
44
|
-
paragraphs.join("\n\n") + "\n"
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def write_output(text)
|
|
48
|
-
File.write(output_path, text)
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def report
|
|
52
|
-
in_kb = (File.size(transcript_path) / 1024.0).round(1)
|
|
53
|
-
out_kb = (File.size(output_path) / 1024.0).round(1)
|
|
54
|
-
puts "Extracted script: #{output_path} (#{out_kb} KB from #{in_kb} KB source, #{segments.size} segments)"
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
if __FILE__ == $PROGRAM_NAME
|
|
59
|
-
transcript_path, output_path = ARGV
|
|
60
|
-
abort("usage: script_extractor.rb <transcript.json> <output.txt>") unless transcript_path && output_path
|
|
61
|
-
abort("file not found: #{transcript_path}") unless File.file?(transcript_path)
|
|
62
|
-
if File.expand_path(output_path) == File.expand_path(transcript_path)
|
|
63
|
-
abort("output path must differ from transcript path: #{transcript_path}")
|
|
64
|
-
end
|
|
65
|
-
ScriptExtractor.extract(transcript_path, output_path)
|
|
66
|
-
end
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: analyze-video
|
|
3
|
-
description: Adds visual descriptions to transcripts by extracting and analyzing video frames with ffmpeg. Creates visual transcript with periodic visual descriptions of the video clip. Use when all files have audio transcripts present (transcript) but don't yet have visual transcripts created (visual_transcript).
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Skill: Analyze Video (parent brief)
|
|
7
|
-
|
|
8
|
-
Adds visual descriptions to a video's audio transcript by extracting JPG frames with ffmpeg and analyzing them.
|
|
9
|
-
|
|
10
|
-
`SKILL.md` is the parent's dispatch brief. The sub-agent's working prompt lives in `agent_prompt.md` — inline its contents when launching the Task agent. Don't pass `SKILL.md`.
|
|
11
|
-
|
|
12
|
-
## Prerequisites
|
|
13
|
-
|
|
14
|
-
Each video must already have an audio transcript. Run `transcribe-audio` first if any are missing.
|
|
15
|
-
|
|
16
|
-
## Parallelism
|
|
17
|
-
|
|
18
|
-
Launch at most **8 in parallel**. ffmpeg frame extraction is a brief CPU burst at the start; the rest of the runtime is LLM API calls. 8 is a comfortable middle ground that won't saturate older machines.
|
|
19
|
-
|
|
20
|
-
## Inputs to gather and pass inline
|
|
21
|
-
|
|
22
|
-
- `video_path` — absolute path to the video file
|
|
23
|
-
- `audio_transcript_path` — absolute path to the prepared audio transcript JSON
|
|
24
|
-
- `visual_transcript_path` — absolute path to write the visual transcript JSON
|
|
25
|
-
|
|
26
|
-
After the agent returns, update `library.yaml` with `visual_transcript: <filename>.json`.
|
|
27
|
-
|
|
28
|
-
## Next step
|
|
29
|
-
|
|
30
|
-
Once all videos have visual transcripts, dispatch `summarize-video` (Haiku model) to produce summaries.
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
# Analyze Video (sub-agent prompt)
|
|
2
|
-
|
|
3
|
-
You are a sub-agent. Add visual descriptions to one video's audio transcript by extracting JPG frames with ffmpeg and analyzing them. **Never read the video file directly** — extract frames first.
|
|
4
|
-
|
|
5
|
-
## Inputs (passed inline by the parent)
|
|
6
|
-
|
|
7
|
-
- `video_path` — absolute path to the video file
|
|
8
|
-
- `audio_transcript_path` — absolute path to the prepared audio transcript JSON
|
|
9
|
-
- `visual_transcript_path` — absolute path to write the visual transcript JSON
|
|
10
|
-
|
|
11
|
-
Do NOT read `library.yaml` or `settings.yaml`.
|
|
12
|
-
|
|
13
|
-
## 1. Copy & clean audio transcript
|
|
14
|
-
|
|
15
|
-
Don't read the audio transcript — just copy it, then prepare it via `prepare_visual_script.rb`. This removes word-level timing data and prettifies the JSON for easier editing:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
cp <audio_transcript_path> <visual_transcript_path>
|
|
19
|
-
ruby .claude/skills/analyze-video/prepare_visual_script.rb <visual_transcript_path>
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## 2. Extract frames (binary search)
|
|
23
|
-
|
|
24
|
-
Create frame directory: `mkdir -p tmp/frames/[video_name]`
|
|
25
|
-
|
|
26
|
-
**Videos ≤30s:** extract one frame at 2s
|
|
27
|
-
**Videos >30s:** extract start (2s), middle (duration/2), end (duration-2s)
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
ffmpeg -ss 00:00:02 -i video.mov -vframes 1 -vf "scale=1280:-1" tmp/frames/[video_name]/start.jpg
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
**Subdivide when:** start, middle, and end have different subjects, settings, or angle changes
|
|
34
|
-
**Stop when:** the footage no longer seems to be changing or only has minor changes
|
|
35
|
-
**Never sample** more frequently than once per 30 seconds
|
|
36
|
-
|
|
37
|
-
## 3. Add visual descriptions
|
|
38
|
-
|
|
39
|
-
Read the visual transcript JSON you created in step 1.
|
|
40
|
-
|
|
41
|
-
**Read the JPG frames** from `tmp/frames/[video_name]/` using the Read tool, then **Edit** the file at `<visual_transcript_path>`. Do this incrementally — no script needed; just edit the JSON each time you read new frames.
|
|
42
|
-
|
|
43
|
-
**Dialogue segments — add `visual` field:**
|
|
44
|
-
```json
|
|
45
|
-
{
|
|
46
|
-
"start": 2.917,
|
|
47
|
-
"end": 7.586,
|
|
48
|
-
"text": "Hey, good afternoon everybody.",
|
|
49
|
-
"visual": "Man in red shirt speaking to camera in medium shot. Home office with bookshelf. Natural lighting.",
|
|
50
|
-
"words": [...]
|
|
51
|
-
}
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
**B-roll segments — insert new entries:**
|
|
55
|
-
```json
|
|
56
|
-
{
|
|
57
|
-
"start": 35.474,
|
|
58
|
-
"end": 56.162,
|
|
59
|
-
"text": "",
|
|
60
|
-
"visual": "Green bicycle parked in front of building. Urban street with trees.",
|
|
61
|
-
"b_roll": true,
|
|
62
|
-
"words": []
|
|
63
|
-
}
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
**Guidelines:**
|
|
67
|
-
- Descriptions: 3 sentences max
|
|
68
|
-
- First segment: detailed (subject, setting, shot type, lighting, camera style)
|
|
69
|
-
- Continuing shots: brief if similar; up to 3 sentences if drastically different
|
|
70
|
-
|
|
71
|
-
## 4. Cleanup & return
|
|
72
|
-
|
|
73
|
-
```bash
|
|
74
|
-
rm -rf tmp/frames/[video_name]
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
Return:
|
|
78
|
-
```
|
|
79
|
-
✓ [video_filename.mov] analyzed successfully
|
|
80
|
-
Visual transcript: <visual_transcript_path>
|
|
81
|
-
Video path: <video_path>
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
**Do NOT update library.yaml** — parent handles this to avoid race conditions in parallel execution.
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
require 'json'
|
|
3
|
-
|
|
4
|
-
abort "Usage: ruby prepare_visual_script.rb <json_file>" if ARGV.empty?
|
|
5
|
-
abort "Error: File not found: #{ARGV[0]}" unless File.exist?(ARGV[0])
|
|
6
|
-
|
|
7
|
-
begin
|
|
8
|
-
data = JSON.parse(File.read(ARGV[0]))
|
|
9
|
-
|
|
10
|
-
data['segments']&.each { |s| s.delete('words') }
|
|
11
|
-
data.delete('word_segments')
|
|
12
|
-
|
|
13
|
-
# Reorder keys: language and video_path first, then segments, then everything else
|
|
14
|
-
reordered = {}
|
|
15
|
-
reordered['language'] = data['language'] if data['language']
|
|
16
|
-
reordered['video_path'] = data['video_path'] if data['video_path']
|
|
17
|
-
reordered['segments'] = data['segments'] if data['segments']
|
|
18
|
-
# Add any other keys that might exist
|
|
19
|
-
data.each { |k, v| reordered[k] = v unless reordered.key?(k) }
|
|
20
|
-
|
|
21
|
-
File.write(ARGV[0], JSON.pretty_generate(reordered))
|
|
22
|
-
puts "Prettified: #{ARGV[0]} (word-level timing removed)"
|
|
23
|
-
rescue JSON::ParserError => e
|
|
24
|
-
abort "Error: Invalid JSON - #{e.message}"
|
|
25
|
-
end
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: backup-library
|
|
3
|
-
description: Backs up user libraries and all their contents (external video excluded). This skill can also be useful when you need to restore a library.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Skill: Backup Library
|
|
7
|
-
|
|
8
|
-
Verify libraries directory exists:
|
|
9
|
-
```bash
|
|
10
|
-
ls -la libraries/
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
Run backup:
|
|
14
|
-
```bash
|
|
15
|
-
ruby .claude/skills/backup-library/backup_libraries.rb
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
Creates `backups/libraries_YYYYMMDD_HHMMSS.zip` containing the entire libraries directory.
|
|
19
|
-
|
|
20
|
-
## Restore Library
|
|
21
|
-
|
|
22
|
-
To restore from a backup, extract the ZIP file to the project root.
|
|
23
|
-
```bash
|
|
24
|
-
unzip backups/libraries_timestamp.zip -d .
|
|
25
|
-
```
|
|
26
|
-
This restores all libraries to their original locations.
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
# Library Backup Utility
|
|
5
|
-
# Creates compressed ZIP backups of the entire libraries directory
|
|
6
|
-
|
|
7
|
-
require 'fileutils'
|
|
8
|
-
require 'time'
|
|
9
|
-
require 'zip'
|
|
10
|
-
|
|
11
|
-
class LibraryBackup
|
|
12
|
-
def initialize(project_root = Dir.pwd)
|
|
13
|
-
@libraries_dir = File.join(project_root, 'libraries')
|
|
14
|
-
@backups_dir = File.join(project_root, 'backups')
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def backup
|
|
18
|
-
unless Dir.exist?(@libraries_dir)
|
|
19
|
-
puts "❌ No libraries directory found"
|
|
20
|
-
return nil
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
FileUtils.mkdir_p(@backups_dir)
|
|
24
|
-
|
|
25
|
-
timestamp = Time.now.strftime('%Y%m%d_%H%M%S')
|
|
26
|
-
backup_path = File.join(@backups_dir, "libraries_#{timestamp}.zip")
|
|
27
|
-
|
|
28
|
-
puts "📦 Creating backup: #{backup_path}"
|
|
29
|
-
|
|
30
|
-
files = Dir.glob(File.join(@libraries_dir, '**', '*')).select { |f| File.file?(f) }
|
|
31
|
-
|
|
32
|
-
Zip::File.open(backup_path, create: true) do |zipfile|
|
|
33
|
-
files.each do |file|
|
|
34
|
-
zipfile.add(file.sub("#{File.dirname(@libraries_dir)}/", ''), file)
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
puts "✅ Backed up #{files.size} files"
|
|
39
|
-
backup_path
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# CLI
|
|
44
|
-
if __FILE__ == $PROGRAM_NAME
|
|
45
|
-
LibraryBackup.new.backup
|
|
46
|
-
end
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: cut-planner
|
|
3
|
-
description: Plans a cut (roughcut, sequence, or scene) from a library's clip summaries. Reads all clip summaries, then talks with the user and iteratively creates a plan markdown file until both agent and user are happy and understand the plan.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Skill: Cut Planner
|
|
7
|
-
|
|
8
|
-
## Overview
|
|
9
|
-
|
|
10
|
-
In the cut-planner skill, the main thread reads clip summaries from a library to understand footage coverage, then asks the user about the footage to confirm its understanding. It confirms who the characters and locations are, then updates the library.yaml's footage_summary and user_context as it learns more about the footage. If it determines summaries are wrong or missing details, it also updates the summary markdowns.
|
|
11
|
-
|
|
12
|
-
After confirming its understanding of the footage, it works with the user to create a narrative plan markdown file.
|
|
13
|
-
|
|
14
|
-
This skill runs in the main thread and does not use a sub-agent.
|
|
15
|
-
|
|
16
|
-
## Cut Planner Process
|
|
17
|
-
|
|
18
|
-
### 1. Verify all clips have visual transcripts and summaries
|
|
19
|
-
Read `libraries/[library-name]/library.yaml`. Every clip must have `visual_transcript` and `summary` populated. If either is missing for any clip, stop and tell the user which clips still need processing — don't try to plan from incomplete footage. Then ask if they want to resume processing the library.
|
|
20
|
-
|
|
21
|
-
### 2. Read summaries
|
|
22
|
-
|
|
23
|
-
#### Sequences
|
|
24
|
-
If the user explicitly says they want something short like a short sequence (60 seconds or less), consider asking them about what they want and then grepping through summaries to find the handful of files they might need.
|
|
25
|
-
|
|
26
|
-
#### Rough Cuts
|
|
27
|
-
If the user wants a full roughcut, read every `libraries/[library-name]/summaries/summary_*.md` file. This will give you full knowledge of the library.
|
|
28
|
-
|
|
29
|
-
### 3. Confirm the footage knowledge and update incorrect summaries
|
|
30
|
-
Tell the user what you've learned about the footage, then confirm you understand the Five W's of all the footage: Who, What, When, Where, Why.
|
|
31
|
-
|
|
32
|
-
Don't tell them "Five W's" or label out Who, What, When, Where, Why, just talk with them conversationally like an assistant editor getting a grip on the footage.
|
|
33
|
-
|
|
34
|
-
If they want a full roughcut, spend more time. If they just want a sequence, be brief.
|
|
35
|
-
|
|
36
|
-
Talk with the user until you confirm you understand the footage. Update library.yaml based on the user's responses as you work through questions.
|
|
37
|
-
|
|
38
|
-
Update footage_summary (locations, characters, narrative, dialogue, clips) and user_context (preferences, goals, etc.) as you iteratively learn more about the footage.
|
|
39
|
-
|
|
40
|
-
Updating user_context and footage_summary helps future agents understand the footage and the user.
|
|
41
|
-
|
|
42
|
-
For example, if a summary mentions a generic man or woman but you learn the person is actually the user, replace man/woman with the user's name. Ask the user's name if you don't know it already.
|
|
43
|
-
|
|
44
|
-
### 4. Ask target length
|
|
45
|
-
If available, use the `AskUserQuestion` tool or similar to ask the user what length of video they want to create. Use your judgement based on the footage — options like short sequence (30–60s), medium cut (5–8 min), or longer roughcut (9+ min) make good starting points. Podcast footage will likely require a longer option.
|
|
46
|
-
|
|
47
|
-
### 5. If creating a roughcut, propose 2–3 concepts (titles only)
|
|
48
|
-
Give the user 2–3 genuinely distinct narrative concepts. Keep this round short — it's about picking a direction, not approving a full plan. For each concept, write only:
|
|
49
|
-
- **Title** — short, evocative
|
|
50
|
-
- **Concept** — 1–2 sentences explaining the angle, tone, or arc
|
|
51
|
-
|
|
52
|
-
Do **not** include beats, footage suggestions, runtime breakdowns, or format notes yet. Those come in step 6 once a direction is chosen.
|
|
53
|
-
|
|
54
|
-
Make the options genuinely distinct — different angles, tones, or arcs. End with: "Which feels right, or want me to explore something different?"
|
|
55
|
-
|
|
56
|
-
If the user just wants a short sequence, give them information about what the sequence will contain.
|
|
57
|
-
|
|
58
|
-
### 6. Flesh out the chosen concept
|
|
59
|
-
Once the user picks a direction for a full roughcut, expand it into a full plan and present that for approval. Now include:
|
|
60
|
-
- **Format** — vlog, YouTube Short, long-form, documentary, etc.
|
|
61
|
-
- **Beats** — 3–6 beats, each with editorial intent and a rough share of the runtime ("open with ~3 min of X", "montage of Y", "close on Z")
|
|
62
|
-
- **Footage suggestions per beat** — name a few videos likely to feed each beat, ie DJI_123, panasonic_1234, etc. Include rough or specific dialogue if you think it will be helpful.
|
|
63
|
-
- **Approx. duration**
|
|
64
|
-
|
|
65
|
-
Iterate on the fleshed-out plan until the user explicitly signals go.
|
|
66
|
-
|
|
67
|
-
If the user wants a short sequence, be brief, just a few sentences, including dialogue if that makes sense.
|
|
68
|
-
|
|
69
|
-
### 7. Save the plan
|
|
70
|
-
Copy `templates/plan_template.md` to `libraries/[library-name]/plans/plan_[short-name]_[YYYYMMDD_HHMMSS].md` and fill in every section. The template is the canonical structure — Concept, Format, Target Duration, Beats (with intent / approx. share / footage suggestions), Required Dialogue, Notes for the Build.
|
|
71
|
-
|
|
72
|
-
The plan is direction. The build agent confirms specific clips inside each beat.
|
|
73
|
-
|
|
74
|
-
Tell the user the plan is ready and confirm they want to move forward, then invoke the `roughcut` skill, passing the full plan path (`libraries/[library-name]/plans/plan_[short-name]_[YYYYMMDD_HHMMSS].md`) as a skill argument — `roughcut` hard-stops if it isn't given one.
|
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: release
|
|
3
|
-
description: Creates a new ButterCut release with version bump, changelog, git tag, gem build, and GitHub release. Use when publishing a new version.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Skill: Release ButterCut
|
|
7
|
-
|
|
8
|
-
Guides through the complete release process: version bump, changelog, git operations, gem publishing, and GitHub release creation.
|
|
9
|
-
|
|
10
|
-
## When to Use
|
|
11
|
-
|
|
12
|
-
- Publishing a new version of ButterCut
|
|
13
|
-
- After merging features or fixes that should be released
|
|
14
|
-
- Creating the first v0.1.0 release
|
|
15
|
-
|
|
16
|
-
## Workflow
|
|
17
|
-
|
|
18
|
-
### 1. Run Tests First
|
|
19
|
-
|
|
20
|
-
**CRITICAL: Always run tests before releasing. Never release if tests fail.**
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
bundle exec rspec
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
If any tests fail, STOP immediately and ask user to fix before proceeding with release.
|
|
27
|
-
|
|
28
|
-
### 2. Check Current State
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
# Read current version
|
|
32
|
-
cat lib/buttercut/version.rb
|
|
33
|
-
|
|
34
|
-
# Check git status (must be clean)
|
|
35
|
-
git status
|
|
36
|
-
|
|
37
|
-
# Check existing tags
|
|
38
|
-
git tag -l
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
If git status is not clean, stop and ask user to commit or stash changes before proceeding.
|
|
42
|
-
|
|
43
|
-
### 3. Determine New Version
|
|
44
|
-
|
|
45
|
-
Ask user what type of release following [Semantic Versioning](https://semver.org/):
|
|
46
|
-
- **MAJOR** (1.0.0): Breaking changes
|
|
47
|
-
- **MINOR** (0.2.0): New features, backward compatible
|
|
48
|
-
- **PATCH** (0.1.1): Bug fixes, backward compatible
|
|
49
|
-
|
|
50
|
-
Calculate new version number based on current version and release type.
|
|
51
|
-
|
|
52
|
-
### 4. Update Version File
|
|
53
|
-
|
|
54
|
-
Edit `lib/buttercut/version.rb` with the new version:
|
|
55
|
-
|
|
56
|
-
```ruby
|
|
57
|
-
class ButterCut
|
|
58
|
-
VERSION = "0.2.0" # Update this
|
|
59
|
-
end
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
### 5. Update Gemfile.lock
|
|
63
|
-
|
|
64
|
-
Run `bundle install` so `Gemfile.lock` reflects the new version:
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
bundle install
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
Verify the version updated in `Gemfile.lock` before proceeding.
|
|
71
|
-
|
|
72
|
-
### 6. Gather Changelog Notes
|
|
73
|
-
|
|
74
|
-
Ask user for release notes. Prompt with:
|
|
75
|
-
- What changed in this release?
|
|
76
|
-
- Any new features?
|
|
77
|
-
- Any bug fixes?
|
|
78
|
-
- Any breaking changes?
|
|
79
|
-
|
|
80
|
-
### 7. Update or Create CHANGELOG.md
|
|
81
|
-
|
|
82
|
-
If `CHANGELOG.md` exists, prepend new entry. Otherwise create it:
|
|
83
|
-
|
|
84
|
-
```markdown
|
|
85
|
-
# Changelog
|
|
86
|
-
|
|
87
|
-
All notable changes to ButterCut will be documented in this file.
|
|
88
|
-
|
|
89
|
-
## [0.2.0] - 2025-01-21
|
|
90
|
-
|
|
91
|
-
### Added
|
|
92
|
-
- Feature X
|
|
93
|
-
- Support for Y
|
|
94
|
-
|
|
95
|
-
### Fixed
|
|
96
|
-
- Bug in Z
|
|
97
|
-
|
|
98
|
-
### Changed
|
|
99
|
-
- Improved W
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### 8. Commit Version Bump
|
|
103
|
-
|
|
104
|
-
```bash
|
|
105
|
-
git add lib/buttercut/version.rb Gemfile.lock CHANGELOG.md
|
|
106
|
-
git commit -m "Bump version to 0.2.0"
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### 9. Create and Push Git Tag
|
|
110
|
-
|
|
111
|
-
```bash
|
|
112
|
-
git tag v0.2.0
|
|
113
|
-
git push origin main
|
|
114
|
-
git push origin v0.2.0
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### 10. Build Gem
|
|
118
|
-
|
|
119
|
-
```bash
|
|
120
|
-
gem build buttercut.gemspec
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
This creates `buttercut-0.2.0.gem` file.
|
|
124
|
-
|
|
125
|
-
### 11. Publish to RubyGems
|
|
126
|
-
|
|
127
|
-
**First time setup check:**
|
|
128
|
-
|
|
129
|
-
If this is the first release, verify RubyGems authentication:
|
|
130
|
-
```bash
|
|
131
|
-
gem signin
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
If not authenticated, provide instructions:
|
|
135
|
-
1. Sign up at https://rubygems.org
|
|
136
|
-
2. Run `gem signin` and follow prompts
|
|
137
|
-
3. Store credentials for future releases
|
|
138
|
-
|
|
139
|
-
**Publish the gem:**
|
|
140
|
-
```bash
|
|
141
|
-
gem push buttercut-0.2.0.gem
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
This makes the gem available for `gem install buttercut` worldwide.
|
|
145
|
-
|
|
146
|
-
### 12. Create GitHub Release
|
|
147
|
-
|
|
148
|
-
**Using GitHub CLI:**
|
|
149
|
-
```bash
|
|
150
|
-
gh release create v0.2.0 \
|
|
151
|
-
--title "v0.2.0" \
|
|
152
|
-
--notes "[Release notes from changelog]" \
|
|
153
|
-
buttercut-0.2.0.gem
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
**If `gh` CLI not available:**
|
|
157
|
-
|
|
158
|
-
Guide user through manual release creation:
|
|
159
|
-
1. Go to https://github.com/andrewford/buttercut/releases/new
|
|
160
|
-
2. Choose tag: v0.2.0
|
|
161
|
-
3. Set title: v0.2.0
|
|
162
|
-
4. Paste changelog notes in description
|
|
163
|
-
5. Attach buttercut-0.2.0.gem file
|
|
164
|
-
6. Click "Publish release"
|
|
165
|
-
|
|
166
|
-
Then wait for user confirmation that release is created before proceeding to cleanup.
|
|
167
|
-
|
|
168
|
-
### 13. Cleanup
|
|
169
|
-
|
|
170
|
-
```bash
|
|
171
|
-
# Remove local gem file (it's on RubyGems and GitHub now)
|
|
172
|
-
rm buttercut-0.2.0.gem
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
### 14. Verify Release
|
|
176
|
-
|
|
177
|
-
Check that everything worked:
|
|
178
|
-
- RubyGems page: https://rubygems.org/gems/buttercut
|
|
179
|
-
- GitHub releases: https://github.com/andrewford/buttercut/releases
|
|
180
|
-
- Git tags: `git tag -l`
|
|
181
|
-
|
|
182
|
-
### 15. Return Success Response
|
|
183
|
-
|
|
184
|
-
Provide summary:
|
|
185
|
-
```
|
|
186
|
-
✓ ButterCut 0.2.0 released successfully
|
|
187
|
-
|
|
188
|
-
Version: 0.2.0
|
|
189
|
-
Git tag: v0.2.0
|
|
190
|
-
RubyGems: Published at https://rubygems.org/gems/buttercut
|
|
191
|
-
GitHub Release: https://github.com/andrewford/buttercut/releases/tag/v0.2.0
|
|
192
|
-
|
|
193
|
-
Installation:
|
|
194
|
-
gem install buttercut
|
|
195
|
-
|
|
196
|
-
Upgrade:
|
|
197
|
-
gem update buttercut
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
## Critical Principles
|
|
201
|
-
|
|
202
|
-
**Always run tests first** - Never release if tests fail
|
|
203
|
-
**Git must be clean** - No uncommitted changes before release
|
|
204
|
-
**Push before publish** - Tags must be pushed before creating GitHub release
|
|
205
|
-
**Semantic versioning** - Follow semver strictly for version numbers
|
|
206
|
-
**Changelog required** - Every release needs documented changes
|
|
207
|
-
|
|
208
|
-
## Common Issues
|
|
209
|
-
|
|
210
|
-
**Tests failing:** Ask user to fix tests before proceeding
|
|
211
|
-
**Git not clean:** Ask user to commit or stash changes first
|
|
212
|
-
**Tag already exists:** Verify this isn't a duplicate release
|
|
213
|
-
**RubyGems authentication:** Guide through `gem signin` process
|
|
214
|
-
**GitHub CLI not installed:** Provide manual release instructions
|