ask-tools-shell 0.2.2 → 0.3.1
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 +13 -0
- data/lib/ask/tools/shell/apply_patch.rb +208 -0
- data/lib/ask/tools/shell/edit.rb +82 -13
- data/lib/ask/tools/shell/file_mutation_queue.rb +84 -0
- data/lib/ask/tools/shell/version.rb +1 -1
- data/lib/ask/tools/shell.rb +6 -1
- metadata +12 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 139a762b4412fcf6dac23e7360f7f49e537f32272491865e90f3ec36ae27da44
|
|
4
|
+
data.tar.gz: d074b1a2173259a65fc9f9eab1efd3424f2ef4bfcea2e3e868a3ee281ca5d5db
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8e16c060e637dc9dfa1c420fff9eaf5c357a8abc2ce7c9489fa082bd2f319b65e4b03b64677d3310df359725877e313c334c0a39a5eaa0b5bd8ff44253c2aac9
|
|
7
|
+
data.tar.gz: ab4bbada08684a89410144614778b70be6a5bc7e7e1d0dbd596aac2727df3eab80d3ec94de9cf172d4a0519d6a1494d102010087c61ea9bee6118fed549bf17f
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.3.0 (2026-06-21)
|
|
4
|
+
|
|
5
|
+
- Added `EditOperations` pluggable interface (read_file, write_file, file_exist?, file?, file_size, expand_path)
|
|
6
|
+
- Added `DefaultEditOperations` default implementation
|
|
7
|
+
- Added BOM detection/stripping to Edit tool (`Shell.strip_bom`)
|
|
8
|
+
- Added line ending detection and preservation to Edit tool (`Shell.detect_line_ending`, `Shell.normalize_line_endings`, `Shell.restore_line_endings`)
|
|
9
|
+
- Added `FileMutationQueue` for atomic batch file edits with rollback
|
|
10
|
+
|
|
11
|
+
## 0.2.2
|
|
12
|
+
|
|
13
|
+
- Various fixes
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module Ask
|
|
6
|
+
module Tools
|
|
7
|
+
module Shell
|
|
8
|
+
module ApplyPatchOperations
|
|
9
|
+
def read_file(path)
|
|
10
|
+
File.read(path)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def write_file(path, content)
|
|
14
|
+
File.write(path, content)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def file_exist?(path)
|
|
18
|
+
File.exist?(path)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def file?(path)
|
|
22
|
+
File.file?(path)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def delete_file(path)
|
|
26
|
+
File.delete(path)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def expand_path(path)
|
|
30
|
+
File.expand_path(path)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def mkdir_p(path)
|
|
34
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class DefaultApplyPatchOperations
|
|
39
|
+
include ApplyPatchOperations
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Apply a patch to files using a unified diff format.
|
|
44
|
+
# Supports creating, updating, and deleting files within a
|
|
45
|
+
# "*** Begin Patch" / "*** End Patch" envelope.
|
|
46
|
+
class ApplyPatch < Ask::Tool
|
|
47
|
+
description "Edit files using a unified diff format. " \
|
|
48
|
+
"Wrap all changes in a \"*** Begin Patch\" / \"*** End Patch\" envelope. " \
|
|
49
|
+
"Each file section starts with a header: " \
|
|
50
|
+
"\"*** Add File: <path>\" for new files, " \
|
|
51
|
+
"\"*** Update File: <path>\" for changes, or " \
|
|
52
|
+
"\"*** Delete File: <path>\" for removals. " \
|
|
53
|
+
"Prefix new lines with +."
|
|
54
|
+
|
|
55
|
+
param :patchText, type: :string, desc: "The full patch text describing all file changes", required: true
|
|
56
|
+
|
|
57
|
+
attr_writer :operations
|
|
58
|
+
|
|
59
|
+
def operations
|
|
60
|
+
@operations ||= Shell::DefaultApplyPatchOperations.new
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
MAX_FILE_SIZE = 10_000_000
|
|
64
|
+
|
|
65
|
+
def execute(patchText:)
|
|
66
|
+
patches = parse_patch(patchText)
|
|
67
|
+
return Ask::Result.error(message: "No valid patch sections found") if patches.empty?
|
|
68
|
+
|
|
69
|
+
results = []
|
|
70
|
+
patches.each do |entry|
|
|
71
|
+
path = operations.expand_path(entry[:path])
|
|
72
|
+
|
|
73
|
+
case entry[:type]
|
|
74
|
+
when :add
|
|
75
|
+
if operations.file_exist?(path)
|
|
76
|
+
return Ask::Result.error(message: "File already exists: #{path}")
|
|
77
|
+
end
|
|
78
|
+
operations.mkdir_p(path)
|
|
79
|
+
content = entry[:lines].join("\n")
|
|
80
|
+
content += "\n" unless content.end_with?("\n")
|
|
81
|
+
operations.write_file(path, content)
|
|
82
|
+
results << { action: "add", path: entry[:path], lines: entry[:lines].size }
|
|
83
|
+
|
|
84
|
+
when :update
|
|
85
|
+
unless operations.file_exist?(path)
|
|
86
|
+
return Ask::Result.error(message: "File does not exist: #{path}")
|
|
87
|
+
end
|
|
88
|
+
unless operations.file?(path)
|
|
89
|
+
return Ask::Result.error(message: "Not a file: #{path}")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
raw = operations.read_file(path)
|
|
93
|
+
size = raw.bytesize
|
|
94
|
+
if size > MAX_FILE_SIZE
|
|
95
|
+
return Ask::Result.error(message: "File too large (#{size} bytes)")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
new_content = apply_chunks(raw, entry[:chunks])
|
|
99
|
+
operations.write_file(path, new_content)
|
|
100
|
+
results << { action: "update", path: entry[:path] }
|
|
101
|
+
|
|
102
|
+
when :delete
|
|
103
|
+
unless operations.file_exist?(path)
|
|
104
|
+
return Ask::Result.error(message: "File does not exist: #{path}")
|
|
105
|
+
end
|
|
106
|
+
operations.delete_file(path)
|
|
107
|
+
results << { action: "delete", path: entry[:path] }
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
summary = results.map { |r| "#{r[:action].upcase_first} #{r[:path]}" }.join("\n")
|
|
112
|
+
Ask::Result.ok(data: { summary: summary, results: results }, metadata: { results: results })
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
private
|
|
116
|
+
|
|
117
|
+
PATCH_START = "*** Begin Patch"
|
|
118
|
+
PATCH_END = "*** End Patch"
|
|
119
|
+
ADD_HEADER = /^\*\*\* Add File:\s+(.+)/i
|
|
120
|
+
UPDATE_HEADER = /^\*\*\* Update File:\s+(.+)/i
|
|
121
|
+
DELETE_HEADER = /^\*\*\* Delete File:\s+(.+)/i
|
|
122
|
+
HUNK_HEADER = /^@@/
|
|
123
|
+
|
|
124
|
+
def parse_patch(text)
|
|
125
|
+
text = text.dup.force_encoding("UTF-8")
|
|
126
|
+
start_idx = text.index(PATCH_START)
|
|
127
|
+
return [] unless start_idx
|
|
128
|
+
|
|
129
|
+
after_start = text[(start_idx + PATCH_START.length)..]
|
|
130
|
+
end_idx = after_start.index(PATCH_END)
|
|
131
|
+
body = end_idx ? after_start[0...end_idx] : after_start
|
|
132
|
+
|
|
133
|
+
entries = []
|
|
134
|
+
lines = body.split("\n")
|
|
135
|
+
i = 0
|
|
136
|
+
while i < lines.length
|
|
137
|
+
line = lines[i]
|
|
138
|
+
|
|
139
|
+
if (m = line.match(ADD_HEADER))
|
|
140
|
+
path = m[1].strip
|
|
141
|
+
i += 1
|
|
142
|
+
content_lines = []
|
|
143
|
+
while i < lines.length && !lines[i].start_with?("***")
|
|
144
|
+
content_lines << lines[i].sub(/^\+/, "") if lines[i].start_with?("+")
|
|
145
|
+
i += 1
|
|
146
|
+
end
|
|
147
|
+
entries << { type: :add, path: path, lines: content_lines }
|
|
148
|
+
|
|
149
|
+
elsif (m = line.match(UPDATE_HEADER))
|
|
150
|
+
path = m[1].strip
|
|
151
|
+
i += 1
|
|
152
|
+
chunks = []
|
|
153
|
+
while i < lines.length && !lines[i].start_with?("***")
|
|
154
|
+
if lines[i].match?(HUNK_HEADER)
|
|
155
|
+
i += 1
|
|
156
|
+
old_lines = []
|
|
157
|
+
new_lines = []
|
|
158
|
+
while i < lines.length && !lines[i].start_with?("***") && !lines[i].match?(HUNK_HEADER) && !lines[i].start_with?("***")
|
|
159
|
+
if lines[i].start_with?("-")
|
|
160
|
+
old_lines << lines[i][1..]
|
|
161
|
+
elsif lines[i].start_with?("+")
|
|
162
|
+
new_lines << lines[i][1..]
|
|
163
|
+
elsif lines[i].start_with?(" ")
|
|
164
|
+
old_lines << lines[i][1..]
|
|
165
|
+
new_lines << lines[i][1..]
|
|
166
|
+
end
|
|
167
|
+
i += 1
|
|
168
|
+
end
|
|
169
|
+
chunks << { old: old_lines, new: new_lines } unless old_lines.empty? && new_lines.empty?
|
|
170
|
+
else
|
|
171
|
+
i += 1
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
entries << { type: :update, path: path, chunks: chunks }
|
|
175
|
+
|
|
176
|
+
elsif (m = line.match(DELETE_HEADER))
|
|
177
|
+
entries << { type: :delete, path: m[1].strip }
|
|
178
|
+
i += 1
|
|
179
|
+
|
|
180
|
+
else
|
|
181
|
+
i += 1
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
entries
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def apply_chunks(content, chunks)
|
|
189
|
+
return content if chunks.empty?
|
|
190
|
+
|
|
191
|
+
lines = content.split("\n", -1)
|
|
192
|
+
chunks.each do |chunk|
|
|
193
|
+
old_text = chunk[:old].join("\n")
|
|
194
|
+
new_text = chunk[:new].join("\n")
|
|
195
|
+
file_text = lines.join("\n")
|
|
196
|
+
idx = file_text.index(old_text)
|
|
197
|
+
next unless idx
|
|
198
|
+
|
|
199
|
+
before = file_text[0...idx]
|
|
200
|
+
after = file_text[(idx + old_text.length)..]
|
|
201
|
+
file_text = before + new_text + after
|
|
202
|
+
lines = file_text.split("\n", -1)
|
|
203
|
+
end
|
|
204
|
+
lines.join("\n")
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
data/lib/ask/tools/shell/edit.rb
CHANGED
|
@@ -2,11 +2,66 @@
|
|
|
2
2
|
|
|
3
3
|
module Ask
|
|
4
4
|
module Tools
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
module Shell
|
|
6
|
+
# Pluggable operations for the Edit tool.
|
|
7
|
+
module EditOperations
|
|
8
|
+
def read_file(path)
|
|
9
|
+
File.read(path)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def write_file(path, content)
|
|
13
|
+
File.write(path, content)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def file_exist?(path)
|
|
17
|
+
File.exist?(path)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def file?(path)
|
|
21
|
+
File.file?(path)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def file_size(path)
|
|
25
|
+
File.size(path)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def expand_path(path)
|
|
29
|
+
File.expand_path(path)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class DefaultEditOperations
|
|
34
|
+
include EditOperations
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Strip UTF-8 BOM from content. Returns { bom: String, text: String }.
|
|
38
|
+
def self.strip_bom(content)
|
|
39
|
+
bom = content.byteslice(0, 3) == "\xEF\xBB\xBF" ? content.byteslice(0, 3) : ""
|
|
40
|
+
[bom, bom.empty? ? content : content.byteslice(3..)]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Detect the dominant line ending in content.
|
|
44
|
+
def self.detect_line_ending(content)
|
|
45
|
+
crlf = content.scan("\r\n").size
|
|
46
|
+
lf = content.count("\n") - crlf
|
|
47
|
+
crlf > lf ? "\r\n" : "\n"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Normalize line endings to LF.
|
|
51
|
+
def self.normalize_line_endings(content)
|
|
52
|
+
content.gsub("\r\n", "\n").gsub("\r", "\n")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Restore original line endings after normalization.
|
|
56
|
+
def self.restore_line_endings(content, original_ending)
|
|
57
|
+
return content if original_ending == "\n"
|
|
58
|
+
content.gsub("\n", original_ending)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Edit a file by replacing exact text. Supports BOM, line-ending preservation.
|
|
7
63
|
class Edit < Ask::Tool
|
|
8
|
-
description "Edit a file by replacing exact text.
|
|
9
|
-
"Uses exact string matching — provide enough surrounding context for uniqueness."
|
|
64
|
+
description "Edit a file by replacing exact text."
|
|
10
65
|
|
|
11
66
|
param :path, type: :string, desc: "Absolute path to the file", required: true
|
|
12
67
|
param :old_string, type: :string, desc: "Exact text to replace", required: true
|
|
@@ -15,24 +70,34 @@ module Ask
|
|
|
15
70
|
|
|
16
71
|
MAX_FILE_SIZE = 1_000_000
|
|
17
72
|
|
|
73
|
+
attr_writer :operations
|
|
74
|
+
|
|
75
|
+
def operations
|
|
76
|
+
@operations ||= Shell::DefaultEditOperations.new
|
|
77
|
+
end
|
|
78
|
+
|
|
18
79
|
def execute(path:, old_string:, new_string:, replace_all: false)
|
|
19
|
-
path =
|
|
80
|
+
path = operations.expand_path(path)
|
|
20
81
|
|
|
21
|
-
unless
|
|
22
|
-
return Ask::Result.error(message: "File does not exist:
|
|
82
|
+
unless operations.file_exist?(path)
|
|
83
|
+
return Ask::Result.error(message: "File does not exist: " + path)
|
|
23
84
|
end
|
|
24
85
|
|
|
25
|
-
unless
|
|
26
|
-
return Ask::Result.error(message: "Not a file:
|
|
86
|
+
unless operations.file?(path)
|
|
87
|
+
return Ask::Result.error(message: "Not a file: " + path)
|
|
27
88
|
end
|
|
28
89
|
|
|
29
|
-
|
|
90
|
+
size = operations.file_size(path)
|
|
91
|
+
if size > MAX_FILE_SIZE
|
|
30
92
|
return Ask::Result.error(
|
|
31
|
-
message: "File too large (#{
|
|
93
|
+
message: "File too large (#{size} bytes). Maximum is #{MAX_FILE_SIZE} bytes."
|
|
32
94
|
)
|
|
33
95
|
end
|
|
34
96
|
|
|
35
|
-
|
|
97
|
+
raw = operations.read_file(path)
|
|
98
|
+
bom, content = Shell.strip_bom(raw)
|
|
99
|
+
original_ending = Shell.detect_line_ending(content)
|
|
100
|
+
content = Shell.normalize_line_endings(content)
|
|
36
101
|
|
|
37
102
|
if replace_all
|
|
38
103
|
count = content.scan(old_string).size
|
|
@@ -45,7 +110,11 @@ module Ask
|
|
|
45
110
|
content = content.sub(old_string, new_string)
|
|
46
111
|
end
|
|
47
112
|
|
|
48
|
-
|
|
113
|
+
# Restore line endings and re-add BOM
|
|
114
|
+
content = Shell.restore_line_endings(content, original_ending)
|
|
115
|
+
content = bom + content
|
|
116
|
+
|
|
117
|
+
operations.write_file(path, content)
|
|
49
118
|
Ask::Result.ok(data: { path: path, replacements: count },
|
|
50
119
|
metadata: { path: path, replacements: count })
|
|
51
120
|
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ask
|
|
4
|
+
module Tools
|
|
5
|
+
module Shell
|
|
6
|
+
# Queues file mutations and applies them atomically.
|
|
7
|
+
# Rollback on any failure — no partial writes.
|
|
8
|
+
#
|
|
9
|
+
# queue = FileMutationQueue.new
|
|
10
|
+
# queue.stage("path/to/file.rb", ->(content) {
|
|
11
|
+
# content.gsub("old", "new")
|
|
12
|
+
# })
|
|
13
|
+
# queue.apply! # reads all staged files, applies transforms, writes back
|
|
14
|
+
#
|
|
15
|
+
class FileMutationQueue
|
|
16
|
+
class ApplyError < StandardError; end
|
|
17
|
+
|
|
18
|
+
def initialize(operations: nil)
|
|
19
|
+
@staged = []
|
|
20
|
+
@operations = operations || DefaultEditOperations.new
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Stage a mutation for a file.
|
|
24
|
+
# @param path [String] absolute path to the file
|
|
25
|
+
# @yield [content] raw file content, returns modified content
|
|
26
|
+
# @yieldparam content [String] the original file content
|
|
27
|
+
# @yieldreturn [String] the modified content
|
|
28
|
+
def stage(path, &block)
|
|
29
|
+
@staged << { path: File.expand_path(path), block: block }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Apply all staged mutations atomically.
|
|
33
|
+
# Reads all files, applies transforms, then writes all back.
|
|
34
|
+
# If any write fails, all written files are rolled back to original.
|
|
35
|
+
# @return [Array<Hash>] results with :path, :original_size, :new_size, :success
|
|
36
|
+
# @raise [ApplyError] if any mutation fails (after rollback)
|
|
37
|
+
def apply!
|
|
38
|
+
snapshots = []
|
|
39
|
+
|
|
40
|
+
# Phase 1: read all files and apply transforms
|
|
41
|
+
@staged.each do |entry|
|
|
42
|
+
path = entry[:path]
|
|
43
|
+
original = @operations.read_file(path)
|
|
44
|
+
modified = entry[:block].call(original)
|
|
45
|
+
snapshots << { path: path, original: original, modified: modified }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Phase 2: write all files, track rollback info
|
|
49
|
+
written = []
|
|
50
|
+
begin
|
|
51
|
+
snapshots.each do |s|
|
|
52
|
+
@operations.write_file(s[:path], s[:modified])
|
|
53
|
+
written << s[:path]
|
|
54
|
+
end
|
|
55
|
+
rescue => e
|
|
56
|
+
# Rollback: restore originals for files we already wrote
|
|
57
|
+
written.each do |path|
|
|
58
|
+
snapshot = snapshots.find { |s| s[:path] == path }
|
|
59
|
+
@operations.write_file(path, snapshot[:original]) if snapshot
|
|
60
|
+
rescue => rb_e
|
|
61
|
+
$stderr.puts "[FileMutationQueue] Rollback failed for #{path}: #{rb_e.message}"
|
|
62
|
+
end
|
|
63
|
+
raise ApplyError, "Mutation failed at #{e.class}: #{e.message}. Rolled back #{written.size} files."
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
snapshots.map do |s|
|
|
67
|
+
{ path: s[:path], original_size: s[:original].length,
|
|
68
|
+
new_size: s[:modified].length, success: true }
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Clear all staged mutations without applying.
|
|
73
|
+
def clear!
|
|
74
|
+
@staged.clear
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Number of staged mutations.
|
|
78
|
+
def size
|
|
79
|
+
@staged.size
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
data/lib/ask/tools/shell.rb
CHANGED
|
@@ -9,11 +9,16 @@ require_relative "shell/edit"
|
|
|
9
9
|
require_relative "shell/glob"
|
|
10
10
|
require_relative "shell/grep"
|
|
11
11
|
require_relative "shell/code"
|
|
12
|
+
require_relative "shell/apply_patch"
|
|
12
13
|
|
|
13
14
|
module Ask
|
|
14
15
|
module Tools
|
|
15
16
|
module Shell
|
|
16
|
-
TOOLS = [Bash, Read, Write, Edit, Glob, Grep, Code].freeze
|
|
17
|
+
TOOLS = [Bash, Read, Write, Edit, Glob, Grep, Code, ApplyPatch].freeze
|
|
18
|
+
|
|
19
|
+
def self.all
|
|
20
|
+
TOOLS.map(&:new)
|
|
21
|
+
end
|
|
17
22
|
end
|
|
18
23
|
end
|
|
19
24
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ask-tools-shell
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kaka Ruto
|
|
@@ -13,28 +13,28 @@ dependencies:
|
|
|
13
13
|
name: ask-tools
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
|
-
- - "
|
|
16
|
+
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
18
|
version: '0.1'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
|
-
- - "
|
|
23
|
+
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '0.1'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: ask-sandbox-providers
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
|
-
- - "
|
|
30
|
+
- - ">="
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
32
|
version: '0.1'
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
|
-
- - "
|
|
37
|
+
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '0.1'
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
@@ -86,14 +86,17 @@ executables: []
|
|
|
86
86
|
extensions: []
|
|
87
87
|
extra_rdoc_files: []
|
|
88
88
|
files:
|
|
89
|
+
- CHANGELOG.md
|
|
89
90
|
- LICENSE
|
|
90
91
|
- README.md
|
|
91
92
|
- lib/ask-tools-shell.rb
|
|
92
93
|
- lib/ask/skills/shell.patterns/SKILL.md
|
|
93
94
|
- lib/ask/tools/shell.rb
|
|
95
|
+
- lib/ask/tools/shell/apply_patch.rb
|
|
94
96
|
- lib/ask/tools/shell/bash.rb
|
|
95
97
|
- lib/ask/tools/shell/code.rb
|
|
96
98
|
- lib/ask/tools/shell/edit.rb
|
|
99
|
+
- lib/ask/tools/shell/file_mutation_queue.rb
|
|
97
100
|
- lib/ask/tools/shell/glob.rb
|
|
98
101
|
- lib/ask/tools/shell/grep.rb
|
|
99
102
|
- lib/ask/tools/shell/read.rb
|
|
@@ -102,7 +105,10 @@ files:
|
|
|
102
105
|
homepage: https://github.com/ask-rb/ask-tools-shell
|
|
103
106
|
licenses:
|
|
104
107
|
- MIT
|
|
105
|
-
metadata:
|
|
108
|
+
metadata:
|
|
109
|
+
homepage_uri: https://github.com/ask-rb/ask-tools-shell
|
|
110
|
+
source_code_uri: https://github.com/ask-rb/ask-tools-shell
|
|
111
|
+
changelog_uri: https://github.com/ask-rb/ask-tools-shell/blob/master/CHANGELOG.md
|
|
106
112
|
rdoc_options: []
|
|
107
113
|
require_paths:
|
|
108
114
|
- lib
|