elelem-tools 0.1.1 → 0.3.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/.github/workflows/release.yml +61 -0
- data/.release-please-manifest.json +3 -0
- data/.rspec +1 -0
- data/.rubocop.yml +62 -0
- data/CHANGELOG.md +27 -0
- data/Rakefile +5 -1
- data/lib/elelem/tools/compact.rb +8 -6
- data/lib/elelem/tools/confirm.rb +11 -0
- data/lib/elelem/tools/edit.rb +17 -11
- data/lib/elelem/tools/eval.rb +15 -14
- data/lib/elelem/tools/exec.rb +50 -0
- data/lib/elelem/tools/fork.rb +9 -0
- data/lib/elelem/tools/git.rb +14 -10
- data/lib/elelem/tools/glob.rb +14 -9
- data/lib/elelem/tools/grep.rb +22 -17
- data/lib/elelem/tools/init.rb +22 -20
- data/lib/elelem/tools/interview.rb +11 -10
- data/lib/elelem/tools/list.rb +11 -10
- data/lib/elelem/tools/permissions.json +7 -0
- data/lib/elelem/tools/permissions.rb +75 -0
- data/lib/elelem/tools/shell.rb +19 -17
- data/lib/elelem/tools/task.rb +10 -9
- data/lib/elelem/tools/verify.rb +43 -37
- data/lib/elelem/tools/version.rb +1 -1
- data/lib/elelem/tools.rb +5 -0
- data/release-please-config.json +13 -0
- data/spec/elelem/tools/arguments_spec.rb +62 -0
- data/spec/elelem/tools/edit_spec.rb +40 -0
- data/spec/elelem/tools/fallback_spec.rb +53 -0
- data/spec/elelem/tools/git_spec.rb +35 -0
- data/spec/elelem/tools/permissions_spec.rb +181 -0
- data/spec/elelem/tools/verify_spec.rb +36 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/support/session.rb +11 -0
- metadata +47 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4b0f5e3a9dd3ff1d691507d3eaffb33f083d57eb607ce2be6af72862820d56ad
|
|
4
|
+
data.tar.gz: 918f507fd626ed799f2469412ed990061ba51739363b3296456151691418a0de
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3f1fcbc49703bb6fbf87d96593fe5b7e5be04833c9e8af3f4cc27f9a9fc395fbb5b912b57a00eef9d82b9e5bdcf425d1f58424f9d26d384084eaa7ca9f7c7dc5
|
|
7
|
+
data.tar.gz: fc5bff90cd435be07a865803f5f269a50b2b98ba4950934031ce19d9131a1ed7e3ee30a4908784221f87e956062a9bf590ef7b4e3afa25f52fe80a24627d0d0c
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions: {}
|
|
9
|
+
|
|
10
|
+
concurrency:
|
|
11
|
+
group: release
|
|
12
|
+
cancel-in-progress: false
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
test:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
permissions:
|
|
18
|
+
contents: read
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
21
|
+
with:
|
|
22
|
+
persist-credentials: false
|
|
23
|
+
- uses: ruby/setup-ruby@3ff19f5e2baf30647122352b96108b1fbe250c64 # v1.299.0
|
|
24
|
+
with:
|
|
25
|
+
ruby-version: "4.0"
|
|
26
|
+
bundler-cache: true
|
|
27
|
+
- run: bin/test
|
|
28
|
+
- run: bin/lint
|
|
29
|
+
|
|
30
|
+
release-please:
|
|
31
|
+
needs: test
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
permissions:
|
|
34
|
+
contents: write
|
|
35
|
+
issues: write
|
|
36
|
+
pull-requests: write
|
|
37
|
+
outputs:
|
|
38
|
+
release_created: ${{ steps.release.outputs.release_created }}
|
|
39
|
+
steps:
|
|
40
|
+
- id: release
|
|
41
|
+
uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
|
|
42
|
+
with:
|
|
43
|
+
config-file: release-please-config.json
|
|
44
|
+
manifest-file: .release-please-manifest.json
|
|
45
|
+
|
|
46
|
+
publish:
|
|
47
|
+
needs: release-please
|
|
48
|
+
if: needs.release-please.outputs.release_created == 'true'
|
|
49
|
+
runs-on: ubuntu-latest
|
|
50
|
+
permissions:
|
|
51
|
+
contents: write
|
|
52
|
+
id-token: write
|
|
53
|
+
steps:
|
|
54
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
55
|
+
with:
|
|
56
|
+
persist-credentials: false
|
|
57
|
+
- uses: ruby/setup-ruby@3ff19f5e2baf30647122352b96108b1fbe250c64 # v1.299.0
|
|
58
|
+
with:
|
|
59
|
+
ruby-version: "4.0"
|
|
60
|
+
bundler-cache: true
|
|
61
|
+
- uses: rubygems/release-gem@7f9650160c1a4e7989fdc9855807bdbd421d8b6b # v1.4.1
|
data/.rspec
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
--require spec_helper
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
NewCops: enable
|
|
3
|
+
TargetRubyVersion: 4.0
|
|
4
|
+
SuggestExtensions: false
|
|
5
|
+
Exclude:
|
|
6
|
+
- "pkg/**/*"
|
|
7
|
+
- "bin/**/*"
|
|
8
|
+
- "vendor/**/*"
|
|
9
|
+
- "fixtures/**/*"
|
|
10
|
+
|
|
11
|
+
Metrics:
|
|
12
|
+
Enabled: false
|
|
13
|
+
|
|
14
|
+
Naming/PredicateMethod:
|
|
15
|
+
AllowedMethods:
|
|
16
|
+
- call
|
|
17
|
+
- run
|
|
18
|
+
- summarize
|
|
19
|
+
- prompt
|
|
20
|
+
|
|
21
|
+
Naming/MethodParameterName:
|
|
22
|
+
AllowedNames:
|
|
23
|
+
- me
|
|
24
|
+
- id
|
|
25
|
+
- as
|
|
26
|
+
- io
|
|
27
|
+
|
|
28
|
+
Style/GlobalVars:
|
|
29
|
+
Exclude:
|
|
30
|
+
- "spec/**/*"
|
|
31
|
+
|
|
32
|
+
Lint/EmptyBlock:
|
|
33
|
+
Exclude:
|
|
34
|
+
- "spec/**/*"
|
|
35
|
+
|
|
36
|
+
Lint/UselessDefaultValueArgument:
|
|
37
|
+
Enabled: false
|
|
38
|
+
|
|
39
|
+
Lint/RedundantRequireStatement:
|
|
40
|
+
Enabled: false
|
|
41
|
+
|
|
42
|
+
Lint/UnusedBlockArgument:
|
|
43
|
+
IgnoreEmptyBlocks: true
|
|
44
|
+
AllowUnusedKeywordArguments: true
|
|
45
|
+
|
|
46
|
+
Style/Documentation:
|
|
47
|
+
Enabled: false
|
|
48
|
+
|
|
49
|
+
Style/FormatStringToken:
|
|
50
|
+
Enabled: false
|
|
51
|
+
|
|
52
|
+
Style/FrozenStringLiteralComment:
|
|
53
|
+
Enabled: true
|
|
54
|
+
|
|
55
|
+
Style/StringLiterals:
|
|
56
|
+
EnforcedStyle: double_quotes
|
|
57
|
+
|
|
58
|
+
Style/StringLiteralsInInterpolation:
|
|
59
|
+
EnforcedStyle: double_quotes
|
|
60
|
+
|
|
61
|
+
Layout/LineLength:
|
|
62
|
+
Max: 120
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.3.0](https://github.com/elelem-sh/tools/compare/v0.2.0...v0.3.0) (2026-11-15)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* a built command skips its prompt only after a person approved the tool that built it (elelem-sh/elelem.sh[#3](https://github.com/elelem-sh/tools/issues/3)) ([0f47899](https://github.com/elelem-sh/tools/commit/0f4789942fe36b780beab507f2d406286852ca1e))
|
|
9
|
+
* a command a tool builds itself does not ask again after the tool was approved (elelem-sh/elelem.sh[#3](https://github.com/elelem-sh/tools/issues/3)) ([097b5b9](https://github.com/elelem-sh/tools/commit/097b5b98411857bd2b94ea439a41ce175ac889cd))
|
|
10
|
+
* a failed verify check is an error, so the model sees ok: false ([acddd22](https://github.com/elelem-sh/tools/commit/acddd22fe00653bf5f09110dc4d84434f17e8f37))
|
|
11
|
+
* a failing git command is an error, and grep with no matches is not (elelem-sh/elelem.sh[#20](https://github.com/elelem-sh/tools/issues/20)) ([fedc829](https://github.com/elelem-sh/tools/commit/fedc8295646dfed601cc2e9120434738b9542ac1))
|
|
12
|
+
* ask before running a tool whose permission policy is misspelled ([4adf5f6](https://github.com/elelem-sh/tools/commit/4adf5f6d22230dca701cdd72b8ee45efc346655a))
|
|
13
|
+
* edit counts overlapping matches and edits files that are not UTF-8 (elelem-sh/elelem.sh[#21](https://github.com/elelem-sh/tools/issues/21)) ([c3c2f73](https://github.com/elelem-sh/tools/commit/c3c2f737358d9df67f4fd75a4c952c70ab960b60))
|
|
14
|
+
* edit fails when old is missing or repeated, and takes new literally (elelem-sh/elelem.sh[#21](https://github.com/elelem-sh/tools/issues/21)) ([c2fd4eb](https://github.com/elelem-sh/tools/commit/c2fd4eb9f78ce1c13717c9e04ac7d08e5256bb2a))
|
|
15
|
+
* end of input at a permission prompt refuses (elelem-sh/elelem.sh[#2](https://github.com/elelem-sh/tools/issues/2)) ([dd75f4b](https://github.com/elelem-sh/tools/commit/dd75f4b2d5749b6972f2cfc7e24718654c4f809d))
|
|
16
|
+
* git always asks for its exact command line, since repository config can make any git command run a program ([d50005a](https://github.com/elelem-sh/tools/commit/d50005a9be0d56f1561887f7abe55ee4c3be23bb))
|
|
17
|
+
* git asks once, at execute, for its exact command line ([4dc6bd9](https://github.com/elelem-sh/tools/commit/4dc6bd975c7fa3441af6baee50d72e8662a176eb))
|
|
18
|
+
* grep and glob also fall back when rg or fd cannot run (elelem-sh/elelem.sh[#20](https://github.com/elelem-sh/tools/issues/20)) ([8a5a5d7](https://github.com/elelem-sh/tools/commit/8a5a5d7924dcfebd310efbb8cbcb841e02de48c3))
|
|
19
|
+
* grep and glob fall back only when rg or fd is not installed (elelem-sh/elelem.sh[#20](https://github.com/elelem-sh/tools/issues/20)) ([43180e3](https://github.com/elelem-sh/tools/commit/43180e30477eeeec5388a953376c2b53f14b865b))
|
|
20
|
+
* grep, glob, list and git never take a model-chosen value as an option that runs a program ([0aca202](https://github.com/elelem-sh/tools/commit/0aca2029521e1793c0e631dfa4df24c7eafebe46))
|
|
21
|
+
* only Enter, y or yes allow at a permission prompt (elelem-sh/elelem.sh[#2](https://github.com/elelem-sh/tools/issues/2)) ([d853f3b](https://github.com/elelem-sh/tools/commit/d853f3bf714142ce1f1303744815baa1ab44af97))
|
|
22
|
+
* only read-only git commands share the git approval; any other git command asks for its exact command line ([bbc1a0f](https://github.com/elelem-sh/tools/commit/bbc1a0ff5bc66b9c95b145653942f768df90a1a1))
|
|
23
|
+
* permission prompts escape characters that could redraw them (elelem-sh/elelem.sh[#2](https://github.com/elelem-sh/tools/issues/2)) ([fa46378](https://github.com/elelem-sh/tools/commit/fa463789635d446fced2b84dc6358d51c9389ed5))
|
|
24
|
+
* permission prompts show the command, and verify is allowed by default (elelem-sh/elelem.sh[#3](https://github.com/elelem-sh/tools/issues/3)) ([97c3437](https://github.com/elelem-sh/tools/commit/97c3437b6ec494c9b27d61e6ab11ee2632d5d3ed))
|
|
25
|
+
* require elelem ~> 0.12, the floor builtins/skills actually need ([48b5818](https://github.com/elelem-sh/tools/commit/48b5818282b37877d72c55aeb79ec20ded25c1b4))
|
|
26
|
+
* require elelem 0.13.1, verify each written file, and grep with no matches is not an error ([4dfab39](https://github.com/elelem-sh/tools/commit/4dfab39bb738f3e41e3d2cb28093a22204409911))
|
|
27
|
+
* verify escapes the path before passing it to the shell ([ed52cf8](https://github.com/elelem-sh/tools/commit/ed52cf8f4682225fff4d0a1380f6c44fb59dd614))
|
data/Rakefile
CHANGED
data/lib/elelem/tools/compact.rb
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
Elelem
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
Elelem.configure do |config|
|
|
4
|
+
config.setup(:compact) do |agent|
|
|
5
|
+
agent.commands.register("compact", description: "Compress context") do
|
|
6
|
+
response = agent.turn("Summarize: accomplishments, state, next steps. Brief.")
|
|
7
|
+
agent.conversation.clear!
|
|
8
|
+
agent.conversation.add(role: "user", content: "Context: #{response}")
|
|
9
|
+
agent.output.say " -> compacted"
|
|
10
|
+
end
|
|
9
11
|
end
|
|
10
12
|
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Elelem.configure do |config|
|
|
4
|
+
config.setup(:confirm) do |agent|
|
|
5
|
+
permissions = Elelem::Tools::Permissions.new
|
|
6
|
+
|
|
7
|
+
agent.toolbox.before do |args, tool_name:|
|
|
8
|
+
permissions.check(tool_name, args, input: agent.input)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
data/lib/elelem/tools/edit.rb
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
Elelem
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
3
|
+
Elelem.configure do |config|
|
|
4
|
+
config.setup(:edit) do |agent|
|
|
5
|
+
agent.toolbox.add("edit",
|
|
6
|
+
description: "Replace the one occurrence of old with new in a file",
|
|
7
|
+
params: { path: { type: "string" }, old: { type: "string" }, new: { type: "string" } },
|
|
8
|
+
required: %w[path old new]) do |args|
|
|
9
|
+
content = Pathname.new(args["path"]).expand_path.read
|
|
10
|
+
first = content.index(args["old"]) unless args["old"].empty?
|
|
11
|
+
next { error: "old not found" } unless first
|
|
12
|
+
# index also finds overlapping matches, and copes with invalid bytes.
|
|
13
|
+
next { error: "old matches more than once; include more context" } if content.index(args["old"], first + 1)
|
|
14
|
+
|
|
15
|
+
# The block form takes new literally, so \0 and \1 are not expanded.
|
|
16
|
+
agent.toolbox
|
|
17
|
+
.run("write", { "path" => args["path"], "content" => content.sub(args["old"]) { args["new"] } })
|
|
18
|
+
.merge(replaced: args["old"], with: args["new"])
|
|
19
|
+
end
|
|
14
20
|
end
|
|
15
21
|
end
|
data/lib/elelem/tools/eval.rb
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
Elelem
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Elelem.configure do |config|
|
|
4
|
+
config.setup(:eval) do |agent|
|
|
5
|
+
description = <<~'DESC'
|
|
6
|
+
Evaluate Ruby code. Available API:
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
name = "search"
|
|
9
|
+
agent.toolbox.add(name, description: "Search using rg", params: { query: { type: "string" } }, required: ["query"], aliases: []) do |args|
|
|
10
|
+
agent.toolbox.run("execute", { "command" => "rg --json -nI -F #{args["query"]}" })
|
|
11
|
+
end
|
|
12
|
+
DESC
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
agent.toolbox.add("eval",
|
|
15
|
+
description: description,
|
|
16
|
+
params: { ruby: { type: "string" } },
|
|
17
|
+
required: ["ruby"]) do |args|
|
|
18
|
+
{ result: binding.eval(args["ruby"]) } # rubocop:disable Security/Eval
|
|
19
|
+
end
|
|
19
20
|
end
|
|
20
21
|
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Elelem
|
|
4
|
+
module Tools
|
|
5
|
+
# glob, grep and list run their command through execute. When a person was
|
|
6
|
+
# just asked about the outer call, which shows the same arguments, the
|
|
7
|
+
# command it builds does not ask again. When the outer call was allowed
|
|
8
|
+
# without asking, the command still asks. A deny on execute always applies.
|
|
9
|
+
# git is not one of them: repository config can make any git command run a
|
|
10
|
+
# program, so git is allowed by default and execute asks for its exact
|
|
11
|
+
# command line, once.
|
|
12
|
+
def self.exec(toolbox, *args)
|
|
13
|
+
command = args.flatten.map { |a| Shellwords.escape(a.to_s) }.join(" ")
|
|
14
|
+
nested { toolbox.run("execute", { "command" => command }) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.nested
|
|
18
|
+
outer = Thread.current[:elelem_tools_nested]
|
|
19
|
+
Thread.current[:elelem_tools_nested] = true
|
|
20
|
+
yield
|
|
21
|
+
ensure
|
|
22
|
+
Thread.current[:elelem_tools_nested] = outer
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.nested?
|
|
26
|
+
Thread.current[:elelem_tools_nested] == true
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.approved?
|
|
30
|
+
Thread.current[:elelem_tools_approved] == true
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.approved=(value)
|
|
34
|
+
Thread.current[:elelem_tools_approved] = value
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# rg and grep exit 1 when nothing matches. That is an answer, not a failure.
|
|
38
|
+
def self.no_matches(result)
|
|
39
|
+
return result unless result[:exit_status] == 1 && result[:content].to_s.strip.empty?
|
|
40
|
+
|
|
41
|
+
result.to_h.except(:ok, :error).merge(content: "no matches")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# bash exits 127 when the command is not installed, 126 when it cannot run
|
|
45
|
+
# it. rg exits 1 when nothing matches, which is not a reason to fall back.
|
|
46
|
+
def self.missing?(result)
|
|
47
|
+
[126, 127].include?(result[:exit_status])
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
data/lib/elelem/tools/git.rb
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
Elelem
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
Elelem.configure do |config|
|
|
4
|
+
config.setup(:git) do |agent|
|
|
5
|
+
agent.toolbox.add("git",
|
|
6
|
+
description: "Run git command",
|
|
7
|
+
params: { command: { type: "string" }, args: { type: "array", items: { type: "string" } } },
|
|
8
|
+
required: ["command"]) do |args|
|
|
9
|
+
# Not Tools.exec: config (aliases, hooks, filters, textconv, signing) can
|
|
10
|
+
# make any git command run a program, so execute asks for the exact line.
|
|
11
|
+
line = ["git", args["command"], *(args["args"] || [])].map { |a| Shellwords.escape(a.to_s) }.join(" ")
|
|
12
|
+
agent.toolbox.run("execute", { "command" => line })
|
|
13
|
+
end
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
agent.toolbox.after("git") do |_, result|
|
|
16
|
+
agent.output.say " ! #{result[:error]}" if result[:error]
|
|
17
|
+
end
|
|
14
18
|
end
|
|
15
19
|
end
|
data/lib/elelem/tools/glob.rb
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
Elelem
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
Elelem.configure do |config|
|
|
4
|
+
config.setup(:glob) do |agent|
|
|
5
|
+
agent.toolbox.add("glob",
|
|
6
|
+
description: "Find files matching pattern",
|
|
7
|
+
params: { pattern: { type: "string" }, path: { type: "string" } },
|
|
8
|
+
required: ["pattern"]) do |args|
|
|
9
|
+
path = args["path"].to_s.empty? ? "." : args["path"]
|
|
10
|
+
# find has no --, so a path like -delete becomes ./-delete.
|
|
11
|
+
path = "./#{path}" if path.start_with?("-")
|
|
12
|
+
result = Elelem::Tools.exec(agent.toolbox, "fd", "--glob", "--", args["pattern"], path)
|
|
13
|
+
next result unless Elelem::Tools.missing?(result)
|
|
14
|
+
|
|
15
|
+
Elelem::Tools.exec(agent.toolbox, "find", path, "-name", args["pattern"])
|
|
16
|
+
end
|
|
12
17
|
end
|
|
13
18
|
end
|
data/lib/elelem/tools/grep.rb
CHANGED
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
Elelem
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
Elelem.configure do |config|
|
|
4
|
+
config.setup(:grep) do |agent|
|
|
5
|
+
agent.toolbox.add("grep",
|
|
6
|
+
description: "Search file contents",
|
|
7
|
+
params: { pattern: { type: "string" }, path: { type: "string" }, glob: { type: "string" } },
|
|
8
|
+
required: ["pattern"]) do |args|
|
|
9
|
+
path = args["path"].to_s.empty? ? "." : args["path"]
|
|
10
|
+
glob = args["glob"]
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
# -e and -- keep a pattern or path starting with - from being read as an
|
|
13
|
+
# option, such as rg --pre=CMD, which runs a program on every file.
|
|
14
|
+
rg_args = ["rg", "-n"]
|
|
15
|
+
rg_args += ["-g", glob] if glob
|
|
16
|
+
rg_args += ["-e", args["pattern"], "--", path]
|
|
17
|
+
result = Elelem::Tools.exec(agent.toolbox, *rg_args)
|
|
18
|
+
if Elelem::Tools.missing?(result)
|
|
19
|
+
grep_args = ["grep", "-rn"]
|
|
20
|
+
grep_args += ["--include", glob] if glob
|
|
21
|
+
grep_args += ["-e", args["pattern"], "--", path]
|
|
22
|
+
result = Elelem::Tools.exec(agent.toolbox, *grep_args)
|
|
23
|
+
end
|
|
24
|
+
Elelem::Tools.no_matches(result)
|
|
25
|
+
end
|
|
21
26
|
end
|
|
22
27
|
end
|
data/lib/elelem/tools/init.rb
CHANGED
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
Elelem
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
Elelem.configure do |config|
|
|
4
|
+
config.setup(:init) do |agent|
|
|
5
|
+
agent.commands.register("init", description: "Generate AGENTS.md") do
|
|
6
|
+
template = <<~PROMPT
|
|
7
|
+
AGENTS.md generator. Analyze codebase and write AGENTS.md to project root.
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
# AGENTS.md Spec (https://agents.md/)
|
|
10
|
+
A file providing context and instructions for AI coding agents.
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
## Recommended Sections
|
|
13
|
+
- Commands: build, test, lint commands
|
|
14
|
+
- Code Style: conventions, patterns
|
|
15
|
+
- Architecture: key components and flow
|
|
16
|
+
- Testing: how to run tests
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
## Process
|
|
19
|
+
1. Read README.md if present
|
|
20
|
+
2. Identify language (Gemfile, package.json, go.mod)
|
|
21
|
+
3. Find test scripts (bin/test, npm test)
|
|
22
|
+
4. Check linter configs
|
|
23
|
+
5. Write concise AGENTS.md
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
Keep it minimal. No fluff.
|
|
26
|
+
PROMPT
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
Elelem::Tools.fork(agent, template: template).turn("Generate AGENTS.md for this project")
|
|
29
|
+
end
|
|
28
30
|
end
|
|
29
31
|
end
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
Elelem
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
Elelem.configure do |config|
|
|
4
|
+
config.setup(:interview) do |agent|
|
|
5
|
+
agent.toolbox.add("interview",
|
|
6
|
+
description: "Ask the user a question and wait for their response",
|
|
7
|
+
params: {
|
|
8
|
+
question: { type: "string", description: "The question to ask the user" }
|
|
9
|
+
},
|
|
10
|
+
required: ["question"]) do |args|
|
|
11
|
+
agent.output.say(args["question"], as: :markdown)
|
|
12
|
+
{ answer: agent.input.ask("> ") }
|
|
13
|
+
end
|
|
13
14
|
end
|
|
14
15
|
end
|
data/lib/elelem/tools/list.rb
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
Elelem
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
Elelem.configure do |config|
|
|
4
|
+
config.setup(:list) do |agent|
|
|
5
|
+
agent.toolbox.add("list",
|
|
6
|
+
description: "List directory contents",
|
|
7
|
+
params: { path: { type: "string" }, recursive: { type: "boolean" } },
|
|
8
|
+
required: [],
|
|
9
|
+
aliases: ["ls"]) do |args|
|
|
10
|
+
path = args["path"] && !args["path"].empty? ? args["path"] : "."
|
|
11
|
+
flags = args["recursive"] ? "-laR" : "-la"
|
|
12
|
+
Elelem::Tools.exec(agent.toolbox, "ls", flags, "--", path)
|
|
13
|
+
end
|
|
13
14
|
end
|
|
14
15
|
end
|