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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 76ec7a010fbd709f164dbf2ac05b85bd60187e1451a6505726fc7f35dbd9d7eb
4
- data.tar.gz: 74de74a8b6add422ef3db5ade2bb9f3eec63eb1373687e324145b2cd09de48c8
3
+ metadata.gz: 4b0f5e3a9dd3ff1d691507d3eaffb33f083d57eb607ce2be6af72862820d56ad
4
+ data.tar.gz: 918f507fd626ed799f2469412ed990061ba51739363b3296456151691418a0de
5
5
  SHA512:
6
- metadata.gz: 49fd1c98c79f4d45d6039d71347b6d96268a6bc10b594b96e9e4a24d54f863c4e0091fd457dfbd7e6bb3caca22bfda71b3c82c350cea2390b14173b81b271124
7
- data.tar.gz: 8049db48ab2f0c0ca92b5db290e8827f2bc2f0f5eef144407872a4bd4f0ebe4850621610abf865ce0161e813edfb427a8a0a64e842fdc33c0027f4bb15c221db
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
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.3.0"
3
+ }
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
@@ -1,4 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "bundler/gem_tasks"
4
- task default: %i[]
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: %i[spec]
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Elelem::Plugins.register(:compact) do |agent|
4
- agent.commands.register("compact", description: "Compress context") do
5
- response = agent.turn("Summarize: accomplishments, state, next steps. Brief.")
6
- agent.conversation.clear!
7
- agent.conversation.add(role: "user", content: "Context: #{response}")
8
- agent.terminal.say " → compacted"
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
@@ -1,15 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Elelem::Plugins.register(:edit) do |agent|
4
- agent.toolbox.add("edit",
5
- description: "Replace first occurrence of text in file",
6
- params: { path: { type: "string" }, old: { type: "string" }, new: { type: "string" } },
7
- required: ["path", "old", "new"]
8
- ) do |args|
9
- path = Pathname.new(args["path"]).expand_path
10
- content = path.read
11
- agent.toolbox
12
- .run("write", { "path" => args["path"], "content" => content.sub(args["old"], args["new"]) })
13
- .merge(replaced: args["old"], with: args["new"])
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
@@ -1,20 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Elelem::Plugins.register(:eval) do |agent|
4
- description = <<~'DESC'
5
- Evaluate Ruby code. Available API:
3
+ Elelem.configure do |config|
4
+ config.setup(:eval) do |agent|
5
+ description = <<~'DESC'
6
+ Evaluate Ruby code. Available API:
6
7
 
7
- name = "search"
8
- agent.toolbox.add(name, description: "Search using rg", params: { query: { type: "string" } }, required: ["query"], aliases: []) do |args|
9
- agent.toolbox.run("execute", { "command" => "rg --json -nI -F #{args["query"]}" })
10
- end
11
- DESC
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
- agent.toolbox.add("eval",
14
- description: description,
15
- params: { ruby: { type: "string" } },
16
- required: ["ruby"]
17
- ) do |args|
18
- { result: binding.eval(args["ruby"]) }
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
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Elelem
4
+ module Tools
5
+ def self.fork(agent, template:)
6
+ Elelem::Agent.new(agent.provider, toolbox: agent.toolbox, output: agent.output, template: template)
7
+ end
8
+ end
9
+ end
@@ -1,15 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Elelem::Plugins.register(:git) do |agent|
4
- agent.toolbox.add("git",
5
- description: "Run git command",
6
- params: { command: { type: "string" }, args: { type: "array", items: { type: "string" } } },
7
- required: ["command"]
8
- ) do |args|
9
- agent.toolbox.exec("git", args["command"], *(args["args"] || []))
10
- end
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
- agent.toolbox.after("git") do |_, result|
13
- agent.terminal.say " ! #{result[:error]}" if result[:error]
15
+ agent.toolbox.after("git") do |_, result|
16
+ agent.output.say " ! #{result[:error]}" if result[:error]
17
+ end
14
18
  end
