ace-demo 0.25.11 → 0.26.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 +6 -0
- data/lib/ace/demo/molecules/runtime_directive_executor.rb +142 -0
- data/lib/ace/demo/organisms/demo_recorder.rb +3 -3
- data/lib/ace/demo/version.rb +1 -1
- data/lib/ace/demo.rb +2 -2
- metadata +17 -3
- data/lib/ace/demo/molecules/tmux_directive_executor.rb +0 -80
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 58391984e07317e02abd29f0fe8153841f4d616aac0849ea916b020b2c8492c3
|
|
4
|
+
data.tar.gz: 48f1b66626923433238798ca500cd71143b5b05df3a6e6ae5163f6d83d707ce7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9cdcfb904e837ace1001c4960f80150ffb957a406428a7ee82f0f21a8851b7678e9322f8e23ea4f1ad56ff86b0aa492494978589f162e8f08026f40d00797ef5
|
|
7
|
+
data.tar.gz: d76d42482f636e3de108eb2f6f372f6c930730b4b9bf34d416580253813f0c8e649a26debb7508ced3f569c87b3c75cb2eb164137e905266fffd4e3225e92312
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.26.0] - 2026-10-04
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Demo terminal directives run through the `ace-runtime` contract (`TmuxDirectiveExecutor` replaced by `RuntimeDirectiveExecutor`): the four lifecycle wait conditions and send directives work on both adapters with identical semantics, with per-directive `runtime:` selection (directive > `ACE_RUNTIME` > configured > detection). `attach`/`detach` stay tmux-local and raise an explicit error under any other runtime.
|
|
14
|
+
- New dependency: `ace-runtime` (~> 0.1); `ace-tmux` is kept for the tmux adapter and detach.
|
|
15
|
+
|
|
10
16
|
## [0.25.11] - 2026-10-02
|
|
11
17
|
|
|
12
18
|
### Technical
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ace/runtime"
|
|
4
|
+
require "ace/tmux"
|
|
5
|
+
require "shellwords"
|
|
6
|
+
|
|
7
|
+
module Ace
|
|
8
|
+
module Demo
|
|
9
|
+
module Molecules
|
|
10
|
+
# Executes demo terminal directives through the runtime contract.
|
|
11
|
+
# wait/send resolve via the selected runtime adapter; attach/detach
|
|
12
|
+
# stay tmux-local human-facing operations (k86.0 validation default).
|
|
13
|
+
# The YAML envelope key remains "tmux" for tape compatibility of the
|
|
14
|
+
# directive shape itself; wait/send never touch tmux APIs directly.
|
|
15
|
+
class RuntimeDirectiveExecutor
|
|
16
|
+
LIFECYCLE_CONDITIONS = Ace::Runtime::Atoms::SendContract::LIFECYCLE_CONDITIONS
|
|
17
|
+
WINDOW_CONDITIONS = %w[window-exists window-active].freeze
|
|
18
|
+
DEFAULT_TIMEOUT = 10.0
|
|
19
|
+
|
|
20
|
+
def initialize(runtime: nil, env: ENV, tmux_executor: nil)
|
|
21
|
+
@runtime_override = runtime
|
|
22
|
+
@env = env || ENV
|
|
23
|
+
@tmux_executor = tmux_executor
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def execute(command, env = nil)
|
|
27
|
+
directive_env = env || @env
|
|
28
|
+
directive = command.fetch("tmux")
|
|
29
|
+
action = directive.fetch("action")
|
|
30
|
+
|
|
31
|
+
case action
|
|
32
|
+
when "attach"
|
|
33
|
+
ensure_tmux_local!(directive, directive_env, action)
|
|
34
|
+
session = directive.fetch("session")
|
|
35
|
+
{shell_command: [tmux_binary, "attach-session", "-t", session].map { |part| Shellwords.escape(part) }.join(" ")}
|
|
36
|
+
when "detach"
|
|
37
|
+
ensure_tmux_local!(directive, directive_env, action)
|
|
38
|
+
session = directive.fetch("session")
|
|
39
|
+
tmux_executor.run([tmux_binary, "detach-client", "-s", session])
|
|
40
|
+
nil
|
|
41
|
+
when "wait"
|
|
42
|
+
wait_directive(directive, directive_env)
|
|
43
|
+
nil
|
|
44
|
+
when "send"
|
|
45
|
+
send_directive(directive, directive_env)
|
|
46
|
+
nil
|
|
47
|
+
else
|
|
48
|
+
raise ArgumentError, "Unsupported tmux action '#{action}'"
|
|
49
|
+
end
|
|
50
|
+
rescue KeyError => e
|
|
51
|
+
raise ArgumentError, "Invalid tmux directive: missing #{e.key}"
|
|
52
|
+
rescue Ace::Runtime::Error => e
|
|
53
|
+
raise Ace::Demo::Error, e.message
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
attr_reader :env, :runtime_override
|
|
61
|
+
|
|
62
|
+
# The adapter resolves per execute() call: the per-directive
|
|
63
|
+
# `runtime:` key and the caller environment are selection inputs,
|
|
64
|
+
# so a memoized adapter could route a later directive to the
|
|
65
|
+
# wrong runtime.
|
|
66
|
+
def adapter(directive, directive_env)
|
|
67
|
+
Ace::Runtime::Molecules::RuntimeSelector.new(
|
|
68
|
+
config: Ace::Runtime.config,
|
|
69
|
+
env: directive_env
|
|
70
|
+
).resolve(explicit: directive_runtime(directive))
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def wait_directive(directive, directive_env)
|
|
74
|
+
condition = directive.fetch("for")
|
|
75
|
+
unless LIFECYCLE_CONDITIONS.include?(condition)
|
|
76
|
+
raise ArgumentError, "wait condition must be one of: #{LIFECYCLE_CONDITIONS.join(', ')}"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
target = if WINDOW_CONDITIONS.include?(condition)
|
|
80
|
+
required_target(directive, "window")
|
|
81
|
+
else
|
|
82
|
+
required_target(directive, "pane")
|
|
83
|
+
end
|
|
84
|
+
adapter(directive, directive_env).wait_lifecycle(
|
|
85
|
+
condition: condition,
|
|
86
|
+
target: target,
|
|
87
|
+
timeout: directive.fetch("timeout", DEFAULT_TIMEOUT)
|
|
88
|
+
)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def send_directive(directive, directive_env)
|
|
92
|
+
pane = required_target(directive, "pane")
|
|
93
|
+
runtime_adapter = adapter(directive, directive_env)
|
|
94
|
+
if directive["command"]
|
|
95
|
+
runtime_adapter.send_command(pane: pane, command: directive["command"])
|
|
96
|
+
else
|
|
97
|
+
runtime_adapter.send_keys(pane: pane, keys: [directive.fetch("key")])
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def required_target(directive, kind)
|
|
102
|
+
target = directive[kind].to_s.strip
|
|
103
|
+
raise ArgumentError, "Invalid tmux directive: missing #{kind}" if target.empty?
|
|
104
|
+
|
|
105
|
+
target
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Selection precedence mirrors the contract selector: per-directive
|
|
109
|
+
# runtime > executor runtime > ACE_RUNTIME > configured runtime >
|
|
110
|
+
# detection. The name form is needed separately for the tmux-local
|
|
111
|
+
# guard, which must not resolve an adapter at all.
|
|
112
|
+
def selected_runtime_name(directive, directive_env)
|
|
113
|
+
directive_runtime(directive) ||
|
|
114
|
+
directive_env["ACE_RUNTIME"] ||
|
|
115
|
+
Ace::Runtime.config["runtime"] ||
|
|
116
|
+
Ace::Runtime.detect(env: directive_env)&.to_s ||
|
|
117
|
+
""
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def directive_runtime(directive)
|
|
121
|
+
explicit = (directive["runtime"] || runtime_override).to_s.strip
|
|
122
|
+
explicit.empty? ? nil : explicit
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def ensure_tmux_local!(directive, directive_env, action)
|
|
126
|
+
selected = selected_runtime_name(directive, directive_env)
|
|
127
|
+
return if selected.empty? || selected == "tmux"
|
|
128
|
+
|
|
129
|
+
raise Ace::Demo::Error, "#{action} is tmux-local and unsupported under the '#{selected}' runtime"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def tmux_executor
|
|
133
|
+
@tmux_executor_instance ||= @tmux_executor || Ace::Tmux::Molecules::TmuxExecutor.new
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def tmux_binary
|
|
137
|
+
"tmux"
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -19,7 +19,7 @@ module Ace
|
|
|
19
19
|
yaml_compiler: Atoms::VhsTapeCompiler,
|
|
20
20
|
asciinema_tape_compiler: Atoms::AsciinemaTapeCompiler,
|
|
21
21
|
media_retimer: Molecules::MediaRetimer.new,
|
|
22
|
-
|
|
22
|
+
directive_executor: Molecules::RuntimeDirectiveExecutor.new,
|
|
23
23
|
sandbox_builder: Molecules::DemoSandboxBuilder.new,
|
|
24
24
|
teardown_executor: Molecules::DemoTeardownExecutor.new,
|
|
25
25
|
output_dir: Demo.config["output_dir"],
|
|
@@ -38,7 +38,7 @@ module Ace
|
|
|
38
38
|
@yaml_compiler = yaml_compiler
|
|
39
39
|
@asciinema_tape_compiler = asciinema_tape_compiler
|
|
40
40
|
@media_retimer = media_retimer
|
|
41
|
-
@
|
|
41
|
+
@directive_executor = directive_executor
|
|
42
42
|
@sandbox_builder = sandbox_builder
|
|
43
43
|
@teardown_executor = teardown_executor
|
|
44
44
|
@output_dir = output_dir || ".ace-local/demo"
|
|
@@ -268,7 +268,7 @@ module Ace
|
|
|
268
268
|
write_io.flush
|
|
269
269
|
nil
|
|
270
270
|
when :tmux
|
|
271
|
-
@
|
|
271
|
+
@directive_executor.execute({"tmux" => command.fetch(:directive)}, env)
|
|
272
272
|
end
|
|
273
273
|
end
|
|
274
274
|
|
data/lib/ace/demo/version.rb
CHANGED
data/lib/ace/demo.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "fileutils"
|
|
4
4
|
require "ace/support/config"
|
|
5
|
-
require "ace/
|
|
5
|
+
require "ace/runtime"
|
|
6
6
|
|
|
7
7
|
require_relative "demo/version"
|
|
8
8
|
require_relative "demo/atoms/vhs_command_builder"
|
|
@@ -41,7 +41,7 @@ require_relative "demo/molecules/media_retimer"
|
|
|
41
41
|
require_relative "demo/molecules/demo_comment_poster"
|
|
42
42
|
require_relative "demo/molecules/verification_report_writer"
|
|
43
43
|
require_relative "demo/molecules/recording_manifest_writer"
|
|
44
|
-
require_relative "demo/molecules/
|
|
44
|
+
require_relative "demo/molecules/runtime_directive_executor"
|
|
45
45
|
require_relative "demo/organisms/demo_recorder"
|
|
46
46
|
require_relative "demo/organisms/demo_attacher"
|
|
47
47
|
require_relative "demo/organisms/tape_creator"
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ace-demo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.26.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michal Czyz
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-10-
|
|
10
|
+
date: 2026-10-04 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: ace-support-cli
|
|
@@ -65,6 +65,20 @@ dependencies:
|
|
|
65
65
|
- - "~>"
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
67
|
version: '0.14'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: ace-runtime
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0.1'
|
|
75
|
+
type: :runtime
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0.1'
|
|
68
82
|
- !ruby/object:Gem::Dependency
|
|
69
83
|
name: ace-tmux
|
|
70
84
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -200,10 +214,10 @@ files:
|
|
|
200
214
|
- lib/ace/demo/molecules/inline_recorder.rb
|
|
201
215
|
- lib/ace/demo/molecules/media_retimer.rb
|
|
202
216
|
- lib/ace/demo/molecules/recording_manifest_writer.rb
|
|
217
|
+
- lib/ace/demo/molecules/runtime_directive_executor.rb
|
|
203
218
|
- lib/ace/demo/molecules/tape_resolver.rb
|
|
204
219
|
- lib/ace/demo/molecules/tape_scanner.rb
|
|
205
220
|
- lib/ace/demo/molecules/tape_writer.rb
|
|
206
|
-
- lib/ace/demo/molecules/tmux_directive_executor.rb
|
|
207
221
|
- lib/ace/demo/molecules/verification_report_writer.rb
|
|
208
222
|
- lib/ace/demo/molecules/vhs_executor.rb
|
|
209
223
|
- lib/ace/demo/organisms/demo_attacher.rb
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "shellwords"
|
|
4
|
-
|
|
5
|
-
module Ace
|
|
6
|
-
module Demo
|
|
7
|
-
module Molecules
|
|
8
|
-
class TmuxDirectiveExecutor
|
|
9
|
-
def initialize(control_surface: nil, executor: nil, tmux: "tmux")
|
|
10
|
-
@control_surface = control_surface
|
|
11
|
-
@executor = executor || Ace::Tmux::Molecules::TmuxExecutor.new
|
|
12
|
-
@tmux = tmux
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def execute(command, env = nil)
|
|
16
|
-
directive = command.fetch("tmux")
|
|
17
|
-
action = directive.fetch("action")
|
|
18
|
-
surface = control_surface_for(env: env)
|
|
19
|
-
|
|
20
|
-
case action
|
|
21
|
-
when "attach"
|
|
22
|
-
session = directive.fetch("session")
|
|
23
|
-
{shell_command: [tmux, "attach-session", "-t", session].map { |part| Shellwords.escape(part) }.join(" ")}
|
|
24
|
-
when "detach"
|
|
25
|
-
surface.detach_session(session: directive["session"])
|
|
26
|
-
nil
|
|
27
|
-
when "wait"
|
|
28
|
-
surface.wait_for_condition(
|
|
29
|
-
condition: directive.fetch("for"),
|
|
30
|
-
session: directive["session"],
|
|
31
|
-
window: directive["window"],
|
|
32
|
-
pane: directive["pane"],
|
|
33
|
-
pattern: directive["pattern"],
|
|
34
|
-
timeout: directive.fetch("timeout", Ace::Tmux::Organisms::ControlSurface::DEFAULT_TIMEOUT)
|
|
35
|
-
)
|
|
36
|
-
nil
|
|
37
|
-
when "send"
|
|
38
|
-
if directive["command"]
|
|
39
|
-
surface.send_command(
|
|
40
|
-
session: directive["session"],
|
|
41
|
-
window: directive["window"],
|
|
42
|
-
pane: directive["pane"],
|
|
43
|
-
command: directive["command"]
|
|
44
|
-
)
|
|
45
|
-
else
|
|
46
|
-
surface.send_key(
|
|
47
|
-
session: directive["session"],
|
|
48
|
-
window: directive["window"],
|
|
49
|
-
pane: directive["pane"],
|
|
50
|
-
key: directive.fetch("key")
|
|
51
|
-
)
|
|
52
|
-
end
|
|
53
|
-
nil
|
|
54
|
-
else
|
|
55
|
-
raise ArgumentError, "Unsupported tmux action '#{action}'"
|
|
56
|
-
end
|
|
57
|
-
rescue KeyError => e
|
|
58
|
-
raise ArgumentError, "Invalid tmux directive: missing #{e.key}"
|
|
59
|
-
rescue Ace::Tmux::Error => e
|
|
60
|
-
raise Ace::Demo::Error, e.message
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
private
|
|
64
|
-
|
|
65
|
-
attr_reader :control_surface, :executor, :tmux
|
|
66
|
-
|
|
67
|
-
def control_surface_for(env:)
|
|
68
|
-
return control_surface if control_surface
|
|
69
|
-
|
|
70
|
-
resolver = Ace::Tmux::Molecules::RuntimeTargetResolver.new(
|
|
71
|
-
executor: executor,
|
|
72
|
-
tmux: tmux,
|
|
73
|
-
env: env || ENV
|
|
74
|
-
)
|
|
75
|
-
Ace::Tmux::Organisms::ControlSurface.new(executor: executor, resolver: resolver, tmux: tmux)
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
end
|