cline-rb 1.0.0 → 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 +6 -0
- data/README.md +37 -0
- data/lib/cline/version.rb +1 -1
- data/spec/cline_test/cli_stub.rb +209 -0
- data/spec/cline_test/stubs/cline +211 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 61431cfcf1fb2275838e3781f564c4697ae093a2605ebd8c2dd579ce10326939
|
|
4
|
+
data.tar.gz: 1604c7050d3d0e21a8d716207f4a43311891bf9205c6372b33df43a971070821
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d675729aaf7d8dc1c6f88164899545cc9e321c1ffcb76f3065fa19f7bffcceeb9a87f7c20de9e1612e08f38431c0b66b82d76ad7b22ab0f0cd0f522de0036063
|
|
7
|
+
data.tar.gz: 23b351b69ccdd74b03e1ced53a7330f813e8eb682051056e4e7d110957f8bc1cca08b751a6047c6b3c4a4d5d97dccf408ab21d67ba35f256031756528cc426be
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
# [v1.1.0](https://github.com/Muriel-Salvan/cline-rb/compare/v1.0.0...v1.1.0) (2026-07-01 13:34:01)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* [[feature] feat: include CliStub spec files in gem and document usage in README](https://github.com/Muriel-Salvan/cline-rb/commit/6d8103fc559194396cd50508728625c82e8a3f19)
|
|
6
|
+
|
|
1
7
|
# [v0.0.1](https://github.com/Muriel-Salvan/cline-rb/compare/...v0.0.1) (2026-07-01 12:59:01)
|
|
2
8
|
|
|
3
9
|
### Patches
|
data/README.md
CHANGED
|
@@ -103,6 +103,7 @@ Designed as a Ruby library (not a standalone CLI), **cline-rb** lets you build a
|
|
|
103
103
|
- [Install dependencies](#install-dependencies)
|
|
104
104
|
- [Project structure](#project-structure)
|
|
105
105
|
- [Running tests](#running-tests)
|
|
106
|
+
- [Using the Cline CLI stub in other project's test cases](#using-the-cline-cli-stub-in-other-projects-test-cases)
|
|
106
107
|
- [Code linting](#code-linting)
|
|
107
108
|
- [Code coverage](#code-coverage)
|
|
108
109
|
- [Building the gem](#building-the-gem)
|
|
@@ -1040,6 +1041,42 @@ TEST_DEBUG=1 bundle exec rspec
|
|
|
1040
1041
|
|
|
1041
1042
|
The `.rspec` file configures `--color` and `--require spec_helper` by default.
|
|
1042
1043
|
|
|
1044
|
+
### Using the Cline CLI stub in other project's test cases
|
|
1045
|
+
|
|
1046
|
+
The `cline-rb` gem ships with a `CliStub` class that external projects can use to mock the Cline CLI in their own unit tests. It intercepts `PTY.spawn` calls and replaces them with a lightweight stub that simulates Cline CLI behaviour (stdout, sessions, tasks, logs, exit codes, etc.).
|
|
1047
|
+
|
|
1048
|
+
Example usage:
|
|
1049
|
+
|
|
1050
|
+
```ruby
|
|
1051
|
+
require "#{Gem.loaded_specs['cline-rb'].full_gem_path}/spec/cline_test/cli_stub"
|
|
1052
|
+
|
|
1053
|
+
it 'tests something in my project' do
|
|
1054
|
+
# Setup the Cline CLI stub
|
|
1055
|
+
cli_stub = ClineTest::CliStub.new
|
|
1056
|
+
cli_stub.mock_commands(
|
|
1057
|
+
{
|
|
1058
|
+
log: {},
|
|
1059
|
+
session: {
|
|
1060
|
+
messages: [
|
|
1061
|
+
{
|
|
1062
|
+
ts: 100,
|
|
1063
|
+
role: 'assistant',
|
|
1064
|
+
content: [{ type: 'text', text: 'Assistant Output' }]
|
|
1065
|
+
}
|
|
1066
|
+
]
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
)
|
|
1070
|
+
# Run the code that would have executed the Cline CLI
|
|
1071
|
+
result = Cline::Cli.new(config: '.test-config').task 'A nice user prompt'
|
|
1072
|
+
expect(result[:message].content.first.text).to eq 'Assistant Output'
|
|
1073
|
+
# Use the stub to check what was called
|
|
1074
|
+
expect(cli_stub.issued_commands.first[:command]).to eq ['--config', '.test-config', 'A nice user prompt']
|
|
1075
|
+
end
|
|
1076
|
+
```
|
|
1077
|
+
|
|
1078
|
+
For full API documentation of the `CliStub` class, see the [ClineTest::CliStub YARD documentation](https://www.rubydoc.info/gems/cline-rb/ClineTest/CliStub).
|
|
1079
|
+
|
|
1043
1080
|
### Code linting
|
|
1044
1081
|
|
|
1045
1082
|
The project uses **RuboCop** (~> 1.86) with `rubocop-rspec` and `rubocop-yard` plugins.
|
data/lib/cline/version.rb
CHANGED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
require 'pty_compat'
|
|
2
|
+
|
|
3
|
+
module ClineTest
|
|
4
|
+
# Stub of the Cline CLI (mocking calls to PTY.spawn).
|
|
5
|
+
# This can be used by external projects that need to mock or stub Cline CLI in their own unit tests.
|
|
6
|
+
class CliStub
|
|
7
|
+
class << self
|
|
8
|
+
# Capture the original PTY.spawn method in case some test cases want to use it while the real one is mocked.
|
|
9
|
+
attr_accessor :original_pty_spawn
|
|
10
|
+
|
|
11
|
+
# @return [Boolean] The debug mode.
|
|
12
|
+
attr_accessor :debug
|
|
13
|
+
|
|
14
|
+
# Log debug a message
|
|
15
|
+
#
|
|
16
|
+
# @param message [String, nil] The message to log debug, or nil if given by a proc returning the message for lazy evaluation
|
|
17
|
+
# @yield The optional code returning the message to log in case of debug
|
|
18
|
+
# @yieldreturn [String] The message to log
|
|
19
|
+
def log_debug(message = nil)
|
|
20
|
+
return unless @debug
|
|
21
|
+
|
|
22
|
+
puts "[CLINE STUB DEBUG] - #{block_given? ? yield : message}"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
self.original_pty_spawn = ::PTY.method(:spawn)
|
|
26
|
+
|
|
27
|
+
# Constructor
|
|
28
|
+
#
|
|
29
|
+
# @param example [Object] The RSpec example for which the stub is executed
|
|
30
|
+
# @param debug [Boolean] Are we in debug mode?
|
|
31
|
+
# @param temp_dir [String] Temporary directory used to communicate with the stub
|
|
32
|
+
def initialize(example:, debug: false, temp_dir: '.cline_test/tmp')
|
|
33
|
+
@example = example
|
|
34
|
+
CliStub.debug = debug
|
|
35
|
+
@temp_dir = temp_dir
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Mock a list of commands, with their corresponding stdout, stderr and exit status.
|
|
39
|
+
# This helper hides the underlying ways of running commands from Cline::Cli.
|
|
40
|
+
# It uses a Cline CLI stub that executes mocked commands in place of the real Cline CLI.
|
|
41
|
+
#
|
|
42
|
+
# @param commands [Hash, Array] The description of the mocked behaviour the Cline CLI stub will have.
|
|
43
|
+
# This parameter can be of 2 kinds:
|
|
44
|
+
# - [Hash{Array<String, Regexp> => Hash{Symbol => Object}, Array<Hash{Symbol => Object}>}] Describe a list of (or a single) instructions,
|
|
45
|
+
# for each command to mock.
|
|
46
|
+
# The command to be mocked is the array of Cline arguments (after the cline executable).
|
|
47
|
+
# Each argument from this command line array can be either a String for exact match or a Regexp for pattern matching.
|
|
48
|
+
# Each mocked instruction is described below.
|
|
49
|
+
# - [Array<Hash{Symbol => Object}, Array<Hash{Symbol => Object}>>] Describe a sequential list of groups (or single) instructions.
|
|
50
|
+
# Each group of instruction will be executed for each new command that gets executed, regardless of its arguments.
|
|
51
|
+
# Each mocked instruction is described below.
|
|
52
|
+
#
|
|
53
|
+
# Each instruction is a [Hash{Symbol => Object}] that describes the behaviour to mock.
|
|
54
|
+
# They are executed in sequence of the list they belong to, and in sequence of the keys inside each Hash.
|
|
55
|
+
# Here is the possible instructions that are available:
|
|
56
|
+
# - debug [Boolean] Set debug mode.
|
|
57
|
+
# - eval [String] Execute some code.
|
|
58
|
+
# - exit [Integer] Exit with the given exit status.
|
|
59
|
+
# - log [Hash, String] Add a line in the Cline logs, either as a JSON Hash or a raw String.
|
|
60
|
+
# This property should be used only with a command using the --config flag.
|
|
61
|
+
# - session [Hash{Symbol => Object}] Create a session.
|
|
62
|
+
# This property should be used only with a command using the --config flag.
|
|
63
|
+
# Here is the list of all properties that can be set for the session description:
|
|
64
|
+
# - Any attribute that is in a session JSON file.
|
|
65
|
+
# - messages [Array<Hash{Symbol => Object}, Array<Hash{Symbol => Object}>>, nil] List of messages (or messages groups) to be created,
|
|
66
|
+
# or nil if none.
|
|
67
|
+
# Each message (or group) from the list will be created with 0.2 seconds interval.
|
|
68
|
+
# If the message's text content is a Hash with an eval key, the text content is replaced by the corresponding code execution.
|
|
69
|
+
# - sleep [Float] Sleep for a given time in seconds.
|
|
70
|
+
# - stderr [String] Output a string to stderr.
|
|
71
|
+
# - stdout [String] Output a string to stdout.
|
|
72
|
+
# - task [Hash{Symbol => Object}] Create a task.
|
|
73
|
+
# This property should be used only with a command using the --config flag.
|
|
74
|
+
# Here is the list of all properties that can be set for the task description:
|
|
75
|
+
# - messages [Array<Hash{Symbol => Object}, Array<Hash{Symbol => Object}>>, nil] List of messages (or messages groups) to be created,
|
|
76
|
+
# or nil if none.
|
|
77
|
+
# Each message (or group) from the list will be created with 0.2 seconds interval.
|
|
78
|
+
def mock_commands(commands = {})
|
|
79
|
+
temp_dir = @temp_dir
|
|
80
|
+
run_idx = 0
|
|
81
|
+
@example.instance_eval do
|
|
82
|
+
# Mock `PTY.spawn(*args) do |reader, writer, pid|` with spies pattern
|
|
83
|
+
allow(::PTY).to receive(:spawn) do |*args, &block|
|
|
84
|
+
cline_args = args[(args.find_index { |arg| arg.end_with?('cline') } + 1)..]
|
|
85
|
+
# Find the instructions corresponding to this Cline CLI run we want to mock
|
|
86
|
+
instructions =
|
|
87
|
+
if commands.is_a?(Hash)
|
|
88
|
+
# Ignore the verbose flag for command research if we activated the debug mode on purpose
|
|
89
|
+
cline_args_search = CliStub.debug ? cline_args - ['--verbose'] : cline_args
|
|
90
|
+
_mocked_command, found_instructions = commands.find do |search_command, _search_instructions|
|
|
91
|
+
search_command.size == cline_args_search.size &&
|
|
92
|
+
search_command.zip(cline_args_search).all? { |search_arg, arg| search_arg.is_a?(Regexp) ? arg =~ search_arg : arg == search_arg }
|
|
93
|
+
end
|
|
94
|
+
found_instructions
|
|
95
|
+
else
|
|
96
|
+
commands[run_idx]
|
|
97
|
+
end
|
|
98
|
+
instructions ||= []
|
|
99
|
+
# Create the JSON file that will give all instructions to execute to our Cline CLI stub.
|
|
100
|
+
stub_conf_file = "#{temp_dir}/cli_stub.json"
|
|
101
|
+
FileUtils.mkdir_p File.dirname(stub_conf_file)
|
|
102
|
+
File.write(
|
|
103
|
+
stub_conf_file,
|
|
104
|
+
JSON.dump(
|
|
105
|
+
# Normalize instructions (always using Arrays, setting default values).
|
|
106
|
+
(
|
|
107
|
+
(@debug ? [{ debug: true }] : []) +
|
|
108
|
+
(instructions.is_a?(Array) ? instructions : [instructions])
|
|
109
|
+
).map do |instructions_set|
|
|
110
|
+
normalized_instructions = instructions_set.dup
|
|
111
|
+
if normalized_instructions[:log].is_a?(Hash)
|
|
112
|
+
normalized_instructions[:log] = {
|
|
113
|
+
level: 30,
|
|
114
|
+
hostname: 'LOCALHOST',
|
|
115
|
+
name: 'cline.cli',
|
|
116
|
+
component: 'main',
|
|
117
|
+
properties: {
|
|
118
|
+
ulid: 'test-session-id'
|
|
119
|
+
}
|
|
120
|
+
}.merge(normalized_instructions[:log])
|
|
121
|
+
end
|
|
122
|
+
if normalized_instructions[:session]
|
|
123
|
+
normalized_instructions[:session] = {
|
|
124
|
+
version: 1,
|
|
125
|
+
session_id: 'test-session-id',
|
|
126
|
+
source: 'cli',
|
|
127
|
+
status: 'running',
|
|
128
|
+
interactive: false,
|
|
129
|
+
provider: 'cline',
|
|
130
|
+
model: 'deepseek/deepseek-v4-flash',
|
|
131
|
+
cwd: Dir.pwd,
|
|
132
|
+
workspace_root: Dir.pwd,
|
|
133
|
+
team_name: 'team-sjHpe',
|
|
134
|
+
enable_tools: true,
|
|
135
|
+
enable_spawn: true,
|
|
136
|
+
enable_teams: true
|
|
137
|
+
}.merge(normalized_instructions[:session])
|
|
138
|
+
if normalized_instructions[:session][:messages]
|
|
139
|
+
default_message = {
|
|
140
|
+
id: 'msg_id_1',
|
|
141
|
+
role: 'assistant',
|
|
142
|
+
content: [
|
|
143
|
+
{
|
|
144
|
+
type: 'text',
|
|
145
|
+
text: 'Message content'
|
|
146
|
+
}
|
|
147
|
+
],
|
|
148
|
+
ts: 100
|
|
149
|
+
}
|
|
150
|
+
normalized_instructions[:session][:messages] = normalized_instructions[:session][:messages].map do |messages_group|
|
|
151
|
+
(messages_group.is_a?(Array) ? messages_group : [messages_group]).map { |message| default_message.merge(message) }
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
if normalized_instructions.dig(:task, :messages)
|
|
156
|
+
default_message = { ts: 100, type: 'say', say: 'text', text: 'Message content' }
|
|
157
|
+
normalized_instructions[:task][:messages] = normalized_instructions[:task][:messages].map do |messages_group|
|
|
158
|
+
(messages_group.is_a?(Array) ? messages_group : [messages_group]).map { |message| default_message.merge(message) }
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
normalized_instructions
|
|
162
|
+
end
|
|
163
|
+
)
|
|
164
|
+
)
|
|
165
|
+
# Run PTY.spawn with our stub instead of the real Cline CLI.
|
|
166
|
+
stubbed_cmd = ["ruby#{'.exe' if OS.windows?}", File.expand_path("#{__dir__}/stubs/cline")] + cline_args
|
|
167
|
+
CliStub.log_debug { "Execute `#{stubbed_cmd}` with stub conf:\n#{JSON.pretty_generate(JSON.parse(File.read(stub_conf_file)))}" }
|
|
168
|
+
# In Windows' Ruby implementation (ruby.exe) there is actually a bug that splits multiline arguments into separate arguments.
|
|
169
|
+
# This bug does not exist on Linux implementations.
|
|
170
|
+
# Because of that, we manually replace the new lines with a magic key so that the behaviour stays consistent with the real arguments
|
|
171
|
+
# that would have been sent to Cline CLI.
|
|
172
|
+
# Our stub is then doing the opposite conversion on Windows only.
|
|
173
|
+
stubbed_cmd.map! { |arg| arg.gsub("\n", '__CLINE_STUB__NEW_LINE__') } if OS.windows?
|
|
174
|
+
result = nil
|
|
175
|
+
original_cline_stub_dir = ENV.fetch('CLINE_STUB_DIR', nil)
|
|
176
|
+
ENV['CLINE_STUB_DIR'] = temp_dir
|
|
177
|
+
begin
|
|
178
|
+
result = CliStub.original_pty_spawn.call(*stubbed_cmd) do |reader, writer, pid|
|
|
179
|
+
if @debug
|
|
180
|
+
allow(reader).to receive(:each_line).and_wrap_original do |original_each_line, &each_line_block|
|
|
181
|
+
original_each_line.call do |line|
|
|
182
|
+
CliStub.log_debug "[Cline stub stdout] - #{Cline::Utils::Logger.sanitize_pty_output(line)}"
|
|
183
|
+
each_line_block.call(line)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
block.call(reader, writer, pid)
|
|
188
|
+
end
|
|
189
|
+
ensure
|
|
190
|
+
ENV['CLINE_STUB_DIR'] = original_cline_stub_dir
|
|
191
|
+
end
|
|
192
|
+
run_idx += 1
|
|
193
|
+
result
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Get the list of Cline CLI commands that were issued during this test run
|
|
199
|
+
#
|
|
200
|
+
# @return [Array<Hash{Symbol => Object}>] List of commands that have been issued:
|
|
201
|
+
# * pid [Integer] The PID of the Cline process
|
|
202
|
+
# * command [Array<String>] The command itself
|
|
203
|
+
# * stdin [String, nil] The stdin that was redirected to this command, or nil if none
|
|
204
|
+
def issued_commands
|
|
205
|
+
calls_file = "#{@temp_dir}/cli_calls.json"
|
|
206
|
+
File.exist?(calls_file) ? JSON.parse(File.read(calls_file), symbolize_names: true) : []
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# This is a stub of the cline command line.
|
|
4
|
+
|
|
5
|
+
# It looks for its stub configuration in "#{ENV['CLINE_STUB_DIR']}/cli_stub.json".
|
|
6
|
+
# The cli_stub.json file contains the attributes defined by the mock_commands method (see #ClineTest::Helpers::Cli#mock_commands).
|
|
7
|
+
|
|
8
|
+
# It also records all its call arguments and stdin in the file named "#{ENV['CLINE_STUB_DIR']}/cli_calls.json".
|
|
9
|
+
# Those records are appended to the file if it already exists.
|
|
10
|
+
# This file contains an Array of calls information:
|
|
11
|
+
# * pid [Integer] The PID of the process
|
|
12
|
+
# * command [String] Command line that was called
|
|
13
|
+
# * stdin [String, nil] The content of stdin, or nil if none
|
|
14
|
+
|
|
15
|
+
require 'fileutils'
|
|
16
|
+
require 'json'
|
|
17
|
+
require 'os'
|
|
18
|
+
|
|
19
|
+
cline_stub_dir = ENV.fetch('CLINE_STUB_DIR', nil)
|
|
20
|
+
raise 'CLINE_STUB_DIR environment variable should be set to the directory containing the Cline stub configuration' unless cline_stub_dir
|
|
21
|
+
|
|
22
|
+
# Ruby's Windows implementation splits multilines arguments.
|
|
23
|
+
# Therefore we manually replaced them with a magic key.
|
|
24
|
+
# Make sure they are converted back to real new lines, as it would be received by the real Cline CLI.
|
|
25
|
+
ARGV.map! { |arg| arg.gsub('__CLINE_STUB__NEW_LINE__', "\n") } if OS.windows?
|
|
26
|
+
|
|
27
|
+
# @return [String] The config directory
|
|
28
|
+
def config_dir
|
|
29
|
+
@config_dir ||= begin
|
|
30
|
+
config_match = ARGV.join(' ').match(/--config ([^\s]+)/)[1]
|
|
31
|
+
raise 'The Cline stub can only handle this instruction if used with the --config flag' unless config_match
|
|
32
|
+
|
|
33
|
+
config_match
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @return [Boolean] Are we in plan mode?
|
|
38
|
+
def plan_mode?
|
|
39
|
+
ARGV.include? '--plan'
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Return a session messages file content, for a given list of messages.
|
|
43
|
+
# Always prepend the user input as a user message.
|
|
44
|
+
#
|
|
45
|
+
# @param session_id [String] The session id
|
|
46
|
+
# @param messages [Array<Hash{Symbol => Object}>] List of messages
|
|
47
|
+
# @return [Hash] The JSON messages file content
|
|
48
|
+
# If the message's text content is Hash with an eval key, the text content is replaced by the corresponding code execution.
|
|
49
|
+
def session_messages(session_id, messages)
|
|
50
|
+
{
|
|
51
|
+
version: 1,
|
|
52
|
+
updated_at: Time.now.utc.strftime('%FT%T.%LZ'),
|
|
53
|
+
agent: 'lead',
|
|
54
|
+
sessionId: session_id,
|
|
55
|
+
messages: (
|
|
56
|
+
[
|
|
57
|
+
{
|
|
58
|
+
ts: 10,
|
|
59
|
+
role: 'user',
|
|
60
|
+
content: [{ type: 'text', text: "<user_input mode=\"#{plan_mode? ? 'plan' : 'act'}\">#{ARGV.last}</user_input>" }]
|
|
61
|
+
}
|
|
62
|
+
] +
|
|
63
|
+
messages
|
|
64
|
+
).map do |message|
|
|
65
|
+
if message[:content]
|
|
66
|
+
message.merge(
|
|
67
|
+
content: message[:content].map do |content|
|
|
68
|
+
if content[:text].is_a?(Hash) && content[:text][:eval]
|
|
69
|
+
content.merge(text: eval(content[:text][:eval]))
|
|
70
|
+
else
|
|
71
|
+
content
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
)
|
|
75
|
+
else
|
|
76
|
+
message
|
|
77
|
+
end
|
|
78
|
+
end,
|
|
79
|
+
system_prompt: 'You are Cline, an AI coding agent.'
|
|
80
|
+
}
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Write session messages for a given session ID
|
|
84
|
+
#
|
|
85
|
+
# @param session_id [String] The session ID
|
|
86
|
+
# @param messages [Array<Hash>] The messages to write
|
|
87
|
+
def write_session_messages(session_id, messages)
|
|
88
|
+
log_debug "[Session #{session_id}] - Write #{messages.size} messages"
|
|
89
|
+
File.write(
|
|
90
|
+
File.join(config_dir, 'data', 'sessions', session_id, "#{session_id}.messages.json"),
|
|
91
|
+
JSON.pretty_generate(session_messages(session_id, messages))
|
|
92
|
+
)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Log debug a message
|
|
96
|
+
#
|
|
97
|
+
# @param message [String] Message to log debug
|
|
98
|
+
def log_debug(message)
|
|
99
|
+
puts "[CLINE STUB DEBUG] - #{message}" if @debug
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Make sure output is sent directly to the calling process
|
|
103
|
+
$stdout.sync = true
|
|
104
|
+
$stderr.sync = true
|
|
105
|
+
|
|
106
|
+
@debug = false
|
|
107
|
+
|
|
108
|
+
# Record the call
|
|
109
|
+
calls_file = "#{cline_stub_dir}/cli_calls.json"
|
|
110
|
+
calls = File.exist?(calls_file) ? JSON.parse(File.read(calls_file)) : []
|
|
111
|
+
File.write(
|
|
112
|
+
calls_file,
|
|
113
|
+
JSON.dump(
|
|
114
|
+
calls + [
|
|
115
|
+
{
|
|
116
|
+
pid: Process.pid,
|
|
117
|
+
command: ARGV,
|
|
118
|
+
stdin:
|
|
119
|
+
if $stdin.tty?
|
|
120
|
+
nil
|
|
121
|
+
else
|
|
122
|
+
stdin = $stdin.read
|
|
123
|
+
stdin.empty? ? nil : stdin
|
|
124
|
+
end
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Read the config driven by mock_commands and execute all instructions in sequence
|
|
131
|
+
JSON.parse(File.read("#{cline_stub_dir}/cli_stub.json"), symbolize_names: true).each do |instructions|
|
|
132
|
+
instructions.each do |name, desc|
|
|
133
|
+
log_debug "Execute instruction #{name}..."
|
|
134
|
+
case name
|
|
135
|
+
when :debug
|
|
136
|
+
@debug = desc
|
|
137
|
+
when :eval
|
|
138
|
+
eval desc
|
|
139
|
+
when :exit
|
|
140
|
+
exit desc
|
|
141
|
+
when :log
|
|
142
|
+
log_file = File.join(config_dir, 'data', 'logs', 'cline.log')
|
|
143
|
+
FileUtils.mkdir_p(File.dirname(log_file))
|
|
144
|
+
File.write(
|
|
145
|
+
log_file,
|
|
146
|
+
"#{
|
|
147
|
+
if desc.is_a?(String)
|
|
148
|
+
desc
|
|
149
|
+
else
|
|
150
|
+
# Prepare default values that are process dependent
|
|
151
|
+
JSON.dump(
|
|
152
|
+
{
|
|
153
|
+
time: Time.now.utc.strftime('%FT%T.%LZ'),
|
|
154
|
+
pid: Process.pid
|
|
155
|
+
}.merge(desc)
|
|
156
|
+
)
|
|
157
|
+
end
|
|
158
|
+
}\n",
|
|
159
|
+
mode: 'a+'
|
|
160
|
+
)
|
|
161
|
+
when :session
|
|
162
|
+
session_messages = desc.delete(:messages)
|
|
163
|
+
session_dir = File.join(config_dir, 'data', 'sessions', desc[:session_id])
|
|
164
|
+
messages_file = File.join(session_dir, "#{desc[:session_id]}.messages.json")
|
|
165
|
+
FileUtils.mkdir_p(session_dir)
|
|
166
|
+
File.write(
|
|
167
|
+
File.join(session_dir, "#{desc[:session_id]}.json"),
|
|
168
|
+
JSON.pretty_generate(
|
|
169
|
+
{
|
|
170
|
+
# Prepare default values that are process dependent
|
|
171
|
+
pid: Process.pid,
|
|
172
|
+
started_at: Time.now.utc.strftime('%FT%T.%LZ'),
|
|
173
|
+
messages_path: messages_file
|
|
174
|
+
}.merge(desc)
|
|
175
|
+
)
|
|
176
|
+
)
|
|
177
|
+
if session_messages
|
|
178
|
+
# First create empty file
|
|
179
|
+
write_session_messages(desc[:session_id], [])
|
|
180
|
+
sleep 0.2
|
|
181
|
+
# Then add test messages 1 by 1 with 0.2 seconds delay
|
|
182
|
+
session_messages.size.times do |idx|
|
|
183
|
+
write_session_messages(desc[:session_id], session_messages[0..idx].flatten(1))
|
|
184
|
+
sleep 0.2
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
when :sleep
|
|
188
|
+
sleep desc
|
|
189
|
+
when :stderr
|
|
190
|
+
$stderr.write desc
|
|
191
|
+
when :stdout
|
|
192
|
+
$stdout.write desc
|
|
193
|
+
when :task
|
|
194
|
+
# Simulate a task being created
|
|
195
|
+
$stdout.write "{\"type\":\"task_started\",\"taskId\":\"12345\"}\n"
|
|
196
|
+
task_dir = File.join(config_dir, 'data', 'tasks', '12345')
|
|
197
|
+
FileUtils.mkdir_p(task_dir)
|
|
198
|
+
if desc[:messages]
|
|
199
|
+
messages_file = File.join(task_dir, 'ui_messages.json')
|
|
200
|
+
# First create empty file
|
|
201
|
+
File.write(messages_file, '[]')
|
|
202
|
+
sleep 0.2
|
|
203
|
+
# Then add test messages 1 by 1 with 0.2 seconds delay
|
|
204
|
+
desc[:messages].size.times do |idx|
|
|
205
|
+
File.write(messages_file, JSON.dump(desc[:messages][0..idx].flatten(1)))
|
|
206
|
+
sleep 0.2
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cline-rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Muriel Salvan
|
|
@@ -224,6 +224,8 @@ files:
|
|
|
224
224
|
- lib/cline/workspace.rb
|
|
225
225
|
- lib/cline/workspace_settings.rb
|
|
226
226
|
- lib/cline/workspaces.rb
|
|
227
|
+
- spec/cline_test/cli_stub.rb
|
|
228
|
+
- spec/cline_test/stubs/cline
|
|
227
229
|
homepage: https://github.com/Muriel-Salvan/cline-rb
|
|
228
230
|
licenses:
|
|
229
231
|
- BSD-3-Clause
|