-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Expand file tree
/
Copy pathcreate-tapes.js
More file actions
160 lines (131 loc) · 4.52 KB
/
create-tapes.js
File metadata and controls
160 lines (131 loc) · 4.52 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
#!/usr/bin/env node
/**
* Generate .tape files from demos.json configuration
*
* Supports single-prompt and multi-prompt demos:
* - "prompt": "text" → single prompt
* - "prompts": ["a", "b"] → multi-prompt (default responseWait each)
* - "prompts": [{ "text": "a", "responseWait": 10 }, "b"] → mixed with overrides
*
* Usage: npm run create:tapes
*/
const { writeFileSync, mkdirSync, existsSync } = require('fs');
const { join } = require('path');
const rootDir = join(__dirname, '..', '..');
const config = require('./demos.json');
function generatePromptBlock(entry, defaultWait, index) {
const text = typeof entry === 'string' ? entry : entry.text;
const wait = (typeof entry === 'object' && entry.responseWait) || defaultWait;
const agentSelect = typeof entry === 'object' && entry.agentSelect;
const label = index != null ? `Prompt ${index + 1}` : 'Execute the prompt';
// Agent selection: type /agent, wait for picker, arrow down to agent, select
if (agentSelect) {
const arrowDown = (typeof entry === 'object' && entry.arrowDown) || 0;
const arrowBlock = arrowDown > 0 ? `Down ${arrowDown}\nSleep 1s\n` : '';
return `# ${label} - Select ${agentSelect} agent
Type "${text}"
Sleep 1s
Enter
# Wait for agent picker
Sleep 3s
${arrowBlock}Enter
# Wait for agent to load
Sleep ${wait}s`;
}
// If prompt ends with a file reference (@path), the file picker will be open.
// Need an extra Enter to select the file before submitting the prompt.
const endsWithFileRef = /@\S+$/.test(text);
const enterBlock = endsWithFileRef
? 'Enter\nSleep 1s\nEnter'
: 'Enter';
// VHS Type command must be single-line; split multi-line prompts
const lines = text.split('\n');
let typeBlock;
if (lines.length > 1) {
typeBlock = lines
.map((line, i) => i < lines.length - 1 ? `Type "${line}"\nEnter` : `Type "${line}"`)
.join('\n');
} else {
typeBlock = `Type "${text}"`;
}
// Break response wait into chunks with periodic hidden nudges.
// A hidden space+backspace forces copilot's TUI to scroll to the input area.
const nudgeInterval = 3;
let waitBlock = '';
let remaining = wait;
while (remaining > nudgeInterval) {
waitBlock += `Sleep ${nudgeInterval}s\nHide\nType " "\nBackspace\nShow\n`;
remaining -= nudgeInterval;
}
if (remaining > 0) {
waitBlock += `Sleep ${remaining}s`;
}
return `# ${label}
${typeBlock}
Sleep 2s
${enterBlock}
# Wait for response (with periodic nudges to keep input visible)
${waitBlock}`;
}
function generateTapeContent(demo, settings) {
const s = { ...settings, ...demo }; // Allow per-demo overrides
// Build prompt blocks from either "prompt" (single) or "prompts" (array)
let promptBlocks;
if (demo.prompts && Array.isArray(demo.prompts)) {
promptBlocks = demo.prompts
.map((entry, i) => generatePromptBlock(entry, s.responseWait, i))
.join('\n\n');
} else {
promptBlocks = generatePromptBlock(demo.prompt, s.responseWait);
}
return `# ${demo.chapter}: ${demo.description}
# Auto-generated from demos.json - Real copilot execution
Output ${demo.name}.gif
Set FontSize ${s.fontSize}
Set Width ${s.width}
Set Height ${s.height}
Set Theme "${s.theme}"
Set Padding 20
Set BorderRadius 8
Set Margin 10
Set MarginFill "#282a36"
Set Framerate ${s.framerate}
# Human typing speed
Set TypingSpeed ${s.typingSpeed}
# Launch copilot
Type "copilot"
Enter
# Wait for copilot to start
Sleep ${s.startupWait}s
${promptBlocks}
# Nudge TUI to scroll to input area
Type " "
Backspace
Sleep ${s.exitWait}s
# Exit cleanly
Ctrl+C
Sleep 1s
`;
}
// Main
console.log('📝 Creating tape files from demos.json...\n');
let created = 0;
for (const demo of config.demos) {
const imagesDir = join(rootDir, demo.chapter, 'images');
const tapePath = join(imagesDir, `${demo.name}.tape`);
// Ensure images directory exists
if (!existsSync(imagesDir)) {
mkdirSync(imagesDir, { recursive: true });
console.log(` Created: ${demo.chapter}/images/`);
}
// Generate tape content
const content = generateTapeContent(demo, config.settings);
// Write tape file
writeFileSync(tapePath, content);
console.log(` ✓ ${demo.chapter}/images/${demo.name}.tape`);
created++;
}
console.log(`\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
console.log(`✓ Created ${created} tape file(s)`);
console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
console.log(`\nNext: npm run generate:vhs`);