shellfie 0.1.1 → 1.1.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/CHANGELOG.md +56 -2
- data/README.md +248 -228
- data/lib/shellfie/animation_frame_builder.rb +202 -0
- data/lib/shellfie/animation_scroll_easing.rb +77 -0
- data/lib/shellfie/animation_timeline.rb +28 -0
- data/lib/shellfie/ansi_colors.rb +94 -0
- data/lib/shellfie/ansi_line_buffer.rb +103 -0
- data/lib/shellfie/ansi_normalizer.rb +61 -0
- data/lib/shellfie/ansi_parser.rb +162 -83
- data/lib/shellfie/cassette.rb +76 -0
- data/lib/shellfie/cli.rb +75 -163
- data/lib/shellfie/cli_authoring.rb +233 -0
- data/lib/shellfie/cli_generate.rb +401 -0
- data/lib/shellfie/cli_info.rb +239 -0
- data/lib/shellfie/cli_run.rb +167 -0
- data/lib/shellfie/config.rb +112 -25
- data/lib/shellfie/config_defaults.rb +83 -0
- data/lib/shellfie/config_validation.rb +289 -0
- data/lib/shellfie/dependency_checker.rb +147 -0
- data/lib/shellfie/errors.rb +12 -1
- data/lib/shellfie/ffmpeg_encoder.rb +46 -0
- data/lib/shellfie/font_resolver.rb +68 -0
- data/lib/shellfie/format_resolver.rb +15 -0
- data/lib/shellfie/gif_generator.rb +231 -89
- data/lib/shellfie/gif_palette.rb +105 -0
- data/lib/shellfie/headless_theme_registry.rb +42 -0
- data/lib/shellfie/html_renderer.rb +54 -0
- data/lib/shellfie/image_magick_command_builder.rb +75 -0
- data/lib/shellfie/line_layout.rb +146 -0
- data/lib/shellfie/output_writer.rb +46 -0
- data/lib/shellfie/parser.rb +183 -30
- data/lib/shellfie/parser_validation.rb +178 -0
- data/lib/shellfie/raster_painter.rb +157 -0
- data/lib/shellfie/render_chrome_cache.rb +40 -0
- data/lib/shellfie/render_geometry.rb +115 -0
- data/lib/shellfie/render_segment.rb +71 -0
- data/lib/shellfie/renderer.rb +96 -149
- data/lib/shellfie/rendering/shape_helpers.rb +42 -0
- data/lib/shellfie/rendering/text_painter.rb +195 -0
- data/lib/shellfie/rendering/window_chrome.rb +196 -0
- data/lib/shellfie/reproducibility_manifest.rb +41 -0
- data/lib/shellfie/session.rb +111 -0
- data/lib/shellfie/session_config.rb +562 -0
- data/lib/shellfie/session_runner.rb +689 -0
- data/lib/shellfie/svg_raster_wrapper.rb +35 -0
- data/lib/shellfie/svg_renderer.rb +222 -0
- data/lib/shellfie/terminal_screen.rb +389 -0
- data/lib/shellfie/text_metrics.rb +155 -0
- data/lib/shellfie/theme_data.rb +80 -0
- data/lib/shellfie/theme_registry.rb +131 -0
- data/lib/shellfie/themes/base.rb +10 -1
- data/lib/shellfie/themes/configured.rb +61 -0
- data/lib/shellfie/themes/macos.rb +3 -1
- data/lib/shellfie/themes/ubuntu.rb +2 -1
- data/lib/shellfie/themes/windows_terminal.rb +7 -1
- data/lib/shellfie/transcript_renderer.rb +92 -0
- data/lib/shellfie/version.rb +1 -1
- data/lib/shellfie/yaml_safety.rb +147 -0
- data/lib/shellfie.rb +44 -3
- data/schema/shellfie-v1.schema.json +153 -0
- data/schema/shellfie-v2.schema.json +262 -0
- metadata +58 -20
- data/.rspec +0 -3
- data/Rakefile +0 -8
- data/examples/animation.yml +0 -33
- data/examples/colored.yml +0 -20
- data/examples/demo.gif +0 -0
- data/examples/demo.png +0 -0
- data/examples/demo_animation.yml +0 -31
- data/examples/headless.png +0 -0
- data/examples/headless.yml +0 -16
- data/examples/scrolling.yml +0 -48
- data/examples/simple.yml +0 -21
- data/examples/theme_macos.png +0 -0
- data/examples/theme_ubuntu.png +0 -0
- data/examples/theme_windows.png +0 -0
- data/shellfie.gemspec +0 -32
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "errors"
|
|
4
|
+
require "yaml"
|
|
5
|
+
|
|
6
|
+
module Shellfie
|
|
7
|
+
module YamlSafety
|
|
8
|
+
MAX_DEPTH = 100
|
|
9
|
+
MAX_NODES = 100_000
|
|
10
|
+
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def annotate_validation_error(error, documents, provenance: {})
|
|
14
|
+
if (target = validation_path(error.message)) && (origin = provenance[target])
|
|
15
|
+
path, local_path = origin
|
|
16
|
+
content = documents.assoc(path)&.last
|
|
17
|
+
if content && (location = location_for_path(content, local_path))
|
|
18
|
+
return error.class.new("#{path}:#{location[0]}:#{location[1]}: #{error.message}")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
documents.each do |path, content|
|
|
23
|
+
next unless path && content
|
|
24
|
+
|
|
25
|
+
location = validation_location(content, error.message)
|
|
26
|
+
return error.class.new("#{path}:#{location[0]}:#{location[1]}: #{error.message}") if location
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
path = documents.first&.first
|
|
30
|
+
error.class.new(path ? "#{path}: #{error.message}" : error.message)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def read_file(path, max_bytes:, label: "Configuration")
|
|
34
|
+
raise ParseError, "#{label} file not found: #{path}" unless File.file?(path)
|
|
35
|
+
|
|
36
|
+
content = File.open(path, "rb") { |file| file.read(max_bytes + 1) }
|
|
37
|
+
raise ParseError, "#{label} file is too large: #{path} (max #{max_bytes} bytes)" if content.bytesize > max_bytes
|
|
38
|
+
|
|
39
|
+
content
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def load_file(path, max_bytes:, label: "Configuration", symbolize_names: true)
|
|
43
|
+
value = YAML.safe_load(
|
|
44
|
+
read_file(path, max_bytes: max_bytes, label: label), symbolize_names: symbolize_names, aliases: true
|
|
45
|
+
)
|
|
46
|
+
validate_tree!(value)
|
|
47
|
+
rescue Psych::Exception => e
|
|
48
|
+
raise ParseError, "Invalid #{label.downcase} YAML syntax: #{e.message}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def validate_tree!(value)
|
|
52
|
+
active = {}
|
|
53
|
+
nodes = 0
|
|
54
|
+
stack = [[value, 0, false]]
|
|
55
|
+
|
|
56
|
+
until stack.empty?
|
|
57
|
+
node, depth, leaving = stack.pop
|
|
58
|
+
next unless node.is_a?(Hash) || node.is_a?(Array)
|
|
59
|
+
if leaving
|
|
60
|
+
active.delete(node.object_id)
|
|
61
|
+
next
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
raise ParseError, "YAML aliases must not contain cycles" if active[node.object_id]
|
|
65
|
+
raise ParseError, "YAML nesting is too deep (max #{MAX_DEPTH})" if depth > MAX_DEPTH
|
|
66
|
+
nodes += 1
|
|
67
|
+
raise ParseError, "YAML structure is too large (max #{MAX_NODES} collections)" if nodes > MAX_NODES
|
|
68
|
+
|
|
69
|
+
active[node.object_id] = true
|
|
70
|
+
stack << [node, depth, true]
|
|
71
|
+
children = node.is_a?(Hash) ? node.flat_map { |key, nested| [key, nested] } : node
|
|
72
|
+
children.reverse_each { |child| stack << [child, depth + 1, false] }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
value
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def validation_location(content, message)
|
|
79
|
+
locations = {}
|
|
80
|
+
collect_locations(Psych.parse(content).root, [], locations)
|
|
81
|
+
target = validation_path(message)
|
|
82
|
+
return locations[target] if target && locations[target]
|
|
83
|
+
|
|
84
|
+
locations.sort_by { |path, _location| -path.size }.each do |path, location|
|
|
85
|
+
return location if message.include?(format_path(path))
|
|
86
|
+
end
|
|
87
|
+
nil
|
|
88
|
+
rescue Psych::Exception
|
|
89
|
+
nil
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def location_for_path(content, path)
|
|
93
|
+
locations = {}
|
|
94
|
+
collect_locations(Psych.parse(content).root, [], locations)
|
|
95
|
+
locations[path] || locations[path[0...-1]]
|
|
96
|
+
rescue Psych::Exception
|
|
97
|
+
nil
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def collect_locations(node, path, locations)
|
|
101
|
+
case node
|
|
102
|
+
when Psych::Nodes::Mapping
|
|
103
|
+
node.children.each_slice(2) do |key, value|
|
|
104
|
+
next unless key.respond_to?(:value)
|
|
105
|
+
|
|
106
|
+
child_path = path + [key.value.to_sym]
|
|
107
|
+
locations[child_path] = [key.start_line + 1, key.start_column + 1]
|
|
108
|
+
collect_locations(value, child_path, locations)
|
|
109
|
+
end
|
|
110
|
+
when Psych::Nodes::Sequence
|
|
111
|
+
node.children.each_with_index { |child, index| collect_locations(child, path + [index], locations) }
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def validation_path(message)
|
|
116
|
+
if (match = /Unknown (.+?) key\(s\):\s*([^,\s]+)/.match(message))
|
|
117
|
+
prefix = match[1] == "configuration" ? [] : parse_path(match[1])
|
|
118
|
+
return prefix + [match[2].to_sym]
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
explicit = message[/\b(?:steps|outputs|frames|lines)\[\d+\](?:\.[a-z_]+)*|\b(?:terminal|render|window|font|animation|cursor|limits)\.[a-z_]+/]
|
|
122
|
+
return parse_path(explicit) if explicit
|
|
123
|
+
|
|
124
|
+
{
|
|
125
|
+
"Session config version" => [:version], "mode must" => [:mode], "Invalid theme" => [:theme],
|
|
126
|
+
"redaction" => [:redact], "requires" => [:requires], "terminal.env" => %i[terminal env],
|
|
127
|
+
"title must" => [:title], "headless must" => [:headless]
|
|
128
|
+
}.each { |fragment, path| return path if message.include?(fragment) }
|
|
129
|
+
nil
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def parse_path(value)
|
|
133
|
+
value.to_s.scan(/[A-Za-z_][A-Za-z0-9_]*|\d+/).map { |part| part.match?(/\A\d+\z/) ? part.to_i : part.to_sym }
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def format_path(path)
|
|
137
|
+
path.each_with_object(+"") do |part, result|
|
|
138
|
+
if part.is_a?(Integer)
|
|
139
|
+
result << "[#{part}]"
|
|
140
|
+
else
|
|
141
|
+
result << "." unless result.empty?
|
|
142
|
+
result << part.to_s
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
data/lib/shellfie.rb
CHANGED
|
@@ -5,9 +5,50 @@ require_relative "shellfie/errors"
|
|
|
5
5
|
require_relative "shellfie/config"
|
|
6
6
|
require_relative "shellfie/parser"
|
|
7
7
|
require_relative "shellfie/ansi_parser"
|
|
8
|
-
require_relative "shellfie/renderer"
|
|
9
|
-
require_relative "shellfie/gif_generator"
|
|
10
|
-
require_relative "shellfie/cli"
|
|
11
8
|
|
|
12
9
|
module Shellfie
|
|
10
|
+
autoload :AnimationFrameBuilder, File.expand_path("shellfie/animation_frame_builder", __dir__)
|
|
11
|
+
autoload :AnimationScrollEasing, File.expand_path("shellfie/animation_scroll_easing", __dir__)
|
|
12
|
+
autoload :AnimationTimeline, File.expand_path("shellfie/animation_timeline", __dir__)
|
|
13
|
+
autoload :CLI, File.expand_path("shellfie/cli", __dir__)
|
|
14
|
+
autoload :GifGenerator, File.expand_path("shellfie/gif_generator", __dir__)
|
|
15
|
+
autoload :GifPalette, File.expand_path("shellfie/gif_palette", __dir__)
|
|
16
|
+
autoload :ImageMagickCommandBuilder, File.expand_path("shellfie/image_magick_command_builder", __dir__)
|
|
17
|
+
autoload :RenderChromeCache, File.expand_path("shellfie/render_chrome_cache", __dir__)
|
|
18
|
+
autoload :Renderer, File.expand_path("shellfie/renderer", __dir__)
|
|
19
|
+
autoload :TerminalScreen, File.expand_path("shellfie/terminal_screen", __dir__)
|
|
20
|
+
autoload :TranscriptRenderer, File.expand_path("shellfie/transcript_renderer", __dir__)
|
|
21
|
+
autoload :Cassette, File.expand_path("shellfie/cassette", __dir__)
|
|
22
|
+
autoload :Session, File.expand_path("shellfie/session", __dir__)
|
|
23
|
+
autoload :SessionConfig, File.expand_path("shellfie/session_config", __dir__)
|
|
24
|
+
autoload :SessionRunner, File.expand_path("shellfie/session_runner", __dir__)
|
|
25
|
+
autoload :ReproducibilityManifest, File.expand_path("shellfie/reproducibility_manifest", __dir__)
|
|
26
|
+
|
|
27
|
+
class << self
|
|
28
|
+
def parse(source)
|
|
29
|
+
Parser.parse(source)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def validate(source)
|
|
33
|
+
parse(source)
|
|
34
|
+
true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def render(config_or_source, output:, animate: nil, scale: 1, shadow: true, transparent: false, format: nil)
|
|
38
|
+
config = config_or_source.is_a?(Config) ? config_or_source : parse(config_or_source)
|
|
39
|
+
animated = animate.nil? ? config.animated? : animate
|
|
40
|
+
|
|
41
|
+
if animated
|
|
42
|
+
GifGenerator.new(config).generate(output, scale: scale, shadow: shadow, transparent: transparent, format: format)
|
|
43
|
+
else
|
|
44
|
+
Renderer.new(config).render(output, scale: scale, shadow: shadow, transparent: transparent, format: format)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def inspect_config(source, scale: 1, shadow: true)
|
|
49
|
+
config = parse(source)
|
|
50
|
+
geometry = Renderer.new(config).estimate(scale: scale, shadow: shadow)
|
|
51
|
+
{ config: config.to_h, theme: config.theme, geometry: geometry, fonts: Renderer.new(config).font_info }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
13
54
|
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://raw.githubusercontent.com/ydah/shellfie/main/schema/shellfie-v1.schema.json",
|
|
4
|
+
"title": "Shellfie compose configuration",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"properties": {
|
|
8
|
+
"version": { "const": 1 },
|
|
9
|
+
"include": { "oneOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }] },
|
|
10
|
+
"include_policy": { "enum": ["allow", "root"] },
|
|
11
|
+
"theme": { "enum": ["macos", "ubuntu", "windows", "custom"] },
|
|
12
|
+
"window_theme": { "enum": ["macos", "ubuntu", "windows"] },
|
|
13
|
+
"color_scheme": { "enum": ["dracula", "one_dark", "solarized_dark", "catppuccin_mocha", null] },
|
|
14
|
+
"colors": {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"propertyNames": { "enum": ["background", "foreground", "title_bar", "title_text", "title_bar_border", "border", "selection", "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", "bright_black", "bright_red", "bright_green", "bright_yellow", "bright_blue", "bright_magenta", "bright_cyan", "bright_white"] },
|
|
17
|
+
"additionalProperties": { "type": "string" }
|
|
18
|
+
},
|
|
19
|
+
"window_decoration": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"additionalProperties": false,
|
|
22
|
+
"properties": {
|
|
23
|
+
"title_bar_height": { "type": "number", "minimum": 0 },
|
|
24
|
+
"button_size": { "type": "number", "minimum": 0 },
|
|
25
|
+
"button_spacing": { "type": "number", "minimum": 0 },
|
|
26
|
+
"button_width": { "type": "number", "minimum": 0 },
|
|
27
|
+
"corner_radius": { "type": "number", "minimum": 0 },
|
|
28
|
+
"shadow": {
|
|
29
|
+
"type": "object", "additionalProperties": false,
|
|
30
|
+
"properties": {
|
|
31
|
+
"blur": { "type": "number", "minimum": 0 }, "offset_x": { "type": "number" },
|
|
32
|
+
"offset_y": { "type": "number" }, "color": { "type": "string" }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"title": { "type": "string" },
|
|
38
|
+
"headless": { "type": "boolean" },
|
|
39
|
+
"window": {
|
|
40
|
+
"type": "object",
|
|
41
|
+
"additionalProperties": false,
|
|
42
|
+
"properties": {
|
|
43
|
+
"width": { "type": "integer", "minimum": 120 },
|
|
44
|
+
"height": { "type": ["integer", "null"], "minimum": 1 },
|
|
45
|
+
"padding": { "type": "integer", "minimum": 0, "maximum": 40 },
|
|
46
|
+
"opacity": { "type": "number", "minimum": 0, "maximum": 1 },
|
|
47
|
+
"visible_lines": { "type": ["integer", "null"], "minimum": 1 },
|
|
48
|
+
"max_lines": { "type": ["integer", "null"], "minimum": 1 },
|
|
49
|
+
"max_height": { "type": ["integer", "null"], "minimum": 1 },
|
|
50
|
+
"wrap": { "type": "boolean" },
|
|
51
|
+
"overflow": { "enum": ["clip", "wrap", "scroll"] },
|
|
52
|
+
"margin": { "type": ["integer", "null"], "minimum": 0 },
|
|
53
|
+
"exact_size": { "type": "boolean" },
|
|
54
|
+
"trim": { "type": "boolean" },
|
|
55
|
+
"tab_width": { "type": "integer", "minimum": 1 },
|
|
56
|
+
"ambiguous_width": { "enum": [1, 2] },
|
|
57
|
+
"osc_policy": { "enum": ["ignore", "preserve", "apply"] },
|
|
58
|
+
"graphics_policy": { "enum": ["ignore", "error"] },
|
|
59
|
+
"ansi_state": { "enum": ["persistent", "line"] },
|
|
60
|
+
"background_gradient": { "type": ["array", "null"], "minItems": 2, "maxItems": 2, "items": { "type": "string" } },
|
|
61
|
+
"scroll_offset": { "type": "number", "minimum": 0, "maximum": 1 }
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"font": {
|
|
65
|
+
"type": "object",
|
|
66
|
+
"additionalProperties": false,
|
|
67
|
+
"properties": {
|
|
68
|
+
"family": { "type": ["string", "null"] },
|
|
69
|
+
"size": { "type": "number", "exclusiveMinimum": 0 },
|
|
70
|
+
"line_height": { "type": "number", "exclusiveMinimum": 0 },
|
|
71
|
+
"fallback_family": { "type": ["string", "null"] },
|
|
72
|
+
"italic_family": { "type": ["string", "null"] },
|
|
73
|
+
"emoji_family": { "type": ["string", "null"] }
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"animation": {
|
|
77
|
+
"type": "object",
|
|
78
|
+
"additionalProperties": false,
|
|
79
|
+
"properties": {
|
|
80
|
+
"typing_speed": { "type": "integer", "minimum": 0, "maximum": 86400000 },
|
|
81
|
+
"command_delay": { "type": "integer", "minimum": 0, "maximum": 86400000 },
|
|
82
|
+
"cursor_blink": { "type": "boolean" },
|
|
83
|
+
"loop": { "type": "boolean" },
|
|
84
|
+
"typing_jitter": { "type": "number", "minimum": 0, "maximum": 1 },
|
|
85
|
+
"seed": { "type": "integer", "minimum": 0, "maximum": 2147483647 },
|
|
86
|
+
"typing_chunk_size": { "type": "integer", "minimum": 1 },
|
|
87
|
+
"output_delay": { "type": "integer", "minimum": 0, "maximum": 86400000 },
|
|
88
|
+
"final_delay": { "type": "integer", "minimum": 0, "maximum": 86400000 },
|
|
89
|
+
"max_frames": { "type": ["integer", "null"], "minimum": 1 },
|
|
90
|
+
"dither": { "type": "boolean" },
|
|
91
|
+
"palette": { "enum": ["global", "adaptive", "theme"] },
|
|
92
|
+
"gif_colors": { "type": "integer", "minimum": 2, "maximum": 256 },
|
|
93
|
+
"gif_optimize": { "type": "boolean" },
|
|
94
|
+
"webp_lossless": { "type": "boolean" },
|
|
95
|
+
"webp_quality": { "type": "integer", "minimum": 0, "maximum": 100 },
|
|
96
|
+
"webp_method": { "type": "integer", "minimum": 0, "maximum": 6 },
|
|
97
|
+
"webp_near_lossless": { "type": "integer", "minimum": 0, "maximum": 100 },
|
|
98
|
+
"apng_prediction": { "enum": ["none", "sub", "up", "avg", "paeth", "mixed"] },
|
|
99
|
+
"loop_count": { "type": ["integer", "null"], "minimum": 0, "maximum": 65535 },
|
|
100
|
+
"direction": { "enum": ["forward", "reverse", "ping_pong"] },
|
|
101
|
+
"loop_offset": { "type": "integer", "minimum": 0 },
|
|
102
|
+
"scroll_easing": { "enum": ["linear", "ease_in", "ease_out", "ease_in_out"] },
|
|
103
|
+
"framerate": { "type": "integer", "minimum": 1, "maximum": 120 },
|
|
104
|
+
"playback_speed": { "type": "number", "exclusiveMinimum": 0, "maximum": 100 }
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
"cursor": {
|
|
108
|
+
"type": "object", "additionalProperties": false,
|
|
109
|
+
"properties": { "style": { "enum": ["block", "bar", "underline"] }, "color": { "type": ["string", "null"] } }
|
|
110
|
+
},
|
|
111
|
+
"limits": {
|
|
112
|
+
"type": "object", "additionalProperties": false,
|
|
113
|
+
"properties": {
|
|
114
|
+
"max_lines": { "type": "integer", "minimum": 1, "maximum": 10000 },
|
|
115
|
+
"max_frames": { "type": "integer", "minimum": 1, "maximum": 500 },
|
|
116
|
+
"max_render_frames": { "type": "integer", "minimum": 1, "maximum": 2000 },
|
|
117
|
+
"max_characters": { "type": "integer", "minimum": 1, "maximum": 200000 },
|
|
118
|
+
"max_pixels": { "type": "integer", "minimum": 1, "maximum": 50000000 },
|
|
119
|
+
"max_total_pixels": { "type": "integer", "minimum": 1, "maximum": 2000000000 },
|
|
120
|
+
"max_temp_bytes": { "type": "integer", "minimum": 1, "maximum": 8000000000 }
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
"lines": { "type": "array", "maxItems": 10000, "items": { "$ref": "#/$defs/line" } },
|
|
124
|
+
"frames": { "type": "array", "maxItems": 500, "items": { "$ref": "#/$defs/frame" } }
|
|
125
|
+
},
|
|
126
|
+
"anyOf": [{ "required": ["lines"] }, { "required": ["frames"] }],
|
|
127
|
+
"$defs": {
|
|
128
|
+
"line": {
|
|
129
|
+
"type": "object",
|
|
130
|
+
"additionalProperties": false,
|
|
131
|
+
"properties": {
|
|
132
|
+
"prompt": { "type": "string" }, "command": { "type": "string" }, "output": { "type": "string" },
|
|
133
|
+
"prompt_color": { "type": "string" }, "command_color": { "type": "string" }, "output_color": { "type": "string" },
|
|
134
|
+
"selected": { "type": "boolean" }
|
|
135
|
+
},
|
|
136
|
+
"anyOf": [{ "required": ["prompt"] }, { "required": ["command"] }, { "required": ["output"] }]
|
|
137
|
+
},
|
|
138
|
+
"frame": {
|
|
139
|
+
"type": "object",
|
|
140
|
+
"additionalProperties": false,
|
|
141
|
+
"properties": {
|
|
142
|
+
"prompt": { "type": "string" }, "type": { "type": "string" }, "output": { "type": "string" },
|
|
143
|
+
"screen": { "type": "array", "items": { "type": "string" } },
|
|
144
|
+
"delay": { "type": "integer", "minimum": 0, "maximum": 86400000 }, "prompt_color": { "type": "string" },
|
|
145
|
+
"command_color": { "type": "string" }, "output_color": { "type": "string" }
|
|
146
|
+
},
|
|
147
|
+
"allOf": [
|
|
148
|
+
{ "anyOf": [{ "required": ["type"] }, { "required": ["output"] }, { "required": ["screen"] }, { "required": ["delay"] }] },
|
|
149
|
+
{ "if": { "required": ["prompt"] }, "then": { "required": ["type"] } }
|
|
150
|
+
]
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://raw.githubusercontent.com/ydah/shellfie/main/schema/shellfie-v2.schema.json",
|
|
4
|
+
"title": "Shellfie executable session",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["version"],
|
|
8
|
+
"anyOf": [{ "required": ["steps"] }, { "required": ["include"] }],
|
|
9
|
+
"dependentRequired": { "include_policy": ["include"] },
|
|
10
|
+
"properties": {
|
|
11
|
+
"version": { "const": 2 },
|
|
12
|
+
"include": { "oneOf": [{ "type": "string" }, { "type": "array", "minItems": 1, "items": { "type": "string" } }] },
|
|
13
|
+
"include_policy": { "enum": ["allow", "root"] },
|
|
14
|
+
"mode": { "enum": ["run", "replay"] },
|
|
15
|
+
"title": { "type": "string" },
|
|
16
|
+
"theme": { "enum": ["macos", "ubuntu", "windows", "custom"] },
|
|
17
|
+
"vars": {
|
|
18
|
+
"type": "object", "maxProperties": 100,
|
|
19
|
+
"propertyNames": { "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" },
|
|
20
|
+
"additionalProperties": { "type": ["string", "number", "boolean", "null"] }
|
|
21
|
+
},
|
|
22
|
+
"step_sets": {
|
|
23
|
+
"type": "object", "maxProperties": 100,
|
|
24
|
+
"propertyNames": { "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" },
|
|
25
|
+
"additionalProperties": {
|
|
26
|
+
"type": "array", "items": { "oneOf": [{ "$ref": "#/$defs/step" }, { "$ref": "#/$defs/step_use" }] }
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"terminal": {
|
|
30
|
+
"type": "object",
|
|
31
|
+
"additionalProperties": false,
|
|
32
|
+
"properties": {
|
|
33
|
+
"shell": { "type": "string" }, "columns": { "type": "integer", "minimum": 1, "maximum": 500 },
|
|
34
|
+
"rows": { "type": "integer", "minimum": 1, "maximum": 200 }, "cwd": { "type": "string" },
|
|
35
|
+
"cwd_policy": { "enum": ["allow", "root"] },
|
|
36
|
+
"env": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"propertyNames": { "allOf": [{ "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" }, { "not": { "const": "PS1" } }] },
|
|
39
|
+
"additionalProperties": { "type": ["string", "null"] }
|
|
40
|
+
},
|
|
41
|
+
"env_allowlist": {
|
|
42
|
+
"type": ["array", "null"], "uniqueItems": true,
|
|
43
|
+
"items": { "type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" }
|
|
44
|
+
},
|
|
45
|
+
"timeout": { "$ref": "#/$defs/duration" },
|
|
46
|
+
"total_timeout": { "oneOf": [{ "$ref": "#/$defs/duration" }, { "type": "null" }] },
|
|
47
|
+
"prompt": { "type": "string", "pattern": "\\S" }
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"requires": { "type": "array", "items": { "type": "string", "pattern": "^[A-Za-z0-9_.+-]+$" } },
|
|
51
|
+
"steps": {
|
|
52
|
+
"type": "array", "maxItems": 10000,
|
|
53
|
+
"items": { "oneOf": [{ "$ref": "#/$defs/step" }, { "$ref": "#/$defs/step_use" }] },
|
|
54
|
+
"contains": { "type": "object", "required": ["capture"] }, "minContains": 0, "maxContains": 100
|
|
55
|
+
},
|
|
56
|
+
"outputs": { "type": "array", "items": { "$ref": "#/$defs/output" } },
|
|
57
|
+
"render": {
|
|
58
|
+
"type": "object", "additionalProperties": false,
|
|
59
|
+
"properties": {
|
|
60
|
+
"window": {
|
|
61
|
+
"$ref": "#/$defs/window"
|
|
62
|
+
},
|
|
63
|
+
"font": {
|
|
64
|
+
"$ref": "#/$defs/font"
|
|
65
|
+
},
|
|
66
|
+
"animation": {
|
|
67
|
+
"$ref": "#/$defs/animation"
|
|
68
|
+
},
|
|
69
|
+
"headless": { "type": "boolean" }
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"redact": { "type": "array", "maxItems": 100, "items": { "type": "string", "maxLength": 512 } }
|
|
73
|
+
},
|
|
74
|
+
"$defs": {
|
|
75
|
+
"duration": {
|
|
76
|
+
"oneOf": [
|
|
77
|
+
{ "type": "number", "exclusiveMinimum": 0, "maximum": 86400 },
|
|
78
|
+
{
|
|
79
|
+
"type": "string",
|
|
80
|
+
"pattern": "^(?=.{1,32}s?$)0*(?:0\\.[0-9]*[1-9][0-9]*|(?:[1-9][0-9]{0,3}|[1-7][0-9]{4}|8[0-5][0-9]{3}|86[0-3][0-9]{2})(?:\\.[0-9]+)?|86400(?:\\.0+)?)s?$"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"type": "string",
|
|
84
|
+
"pattern": "^(?=.{1,32}ms$)0*(?:0\\.[0-9]*[1-9][0-9]*|(?:[1-9][0-9]{0,6}|[1-7][0-9]{7}|8[0-5][0-9]{6}|86[0-3][0-9]{5})(?:\\.[0-9]+)?|86400000(?:\\.0+)?)ms$"
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
"window": {
|
|
89
|
+
"type": "object",
|
|
90
|
+
"additionalProperties": false,
|
|
91
|
+
"properties": {
|
|
92
|
+
"width": { "type": "integer", "minimum": 120 },
|
|
93
|
+
"height": { "type": ["integer", "null"], "minimum": 1 },
|
|
94
|
+
"padding": { "type": "integer", "minimum": 0, "maximum": 40 },
|
|
95
|
+
"opacity": { "type": "number", "minimum": 0, "maximum": 1 },
|
|
96
|
+
"visible_lines": { "type": ["integer", "null"], "minimum": 1 },
|
|
97
|
+
"max_lines": { "type": ["integer", "null"], "minimum": 1 },
|
|
98
|
+
"max_height": { "type": ["integer", "null"], "minimum": 1 },
|
|
99
|
+
"wrap": { "type": "boolean" },
|
|
100
|
+
"overflow": { "enum": ["clip", "wrap", "scroll"] },
|
|
101
|
+
"margin": { "type": ["integer", "null"], "minimum": 0 },
|
|
102
|
+
"exact_size": { "type": "boolean" },
|
|
103
|
+
"trim": { "type": "boolean" },
|
|
104
|
+
"tab_width": { "type": "integer", "minimum": 1 },
|
|
105
|
+
"ambiguous_width": { "enum": [1, 2] },
|
|
106
|
+
"osc_policy": { "enum": ["ignore", "preserve", "apply"] },
|
|
107
|
+
"graphics_policy": { "enum": ["ignore", "error"] },
|
|
108
|
+
"ansi_state": { "enum": ["persistent", "line"] },
|
|
109
|
+
"background_gradient": { "type": ["array", "null"], "minItems": 2, "maxItems": 2, "items": { "type": "string" } },
|
|
110
|
+
"scroll_offset": { "type": "number", "minimum": 0, "maximum": 1 }
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
"font": {
|
|
114
|
+
"type": "object",
|
|
115
|
+
"additionalProperties": false,
|
|
116
|
+
"properties": {
|
|
117
|
+
"family": { "type": ["string", "null"] },
|
|
118
|
+
"size": { "type": "number", "exclusiveMinimum": 0 },
|
|
119
|
+
"line_height": { "type": "number", "exclusiveMinimum": 0 },
|
|
120
|
+
"fallback_family": { "type": ["string", "null"] },
|
|
121
|
+
"italic_family": { "type": ["string", "null"] },
|
|
122
|
+
"emoji_family": { "type": ["string", "null"] }
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
"animation": {
|
|
126
|
+
"type": "object",
|
|
127
|
+
"additionalProperties": false,
|
|
128
|
+
"properties": {
|
|
129
|
+
"typing_speed": { "type": "integer", "minimum": 0, "maximum": 86400000 },
|
|
130
|
+
"command_delay": { "type": "integer", "minimum": 0, "maximum": 86400000 },
|
|
131
|
+
"cursor_blink": { "type": "boolean" },
|
|
132
|
+
"loop": { "type": "boolean" },
|
|
133
|
+
"typing_jitter": { "type": "number", "minimum": 0, "maximum": 1 },
|
|
134
|
+
"seed": { "type": "integer", "minimum": 0, "maximum": 2147483647 },
|
|
135
|
+
"typing_chunk_size": { "type": "integer", "minimum": 1 },
|
|
136
|
+
"output_delay": { "type": "integer", "minimum": 0, "maximum": 86400000 },
|
|
137
|
+
"final_delay": { "type": "integer", "minimum": 0, "maximum": 86400000 },
|
|
138
|
+
"max_frames": { "type": ["integer", "null"], "minimum": 1 },
|
|
139
|
+
"dither": { "type": "boolean" },
|
|
140
|
+
"palette": { "enum": ["global", "adaptive", "theme"] },
|
|
141
|
+
"gif_colors": { "type": "integer", "minimum": 2, "maximum": 256 },
|
|
142
|
+
"gif_optimize": { "type": "boolean" },
|
|
143
|
+
"webp_lossless": { "type": "boolean" },
|
|
144
|
+
"webp_quality": { "type": "integer", "minimum": 0, "maximum": 100 },
|
|
145
|
+
"webp_method": { "type": "integer", "minimum": 0, "maximum": 6 },
|
|
146
|
+
"webp_near_lossless": { "type": "integer", "minimum": 0, "maximum": 100 },
|
|
147
|
+
"apng_prediction": { "enum": ["none", "sub", "up", "avg", "paeth", "mixed"] },
|
|
148
|
+
"loop_count": { "type": ["integer", "null"], "minimum": 0, "maximum": 65535 },
|
|
149
|
+
"direction": { "enum": ["forward", "reverse", "ping_pong"] },
|
|
150
|
+
"loop_offset": { "type": "integer", "minimum": 0 },
|
|
151
|
+
"scroll_easing": { "enum": ["linear", "ease_in", "ease_out", "ease_in_out"] },
|
|
152
|
+
"framerate": { "type": "integer", "minimum": 1, "maximum": 120 },
|
|
153
|
+
"playback_speed": { "type": "number", "exclusiveMinimum": 0, "maximum": 100 }
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
"step": {
|
|
157
|
+
"oneOf": [
|
|
158
|
+
{ "type": "string", "enum": ["hide", "show"] },
|
|
159
|
+
{
|
|
160
|
+
"type": "object",
|
|
161
|
+
"additionalProperties": false,
|
|
162
|
+
"properties": {
|
|
163
|
+
"run": { "type": "string" }, "type": { "type": "string" }, "key": { "type": "string" },
|
|
164
|
+
"sleep": { "$ref": "#/$defs/duration" }, "wait": { "$ref": "#/$defs/wait" },
|
|
165
|
+
"expect": { "$ref": "#/$defs/expect" }, "capture": { "type": "string" },
|
|
166
|
+
"hide": { "const": true }, "show": { "const": true }, "visibility": { "enum": ["visible", "hidden"] },
|
|
167
|
+
"timeout": { "$ref": "#/$defs/duration" },
|
|
168
|
+
"speed": { "type": "string", "maxLength": 32, "pattern": "^0*(?:[1-9][0-9]{0,2}(?:\\.[0-9]+)?|1000(?:\\.0+)?)cps$" },
|
|
169
|
+
"count": { "type": "integer", "minimum": 1, "maximum": 10000 },
|
|
170
|
+
"async": { "type": "boolean" }, "cwd": { "type": "string" },
|
|
171
|
+
"delay": { "$ref": "#/$defs/duration" },
|
|
172
|
+
"repeat": { "type": "integer", "minimum": 1, "maximum": 10000 },
|
|
173
|
+
"if": { "$ref": "#/$defs/condition" }
|
|
174
|
+
},
|
|
175
|
+
"oneOf": [
|
|
176
|
+
{ "required": ["run"] }, { "required": ["type"] }, { "required": ["key"] },
|
|
177
|
+
{ "required": ["sleep"] }, { "required": ["wait"] }, { "required": ["expect"] },
|
|
178
|
+
{ "required": ["capture"] }, { "required": ["hide"] }, { "required": ["show"] }
|
|
179
|
+
],
|
|
180
|
+
"dependentRequired": { "visibility": ["run"], "speed": ["type"], "count": ["key"], "cwd": ["run"], "delay": ["key"] },
|
|
181
|
+
"allOf": [
|
|
182
|
+
{ "if": { "required": ["async"] }, "then": { "anyOf": [{ "required": ["run"] }, { "required": ["key"] }] } },
|
|
183
|
+
{ "if": { "required": ["timeout"] }, "then": { "anyOf": [{ "required": ["run"] }, { "required": ["key"] }, { "required": ["wait"] }] } },
|
|
184
|
+
{ "not": { "required": ["run", "async", "visibility"], "properties": { "async": { "const": true }, "visibility": { "const": "hidden" } } } }
|
|
185
|
+
]
|
|
186
|
+
}
|
|
187
|
+
]
|
|
188
|
+
},
|
|
189
|
+
"step_use": {
|
|
190
|
+
"type": "object", "required": ["use"], "additionalProperties": false,
|
|
191
|
+
"properties": {
|
|
192
|
+
"use": { "type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" },
|
|
193
|
+
"repeat": { "type": "integer", "minimum": 1, "maximum": 10000 },
|
|
194
|
+
"if": { "$ref": "#/$defs/condition" }
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
"condition": {
|
|
198
|
+
"type": "object", "minProperties": 1, "additionalProperties": false,
|
|
199
|
+
"properties": {
|
|
200
|
+
"os": {
|
|
201
|
+
"oneOf": [
|
|
202
|
+
{ "enum": ["macos", "linux", "windows"] },
|
|
203
|
+
{ "type": "array", "minItems": 1, "uniqueItems": true, "items": { "enum": ["macos", "linux", "windows"] } }
|
|
204
|
+
]
|
|
205
|
+
},
|
|
206
|
+
"shell": { "type": "string" },
|
|
207
|
+
"ruby": { "type": "string" },
|
|
208
|
+
"env": { "type": "object", "additionalProperties": { "type": ["string", "null"] } }
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
"wait": {
|
|
212
|
+
"oneOf": [
|
|
213
|
+
{ "type": "string", "maxLength": 512 },
|
|
214
|
+
{
|
|
215
|
+
"type": "object",
|
|
216
|
+
"additionalProperties": false,
|
|
217
|
+
"properties": {
|
|
218
|
+
"screen": { "type": "string", "maxLength": 512 }, "line": { "type": "string", "maxLength": 512 },
|
|
219
|
+
"prompt": { "const": true },
|
|
220
|
+
"stable": { "$ref": "#/$defs/duration" }, "exit": { "const": true },
|
|
221
|
+
"timeout": { "$ref": "#/$defs/duration" }
|
|
222
|
+
},
|
|
223
|
+
"oneOf": [
|
|
224
|
+
{ "required": ["screen"] }, { "required": ["line"] }, { "required": ["prompt"] },
|
|
225
|
+
{ "required": ["stable"] }, { "required": ["exit"] }
|
|
226
|
+
]
|
|
227
|
+
}
|
|
228
|
+
]
|
|
229
|
+
},
|
|
230
|
+
"expect": {
|
|
231
|
+
"oneOf": [
|
|
232
|
+
{ "type": "string" },
|
|
233
|
+
{
|
|
234
|
+
"type": "object",
|
|
235
|
+
"additionalProperties": false,
|
|
236
|
+
"properties": {
|
|
237
|
+
"screen_contains": { "type": "string" }, "screen": { "type": "string", "maxLength": 512 },
|
|
238
|
+
"line": { "type": "string", "maxLength": 512 },
|
|
239
|
+
"exit_status": { "type": "integer", "minimum": 0, "maximum": 255 },
|
|
240
|
+
"cursor_row": { "type": "integer", "minimum": 0 },
|
|
241
|
+
"cursor_column": { "type": "integer", "minimum": 0 },
|
|
242
|
+
"golden": { "type": "string" },
|
|
243
|
+
"elapsed_under": { "$ref": "#/$defs/duration" }, "elapsed_over": { "$ref": "#/$defs/duration" }
|
|
244
|
+
},
|
|
245
|
+
"minProperties": 1
|
|
246
|
+
}
|
|
247
|
+
]
|
|
248
|
+
},
|
|
249
|
+
"output": {
|
|
250
|
+
"type": "object",
|
|
251
|
+
"required": ["path"],
|
|
252
|
+
"additionalProperties": false,
|
|
253
|
+
"properties": {
|
|
254
|
+
"path": { "type": "string" },
|
|
255
|
+
"format": { "enum": ["png", "gif", "svg", "svg-raster", "webp", "apng", "mp4", "webm", "png-sequence", "html", "txt", "ansi", "json", "asciicast", "cast"] },
|
|
256
|
+
"animate": { "type": "boolean" }, "scale": { "type": "integer", "minimum": 1, "maximum": 3 },
|
|
257
|
+
"shadow": { "type": "boolean" }, "transparent": { "type": "boolean" },
|
|
258
|
+
"capture": { "type": "string" }
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|