15
19
  end
@@ -1,13 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Elelem::Plugins.register(:glob) do |agent|
4
- agent.toolbox.add("glob",
5
- description: "Find files matching pattern",
6
- params: { pattern: { type: "string" }, path: { type: "string" } },
7
- required: ["pattern"]
8
- ) do |args|
9
- path = args["path"].to_s.empty? ? "." : args["path"]
10
- result = agent.toolbox.exec("fd", "--glob", args["pattern"], path)
11
- result[:ok] ? result : agent.toolbox.exec("find", path, "-name", args["pattern"])
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
@@ -1,22 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Elelem::Plugins.register(:grep) do |agent|
4
- agent.toolbox.add("grep",
5
- description: "Search file contents",
6
- params: { pattern: { type: "string" }, path: { type: "string" }, glob: { type: "string" } },
7
- required: ["pattern"]
8
- ) do |args|
9
- path = args["path"].to_s.empty? ? "." : args["path"]
10
- glob = args["glob"]
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
- rg_args = ["rg", "-n", args["pattern"], path]
13
- rg_args += ["-g", glob] if glob
14
- result = agent.toolbox.exec(*rg_args)
15
- next result if result[:ok]
16
-
17
- grep_args = ["grep", "-rn"]
18
- grep_args += ["--include", glob] if glob
19
- grep_args += [args["pattern"], path]
20
- agent.toolbox.exec(*grep_args)
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
@@ -1,29 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Elelem::Plugins.register(:init) do |agent|
4
- agent.commands.register("init", description: "Generate AGENTS.md") do
5
- system_prompt = <<~PROMPT
6
- AGENTS.md generator. Analyze codebase and write AGENTS.md to project root.
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
- # AGENTS.md Spec (https://agents.md/)
9
- A file providing context and instructions for AI coding agents.
9
+ # AGENTS.md Spec (https://agents.md/)
10
+ A file providing context and instructions for AI coding agents.
10
11
 
11
- ## Recommended Sections
12
- - Commands: build, test, lint commands
13
- - Code Style: conventions, patterns
14
- - Architecture: key components and flow
15
- - Testing: how to run tests
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
- ## Process
18
- 1. Read README.md if present
19
- 2. Identify language (Gemfile, package.json, go.mod)
20
- 3. Find test scripts (bin/test, npm test)
21
- 4. Check linter configs
22
- 5. Write concise AGENTS.md
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
- Keep it minimal. No fluff.
25
- PROMPT
25
+ Keep it minimal. No fluff.
26
+ PROMPT
26
27
 
27
- agent.fork(system_prompt: system_prompt).turn("Generate AGENTS.md for this project")
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::Plugins.register(:interview) do |agent|
4
- agent.toolbox.add("interview",
5
- description: "Ask the user a question and wait for their response",
6
- params: {
7
- question: { type: "string", description: "The question to ask the user" },
8
- },
9
- required: ["question"]
10
- ) do |args|
11
- agent.terminal.say(agent.terminal.markdown(args["question"]))
12
- { answer: agent.terminal.ask("> ") }
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
@@ -1,14 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Elelem::Plugins.register(:list) do |agent|
4
- agent.toolbox.add("list",
5
- description: "List directory contents",
6
- params: { path: { type: "string" }, recursive: { type: "boolean" } },
7
- required: [],
8
- aliases: ["ls"]
9
- ) do |args|
10
- path = args["path"] && !args["path"].empty? ? args["path"] : "."
11
- flags = args["recursive"] ? "-laR" : "-la"
12
- agent.toolbox.exec("ls", flags, path)
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
@@ -0,0 +1,7 @@
1
+ {
2
+ "execute": "ask",
3
+ "git": "allow",
4
+ "read": "allow",
5
+ "verify": "allow",
6
+ "write": "allow"
7
+ }