elelem-tools 0.1.0 → 0.2.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: 10d95e567040251074141f329ea4f0e1400a866715c8ca6b3c5c58f61136576a
4
- data.tar.gz: 135c83d3d9945b31555a28012a5b0e9d91260d4d8e2e984de42f71461e8f76dc
3
+ metadata.gz: 0074cea1a49859fbbcd424c4a30cb791b22e49b3a023484386644f50d8590925
4
+ data.tar.gz: f4fd8cacf3d4e8509aac90aeadb0515757ce429a1390a9138f344919983f3c0e
5
5
  SHA512:
6
- metadata.gz: c7026b46f8b420cb9be98b16282dda7a0048b909057b7c945e76df481a5b7139846ffe3f12239868c3829d43e45bc5da2a0f2e8155caa5b90f73001841ae6741
7
- data.tar.gz: fb44a774890cafa5d26cf14eb8d5d7b117a73cd56bd8190c49cef711e7611aa7c431faf95b0b8b4ed0be399a65fb61a818bf7a76e66a73877bd36bdc4b4e410e
6
+ metadata.gz: 33be226b5096e099d4c1e7c2e32af8f3ae1c0d581ee82c7250585b439a60972da0b396a26ec258ad6130140ed6b66138672799b48a26b575ffa17f1934fc53bd
7
+ data.tar.gz: 62e813b53649bce2a34abed94160dda5ba0d1b1e2876977fcce821c4fd5241cda569f2954bdc3b5f42193316c8ef8a2847faf3e8414bfdcbeec1427ab5e11508
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
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,17 @@
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 first occurrence of text in file",
7
+ params: { path: { type: "string" }, old: { type: "string" }, new: { type: "string" } },
8
+ required: ["path", "old", "new"]
9
+ ) do |args|
10
+ path = Pathname.new(args["path"]).expand_path
11
+ content = path.read
12
+ agent.toolbox
13
+ .run("write", { "path" => args["path"], "content" => content.sub(args["old"], args["new"]) })
14
+ .merge(replaced: args["old"], with: args["new"])
15
+ end
14
16
  end
15
17
  end
@@ -1,20 +1,22 @@
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"]
18
+ ) do |args|
19
+ { result: binding.eval(args["ruby"]) }
20
+ end
19
21
  end
20
22
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Elelem
4
+ module Tools
5
+ def self.exec(toolbox, *args)
6
+ command = args.flatten.map { |a| Shellwords.escape(a.to_s) }.join(" ")
7
+ toolbox.run("execute", { "command" => command })
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Elelem
4
+ module Tools
5
+ def self.fork(agent, system_prompt:)
6
+ Elelem::Agent.new(agent.provider, toolbox: agent.toolbox, output: agent.output, input: agent.input, system_prompt: system_prompt)
7
+ end
8
+ end
9
+ end
@@ -1,15 +1,17 @@
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"]
9
+ ) do |args|
10
+ Elelem::Tools.exec(agent.toolbox, "git", args["command"], *(args["args"] || []))
11
+ end
11
12
 
12
- agent.toolbox.after("git") do |_, result|
13
- agent.terminal.say " ! #{result[:error]}" if result[:error]
13
+ agent.toolbox.after("git") do |_, result|
14
+ agent.output.say " ! #{result[:error]}" if result[:error]
15
+ end
14
16
  end
15
17
  end
@@ -1,13 +1,15 @@
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"]
9
+ ) do |args|
10
+ path = args["path"].to_s.empty? ? "." : args["path"]
11
+ result = Elelem::Tools.exec(agent.toolbox, "fd", "--glob", args["pattern"], path)
12
+ result[:ok] ? result : Elelem::Tools.exec(agent.toolbox, "find", path, "-name", args["pattern"])
13
+ end
12
14
  end
13
15
  end
@@ -1,22 +1,24 @@
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"]
9
+ ) do |args|
10
+ path = args["path"].to_s.empty? ? "." : args["path"]
11
+ glob = args["glob"]
11
12
 
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]
13
+ rg_args = ["rg", "-n", args["pattern"], path]
14
+ rg_args += ["-g", glob] if glob
15
+ result = Elelem::Tools.exec(agent.toolbox, *rg_args)
16
+ next result if result[:ok]
16
17
 
17
- grep_args = ["grep", "-rn"]
18
- grep_args += ["--include", glob] if glob
19
- grep_args += [args["pattern"], path]
20
- agent.toolbox.exec(*grep_args)
18
+ grep_args = ["grep", "-rn"]
19
+ grep_args += ["--include", glob] if glob
20
+ grep_args += [args["pattern"], path]
21
+ Elelem::Tools.exec(agent.toolbox, *grep_args)
22
+ end
21
23
  end
22
24
  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
+ system_prompt = <<~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, system_prompt: system_prompt).turn("Generate AGENTS.md for this project")
29
+ end
28
30
  end
29
31
  end
@@ -1,14 +1,16 @@
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"]
11
+ ) do |args|
12
+ agent.output.say(args["question"], as: :markdown)
13
+ { answer: agent.input.ask("> ") }
14
+ end
13
15
  end
14
16
  end
@@ -1,14 +1,16 @@
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"]
10
+ ) do |args|
11
+ path = args["path"] && !args["path"].empty? ? args["path"] : "."
12
+ flags = args["recursive"] ? "-laR" : "-la"
13
+ Elelem::Tools.exec(agent.toolbox, "ls", flags, path)
14
+ end
13
15
  end
14
16
  end
@@ -0,0 +1,5 @@
1
+ {
2
+ "execute": "ask",
3
+ "read": "allow",
4
+ "write": "allow"
5
+ }
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Elelem
4
+ module Tools
5
+ class Permissions
6
+ LOAD_PATHS = [
7
+ File.expand_path("permissions.json", __dir__),
8
+ "~/.agents/permissions.json",
9
+ ".agents/permissions.json"
10
+ ].freeze
11
+
12
+ def initialize(rules: default_rules)
13
+ @rules = rules
14
+ end
15
+
16
+ def check(tool_name, args, input:)
17
+ policy = @rules[tool_name.to_sym] || :ask
18
+ case policy
19
+ when :allow then true
20
+ when :deny then raise "Permission denied: #{tool_name}"
21
+ when :ask then prompt(tool_name, args, input)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def load_config(path)
28
+ return {} unless File.exist?(path)
29
+
30
+ JSON.parse(File.read(path)).transform_keys(&:to_sym).transform_values(&:to_sym)
31
+ rescue JSON::ParserError
32
+ {}
33
+ end
34
+
35
+ def prompt(tool_name, args, input)
36
+ return true unless input.interactive?
37
+
38
+ answer = input.ask(" Allow? [Y/n] > ")&.downcase
39
+ raise "User denied permission: #{tool_name}" if answer == "n"
40
+
41
+ true
42
+ end
43
+
44
+ def default_rules
45
+ LOAD_PATHS.reduce({}) do |rules, path|
46
+ rules.merge(load_config(File.expand_path(path)))
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -1,23 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Elelem::Plugins.register(:shell) do |agent|
4
- strip_ansi = ->(text) do
5
- text
6
- .gsub(/^Script started.*?\n/, "")
7
- .gsub(/\nScript done.*$/, "")
8
- .gsub(/\e\].*?(?:\a|\e\\)/, "")
9
- .gsub(/\e\[[0-9;?]*[A-Za-z]/, "")
10
- .gsub(/\e[PX^_].*?\e\\/, "")
11
- .gsub(/\e./, "")
12
- .gsub(/[\b]/, "")
13
- .gsub(/\r/, "")
14
- end
3
+ Elelem.configure do |config|
4
+ config.setup(:shell) do |agent|
5
+ strip_ansi = ->(text) do
6
+ text
7
+ .gsub(/^Script started.*?\n/, "")
8
+ .gsub(/\nScript done.*$/, "")
9
+ .gsub(/\e\].*?(?:\a|\e\\)/, "")
10
+ .gsub(/\e\[[0-9;?]*[A-Za-z]/, "")
11
+ .gsub(/\e[PX^_].*?\e\\/, "")
12
+ .gsub(/\e./, "")
13
+ .gsub(/[\b]/, "")
14
+ .gsub(/\r/, "")
15
+ end
15
16
 
16
- agent.commands.register("shell", description: "Start interactive shell") do
17
- transcript = Tempfile.create do |file|
18
- system("script", "-q", file.path, chdir: Dir.pwd)
19
- strip_ansi.call(File.read(file.path))
17
+ agent.commands.register("shell", description: "Start interactive shell") do
18
+ transcript = Tempfile.create do |file|
19
+ system("script", "-q", file.path, chdir: Dir.pwd)
20
+ strip_ansi.call(File.read(file.path))
21
+ end
22
+ agent.conversation.add(role: "user", content: transcript) unless transcript.strip.empty?
20
23
  end
21
- agent.conversation.add(role: "user", content: transcript) unless transcript.strip.empty?
22
24
  end
23
25
  end
@@ -1,13 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Elelem::Plugins.register(:task) do |agent|
4
- agent.toolbox.add("task",
5
- description: "Delegate subtask to focused agent (complex searches, multi-file analysis)",
6
- params: { prompt: { type: "string" } },
7
- required: ["prompt"]
8
- ) do |args|
9
- sub = agent.fork(system_prompt: "Research agent. Search, analyze, report. Be concise.")
10
- sub.turn(args["prompt"])
11
- { result: sub.conversation.last[:content] }
3
+ Elelem.configure do |config|
4
+ config.setup(:task) do |agent|
5
+ agent.toolbox.add("task",
6
+ description: "Delegate subtask to focused agent (complex searches, multi-file analysis)",
7
+ params: { prompt: { type: "string" } },
8
+ required: ["prompt"]
9
+ ) do |args|
10
+ sub = Elelem::Tools.fork(agent, system_prompt: "Research agent. Search, analyze, report. Be concise.")
11
+ sub.turn(args["prompt"])
12
+ { result: sub.conversation.last[:content] }
13
+ end
12
14
  end
13
15
  end
@@ -1,19 +1,47 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Elelem::Plugins.register(:verify) do |agent|
4
- agent.toolbox.add("verify",
5
- description: "Verify file syntax and run tests",
6
- params: { path: { type: "string" } },
7
- required: ["path"]
8
- ) do |args|
9
- path = args["path"]
10
- Elelem::Plugins::Verifiers.for(path).inject({ verified: [] }) do |memo, cmd|
11
- agent.terminal.say agent.toolbox.header("execute", { "command" => cmd })
12
- v = agent.toolbox.run("execute", { "command" => cmd })
13
- break v.merge(path: path, command: cmd) if v[:exit_status] != 0
3
+ class Verifiers
4
+ SYNTAX = {
5
+ ".rb" => "ruby -c %{path}",
6
+ ".erb" => "erb -x %{path} | ruby -c",
7
+ ".py" => "python -m py_compile %{path}",
8
+ ".go" => "go vet %{path}",
9
+ ".rs" => "cargo check --quiet",
10
+ ".ts" => "npx tsc --noEmit %{path}",
11
+ ".js" => "node --check %{path}",
12
+ }.freeze
14
13
 
15
- memo[:verified] << cmd
16
- memo
14
+ def self.for(path)
15
+ return [] unless path
16
+
17
+ cmds = []
18
+ ext = File.extname(path)
19
+ cmds << (SYNTAX[ext] % { path: path }) if SYNTAX[ext]
20
+ cmds << test_runner
21
+ cmds.compact
22
+ end
23
+
24
+ def self.test_runner
25
+ %w[bin/test script/test].find { |s| File.executable?(s) }
26
+ end
27
+ end
28
+
29
+ Elelem.configure do |config|
30
+ config.setup(:verify) do |agent|
31
+ agent.toolbox.add("verify",
32
+ description: "Verify file syntax and run tests",
33
+ params: { path: { type: "string" } },
34
+ required: ["path"]
35
+ ) do |args|
36
+ path = args["path"]
37
+ Verifiers.for(path).inject({ verified: [] }) do |memo, cmd|
38
+ agent.output.doing("execute", { "command" => cmd })
39
+ v = agent.toolbox.run("execute", { "command" => cmd })
40
+ break v.merge(path: path, command: cmd) if v[:exit_status] != 0
41
+
42
+ memo[:verified] << cmd
43
+ memo
44
+ end
17
45
  end
18
46
  end
19
47
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Elelem
4
4
  module Tools
5
- VERSION = "0.1.0"
5
+ VERSION = "0.2.0"
6
6
  end
7
7
  end
data/lib/elelem/tools.rb CHANGED
@@ -2,18 +2,23 @@
2
2
 
3
3
  require "elelem"
4
4
  require "pathname"
5
+ require "shellwords"
5
6
  require "tempfile"
6
7
 
7
8
  require_relative "tools/version"
8
9
  require_relative "tools/compact"
10
+ require_relative "tools/confirm"
9
11
  require_relative "tools/edit"
10
12
  require_relative "tools/eval"
13
+ require_relative "tools/exec"
14
+ require_relative "tools/fork"
11
15
  require_relative "tools/git"
12
16
  require_relative "tools/glob"
13
17
  require_relative "tools/grep"
14
18
  require_relative "tools/init"
15
19
  require_relative "tools/interview"
16
20
  require_relative "tools/list"
21
+ require_relative "tools/permissions"
17
22
  require_relative "tools/shell"
18
23
  require_relative "tools/task"
19
24
  require_relative "tools/verify"
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Elelem::Tools::Permissions do
4
+ subject { described_class.new }
5
+
6
+ let(:input) { double(ask: nil, interactive?: false) }
7
+
8
+ describe "#check" do
9
+ context "with default allow policies" do
10
+ it "allows read without prompting" do
11
+ expect(subject.check("read", {}, input: input)).to be true
12
+ expect(input).not_to have_received(:ask)
13
+ end
14
+ end
15
+
16
+ context "with deny policy" do
17
+ subject { described_class.new(rules: { write: :deny }) }
18
+
19
+ it { expect { subject.check("write", {}, input: input) }.to raise_error(/Permission denied/) }
20
+ end
21
+
22
+ context "with ask policy on a non-interactive input" do
23
+ it "returns true without prompting" do
24
+ expect(subject.check("execute", {}, input: input)).to be true
25
+ expect(input).not_to have_received(:ask)
26
+ end
27
+ end
28
+
29
+ context "with ask policy on an interactive input" do
30
+ let(:input) { double(ask: answer, interactive?: true) }
31
+
32
+ context "when approved" do
33
+ let(:answer) { "y" }
34
+
35
+ it "prompts and returns true" do
36
+ expect(subject.check("execute", {}, input: input)).to be true
37
+ expect(input).to have_received(:ask)
38
+ end
39
+ end
40
+
41
+ context "when denied" do
42
+ let(:answer) { "n" }
43
+
44
+ it { expect { subject.check("execute", {}, input: input) }.to raise_error(/User denied permission/) }
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../lib/elelem/tools"
4
+
5
+ RSpec.configure do |config|
6
+ config.disable_monkey_patching!
7
+
8
+ config.expect_with :rspec do |c|
9
+ c.syntax = :expect
10
+ end
11
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elelem-tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - mo khan
@@ -15,14 +15,42 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '0.10'
18
+ version: '0.11'
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
- version: '0.10'
25
+ version: '0.11'
26
+ - !ruby/object:Gem::Dependency
27
+ name: elelem-builtins
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '0.1'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.1'
40
+ - !ruby/object:Gem::Dependency
41
+ name: json
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '3.0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '3.0'
26
54
  - !ruby/object:Gem::Dependency
27
55
  name: pathname
28
56
  requirement: !ruby/object:Gem::Requirement
@@ -58,22 +86,30 @@ executables: []
58
86
  extensions: []
59
87
  extra_rdoc_files: []
60
88
  files:
89
+ - ".rspec"
61
90
  - LICENSE.txt
62
91
  - Rakefile
63
92
  - lib/elelem/tools.rb
64
93
  - lib/elelem/tools/compact.rb
94
+ - lib/elelem/tools/confirm.rb
65
95
  - lib/elelem/tools/edit.rb
66
96
  - lib/elelem/tools/eval.rb
97
+ - lib/elelem/tools/exec.rb
98
+ - lib/elelem/tools/fork.rb
67
99
  - lib/elelem/tools/git.rb
68
100
  - lib/elelem/tools/glob.rb
69
101
  - lib/elelem/tools/grep.rb
70
102
  - lib/elelem/tools/init.rb
71
103
  - lib/elelem/tools/interview.rb
72
104
  - lib/elelem/tools/list.rb
105
+ - lib/elelem/tools/permissions.json
106
+ - lib/elelem/tools/permissions.rb
73
107
  - lib/elelem/tools/shell.rb
74
108
  - lib/elelem/tools/task.rb
75
109
  - lib/elelem/tools/verify.rb
76
110
  - lib/elelem/tools/version.rb
111
+ - spec/elelem/tools/permissions_spec.rb
112
+ - spec/spec_helper.rb
77
113
  homepage: https://src.mokhan.ca/elelem/tools
78
114
  licenses:
79
115
  - MIT