agentf 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 +7 -0
- data/bin/agentf +8 -0
- data/lib/agentf/agent_policy.rb +54 -0
- data/lib/agentf/agents/architect.rb +67 -0
- data/lib/agentf/agents/base.rb +53 -0
- data/lib/agentf/agents/debugger.rb +75 -0
- data/lib/agentf/agents/designer.rb +69 -0
- data/lib/agentf/agents/documenter.rb +58 -0
- data/lib/agentf/agents/explorer.rb +65 -0
- data/lib/agentf/agents/reviewer.rb +64 -0
- data/lib/agentf/agents/security.rb +84 -0
- data/lib/agentf/agents/specialist.rb +68 -0
- data/lib/agentf/agents/tester.rb +79 -0
- data/lib/agentf/agents.rb +19 -0
- data/lib/agentf/cli/architecture.rb +83 -0
- data/lib/agentf/cli/arg_parser.rb +50 -0
- data/lib/agentf/cli/code.rb +165 -0
- data/lib/agentf/cli/install.rb +112 -0
- data/lib/agentf/cli/memory.rb +393 -0
- data/lib/agentf/cli/metrics.rb +103 -0
- data/lib/agentf/cli/router.rb +111 -0
- data/lib/agentf/cli/update.rb +204 -0
- data/lib/agentf/commands/architecture.rb +183 -0
- data/lib/agentf/commands/debugger.rb +238 -0
- data/lib/agentf/commands/designer.rb +179 -0
- data/lib/agentf/commands/explorer.rb +208 -0
- data/lib/agentf/commands/memory_reviewer.rb +186 -0
- data/lib/agentf/commands/metrics.rb +272 -0
- data/lib/agentf/commands/security_scanner.rb +98 -0
- data/lib/agentf/commands/tester.rb +232 -0
- data/lib/agentf/commands.rb +17 -0
- data/lib/agentf/context_builder.rb +35 -0
- data/lib/agentf/installer.rb +580 -0
- data/lib/agentf/mcp/server.rb +310 -0
- data/lib/agentf/memory.rb +530 -0
- data/lib/agentf/packs.rb +74 -0
- data/lib/agentf/service/providers.rb +158 -0
- data/lib/agentf/tools/component_spec.rb +28 -0
- data/lib/agentf/tools/error_analysis.rb +19 -0
- data/lib/agentf/tools/file_match.rb +21 -0
- data/lib/agentf/tools/test_template.rb +17 -0
- data/lib/agentf/tools.rb +12 -0
- data/lib/agentf/version.rb +5 -0
- data/lib/agentf/workflow_contract.rb +158 -0
- data/lib/agentf/workflow_engine.rb +424 -0
- data/lib/agentf.rb +87 -0
- metadata +164 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
require_relative "../commands"
|
|
5
|
+
|
|
6
|
+
module Agentf
|
|
7
|
+
module Agents
|
|
8
|
+
# Tester Agent - Test generation and execution
|
|
9
|
+
class Tester < Base
|
|
10
|
+
DESCRIPTION = "Automated test generation and execution."
|
|
11
|
+
COMMANDS = %w[detect_framework generate_unit_tests run_tests].freeze
|
|
12
|
+
MEMORY_CONCEPTS = {
|
|
13
|
+
"reads" => [],
|
|
14
|
+
"writes" => ["store_success"],
|
|
15
|
+
"policy" => "Persist test generation outcomes for future reuse."
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
def self.description
|
|
19
|
+
DESCRIPTION
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.commands
|
|
23
|
+
COMMANDS
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.memory_concepts
|
|
27
|
+
MEMORY_CONCEPTS
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.policy_boundaries
|
|
31
|
+
{
|
|
32
|
+
"always" => ["Produce framework-aware tests", "Verify red/green state when TDD enabled"],
|
|
33
|
+
"ask_first" => ["Changing test framework conventions"],
|
|
34
|
+
"never" => ["Mark passing when command output is uncertain"],
|
|
35
|
+
"required_inputs" => [],
|
|
36
|
+
"required_outputs" => ["test_file"]
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def initialize(memory, commands: nil)
|
|
41
|
+
super(memory)
|
|
42
|
+
@commands = commands || Agentf::Commands::Tester.new
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def generate_tests(code_file, test_type: "unit")
|
|
46
|
+
log "Generating #{test_type} tests for: #{code_file}"
|
|
47
|
+
|
|
48
|
+
template = @commands.generate_unit_tests(code_file)
|
|
49
|
+
|
|
50
|
+
memory.store_success(
|
|
51
|
+
title: "Generated #{test_type} tests for #{code_file}",
|
|
52
|
+
description: "Created #{template.test_file} with #{test_type} tests",
|
|
53
|
+
context: "Test framework: #{template.framework}",
|
|
54
|
+
tags: ["testing", test_type, code_file.split(".").last],
|
|
55
|
+
agent: name
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
log "Created: #{template.test_file}"
|
|
59
|
+
|
|
60
|
+
{
|
|
61
|
+
"source_file" => code_file,
|
|
62
|
+
"test_file" => template.test_file,
|
|
63
|
+
"test_type" => test_type,
|
|
64
|
+
"generated_code" => template.test_code
|
|
65
|
+
}
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def run_tests(test_file)
|
|
69
|
+
log "Running tests: #{test_file}"
|
|
70
|
+
|
|
71
|
+
result = @commands.run_tests(test_file: test_file)
|
|
72
|
+
|
|
73
|
+
log "Tests passed: #{result['passed']}"
|
|
74
|
+
|
|
75
|
+
{ "test_file" => test_file, "passed" => result["passed"] }
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Load all agents
|
|
4
|
+
require_relative "agents/base"
|
|
5
|
+
require_relative "agents/architect"
|
|
6
|
+
require_relative "agents/specialist"
|
|
7
|
+
require_relative "agents/reviewer"
|
|
8
|
+
require_relative "agents/documenter"
|
|
9
|
+
require_relative "agents/explorer"
|
|
10
|
+
require_relative "agents/tester"
|
|
11
|
+
require_relative "agents/debugger"
|
|
12
|
+
require_relative "agents/designer"
|
|
13
|
+
require_relative "agents/security"
|
|
14
|
+
|
|
15
|
+
module Agentf
|
|
16
|
+
module Agents
|
|
17
|
+
# All agents are loaded above
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agentf
|
|
4
|
+
module CLI
|
|
5
|
+
class Architecture
|
|
6
|
+
include ArgParser
|
|
7
|
+
|
|
8
|
+
def initialize(architecture: nil)
|
|
9
|
+
@architecture = architecture || Commands::Architecture.new
|
|
10
|
+
@json_output = false
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def run(args)
|
|
14
|
+
@json_output = !args.delete("--json").nil?
|
|
15
|
+
command = args.shift || "help"
|
|
16
|
+
|
|
17
|
+
payload = case command
|
|
18
|
+
when "analyze"
|
|
19
|
+
@architecture.analyze_layers
|
|
20
|
+
when "callbacks"
|
|
21
|
+
@architecture.analyze_callbacks(limit: extract_limit(args))
|
|
22
|
+
when "gods"
|
|
23
|
+
@architecture.find_god_objects(limit: extract_limit(args))
|
|
24
|
+
when "review"
|
|
25
|
+
@architecture.review_layer_violations(limit: extract_limit(args))
|
|
26
|
+
when "gradual"
|
|
27
|
+
goal = args.join(" ").strip
|
|
28
|
+
@architecture.plan_gradual_adoption(goal: goal.empty? ? "improve architecture boundaries" : goal)
|
|
29
|
+
when "help", "--help", "-h"
|
|
30
|
+
show_help
|
|
31
|
+
return
|
|
32
|
+
else
|
|
33
|
+
emit_error("Unknown architecture command: #{command}")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
emit(payload)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def emit(payload)
|
|
42
|
+
if @json_output
|
|
43
|
+
puts JSON.generate(payload)
|
|
44
|
+
return
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
puts JSON.pretty_generate(payload)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def emit_error(message)
|
|
51
|
+
if @json_output
|
|
52
|
+
puts JSON.generate({ "error" => message })
|
|
53
|
+
else
|
|
54
|
+
$stderr.puts "Error: #{message}"
|
|
55
|
+
end
|
|
56
|
+
exit 1
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def show_help
|
|
60
|
+
puts <<~HELP
|
|
61
|
+
Usage: agentf architecture <command> [options]
|
|
62
|
+
|
|
63
|
+
Commands:
|
|
64
|
+
analyze Analyze layer distribution
|
|
65
|
+
callbacks Find callback-heavy model files
|
|
66
|
+
gods Find likely god objects
|
|
67
|
+
review Review layer violations
|
|
68
|
+
gradual [goal] Generate gradual adoption plan
|
|
69
|
+
|
|
70
|
+
Options:
|
|
71
|
+
-n <count> Result limit for callbacks/gods/review (default: 10)
|
|
72
|
+
--json Output in JSON format
|
|
73
|
+
|
|
74
|
+
Examples:
|
|
75
|
+
agentf architecture analyze
|
|
76
|
+
agentf architecture callbacks -n 20
|
|
77
|
+
agentf architecture review --json
|
|
78
|
+
agentf architecture gradual "adopt layered rails patterns"
|
|
79
|
+
HELP
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agentf
|
|
4
|
+
module CLI
|
|
5
|
+
# Shared argument parsing helpers used by all CLI subcommands.
|
|
6
|
+
module ArgParser
|
|
7
|
+
# Extracts a -n <value> flag from args, removes both entries,
|
|
8
|
+
# and returns the integer value. Returns default if not found.
|
|
9
|
+
def extract_limit(args, default: 10)
|
|
10
|
+
idx = args.index("-n")
|
|
11
|
+
return default unless idx && args[idx + 1]
|
|
12
|
+
|
|
13
|
+
limit = args[idx + 1].to_i
|
|
14
|
+
args.delete_at(idx + 1)
|
|
15
|
+
args.delete_at(idx)
|
|
16
|
+
limit
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Extracts --prefix=value from args, removes the entry,
|
|
20
|
+
# and returns the value string. Returns nil if not found.
|
|
21
|
+
def parse_single_option(args, prefix)
|
|
22
|
+
idx = args.index { |arg| arg.start_with?(prefix) }
|
|
23
|
+
return nil unless idx
|
|
24
|
+
|
|
25
|
+
args.delete_at(idx).delete_prefix(prefix)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Extracts --prefix=a,b,c from args, removes it,
|
|
29
|
+
# and returns an array of strings. Supports semicolons as delimiters.
|
|
30
|
+
def parse_list_option(args, prefix)
|
|
31
|
+
raw = parse_single_option(args, prefix)
|
|
32
|
+
return [] if raw.to_s.empty?
|
|
33
|
+
|
|
34
|
+
delimiter = raw.include?(";") ? ";" : ","
|
|
35
|
+
raw.split(delimiter).map(&:strip).reject(&:empty?)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Extracts --prefix=N from args, removes it,
|
|
39
|
+
# and returns the integer. Returns default on missing/invalid.
|
|
40
|
+
def parse_integer_option(args, prefix, default: 0)
|
|
41
|
+
raw = parse_single_option(args, prefix)
|
|
42
|
+
return default if raw.to_s.empty?
|
|
43
|
+
|
|
44
|
+
Integer(raw)
|
|
45
|
+
rescue ArgumentError
|
|
46
|
+
default
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agentf
|
|
4
|
+
module CLI
|
|
5
|
+
# CLI subcommand for code exploration operations.
|
|
6
|
+
# Refactored from bin/agentf-code with fixes:
|
|
7
|
+
# - Human-readable output now shows actual results, not just counts (finding #13)
|
|
8
|
+
# - Argument parsing uses shared ArgParser module
|
|
9
|
+
class Code
|
|
10
|
+
include ArgParser
|
|
11
|
+
|
|
12
|
+
def initialize(explorer: nil)
|
|
13
|
+
@explorer = explorer || Commands::Explorer.new
|
|
14
|
+
@json_output = false
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def run(args)
|
|
18
|
+
@json_output = !args.delete("--json").nil?
|
|
19
|
+
command = args.shift || "help"
|
|
20
|
+
|
|
21
|
+
case command
|
|
22
|
+
when "glob"
|
|
23
|
+
run_glob(args)
|
|
24
|
+
when "grep"
|
|
25
|
+
run_grep(args)
|
|
26
|
+
when "tree"
|
|
27
|
+
run_tree(args)
|
|
28
|
+
when "related"
|
|
29
|
+
run_related(args)
|
|
30
|
+
when "help", "--help", "-h"
|
|
31
|
+
show_help
|
|
32
|
+
else
|
|
33
|
+
emit_error("Unknown code command: #{command}")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def run_glob(args)
|
|
40
|
+
pattern = args.shift
|
|
41
|
+
return emit_error("glob requires a pattern") if pattern.to_s.empty?
|
|
42
|
+
|
|
43
|
+
file_types = parse_list_option(args, "--types=")
|
|
44
|
+
file_types = nil if file_types.empty?
|
|
45
|
+
|
|
46
|
+
results = @explorer.glob(pattern, file_types: file_types)
|
|
47
|
+
emit_success("glob", { "pattern" => pattern, "matches" => results, "count" => results.length })
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def run_grep(args)
|
|
51
|
+
pattern = args.shift
|
|
52
|
+
return emit_error("grep requires a pattern") if pattern.to_s.empty?
|
|
53
|
+
|
|
54
|
+
file_pattern = parse_single_option(args, "--file-pattern=")
|
|
55
|
+
context_lines = parse_integer_option(args, "--context=", default: 2)
|
|
56
|
+
|
|
57
|
+
matches = @explorer.grep(pattern, file_pattern: file_pattern, context_lines: context_lines)
|
|
58
|
+
serialized = matches.map { |match| match.respond_to?(:to_h) ? match.to_h : match }
|
|
59
|
+
emit_success("grep", { "pattern" => pattern, "matches" => serialized, "count" => serialized.length })
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def run_tree(args)
|
|
63
|
+
max_depth = parse_integer_option(args, "--depth=", default: 3)
|
|
64
|
+
tree = @explorer.get_file_tree(max_depth: max_depth)
|
|
65
|
+
emit_success("tree", { "max_depth" => max_depth, "tree" => tree })
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def run_related(args)
|
|
69
|
+
target_file = args.shift
|
|
70
|
+
return emit_error("related requires a target file") if target_file.to_s.empty?
|
|
71
|
+
|
|
72
|
+
related = @explorer.find_related_files(target_file)
|
|
73
|
+
emit_success("related", { "target_file" => target_file, "related" => related })
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def emit_success(command, payload)
|
|
77
|
+
if @json_output
|
|
78
|
+
puts JSON.generate(payload.merge("command" => command))
|
|
79
|
+
else
|
|
80
|
+
format_human_output(command, payload)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def format_human_output(command, payload)
|
|
85
|
+
count = payload["count"] || 1
|
|
86
|
+
puts "#{command} -> #{count} results"
|
|
87
|
+
|
|
88
|
+
case command
|
|
89
|
+
when "glob"
|
|
90
|
+
payload["matches"]&.each { |f| puts " #{f}" }
|
|
91
|
+
when "grep"
|
|
92
|
+
payload["matches"]&.each do |m|
|
|
93
|
+
if m.is_a?(Hash)
|
|
94
|
+
puts " #{m["path"]}:#{m["line_number"]} #{m["content"]}"
|
|
95
|
+
else
|
|
96
|
+
puts " #{m}"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
when "tree"
|
|
100
|
+
tree = payload["tree"]
|
|
101
|
+
if tree.is_a?(Hash)
|
|
102
|
+
print_tree(tree)
|
|
103
|
+
else
|
|
104
|
+
puts tree
|
|
105
|
+
end
|
|
106
|
+
when "related"
|
|
107
|
+
related = payload["related"]
|
|
108
|
+
if related.is_a?(Hash)
|
|
109
|
+
related.each do |key, values|
|
|
110
|
+
next unless values.is_a?(Array) && !values.empty?
|
|
111
|
+
|
|
112
|
+
puts " #{key}:"
|
|
113
|
+
values.each { |v| puts " #{v}" }
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def print_tree(tree, indent: "")
|
|
120
|
+
tree.each do |name, subtree|
|
|
121
|
+
if subtree.is_a?(Hash)
|
|
122
|
+
puts "#{indent}#{name}/"
|
|
123
|
+
print_tree(subtree, indent: "#{indent} ")
|
|
124
|
+
else
|
|
125
|
+
puts "#{indent}#{name}"
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def emit_error(message)
|
|
131
|
+
if @json_output
|
|
132
|
+
puts JSON.generate({ "error" => message })
|
|
133
|
+
else
|
|
134
|
+
$stderr.puts "Error: #{message}"
|
|
135
|
+
end
|
|
136
|
+
exit 1
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def show_help
|
|
140
|
+
puts <<~HELP
|
|
141
|
+
Usage: agentf code <command> [options]
|
|
142
|
+
|
|
143
|
+
Commands:
|
|
144
|
+
glob <pattern> Find files matching glob pattern
|
|
145
|
+
grep <pattern> Search file contents with regex
|
|
146
|
+
tree Print directory tree
|
|
147
|
+
related <file> Find related/imported files
|
|
148
|
+
|
|
149
|
+
Options:
|
|
150
|
+
--json Output in JSON format
|
|
151
|
+
--types=rb,py Filter glob by file extensions
|
|
152
|
+
--file-pattern=*.rb Grep include pattern
|
|
153
|
+
--context=2 Grep context lines
|
|
154
|
+
--depth=3 Tree max depth
|
|
155
|
+
|
|
156
|
+
Examples:
|
|
157
|
+
agentf code glob "lib/**/*.rb"
|
|
158
|
+
agentf code grep "WorkflowEngine" --file-pattern=*.rb --json
|
|
159
|
+
agentf code tree --depth=2
|
|
160
|
+
agentf code related lib/agentf/workflow_engine.rb
|
|
161
|
+
HELP
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
5
|
+
module Agentf
|
|
6
|
+
module CLI
|
|
7
|
+
# CLI subcommand for generating provider manifests.
|
|
8
|
+
# Wraps the existing Agentf::Installer with CLI argument parsing.
|
|
9
|
+
class Install
|
|
10
|
+
include ArgParser
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@options = {
|
|
14
|
+
providers: ["opencode"],
|
|
15
|
+
scope: "all",
|
|
16
|
+
global_root: Dir.home,
|
|
17
|
+
local_root: Dir.pwd,
|
|
18
|
+
dry_run: false,
|
|
19
|
+
only_agents: nil,
|
|
20
|
+
only_commands: nil
|
|
21
|
+
}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def run(args)
|
|
25
|
+
parse_args(args)
|
|
26
|
+
|
|
27
|
+
if args.include?("help") || args.include?("--help") || args.include?("-h")
|
|
28
|
+
show_help
|
|
29
|
+
return
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
installer = Agentf::Installer.new(
|
|
33
|
+
global_root: @options[:global_root],
|
|
34
|
+
local_root: @options[:local_root],
|
|
35
|
+
dry_run: @options[:dry_run]
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
results = installer.install(
|
|
39
|
+
providers: @options[:providers],
|
|
40
|
+
scope: @options[:scope],
|
|
41
|
+
only_agents: @options[:only_agents],
|
|
42
|
+
only_commands: @options[:only_commands]
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
results.each do |result|
|
|
46
|
+
puts "#{result.fetch("status").upcase}: #{Pathname.new(result.fetch("path")).cleanpath}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
puts "\nCompleted #{results.size} manifest operations."
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def parse_args(args)
|
|
55
|
+
# Extract --dry-run flag
|
|
56
|
+
@options[:dry_run] = !args.delete("--dry-run").nil?
|
|
57
|
+
|
|
58
|
+
# Extract --provider
|
|
59
|
+
provider_val = parse_single_option(args, "--provider=") || parse_single_option(args, "-p=")
|
|
60
|
+
if provider_val
|
|
61
|
+
providers = provider_val.split(",").map { |item| item.strip.downcase }.reject(&:empty?)
|
|
62
|
+
@options[:providers] = providers == ["all"] ? Agentf::Installer::PROVIDER_LAYOUTS.keys : providers
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Extract --scope
|
|
66
|
+
scope_val = parse_single_option(args, "--scope=") || parse_single_option(args, "-s=")
|
|
67
|
+
@options[:scope] = scope_val.downcase if scope_val
|
|
68
|
+
|
|
69
|
+
# Extract --global-root and --local-root
|
|
70
|
+
global_root = parse_single_option(args, "--global-root=")
|
|
71
|
+
@options[:global_root] = File.expand_path(global_root) if global_root
|
|
72
|
+
|
|
73
|
+
local_root = parse_single_option(args, "--local-root=")
|
|
74
|
+
@options[:local_root] = File.expand_path(local_root) if local_root
|
|
75
|
+
|
|
76
|
+
# Extract --agent and --command filters
|
|
77
|
+
agent_val = parse_single_option(args, "--agent=")
|
|
78
|
+
if agent_val
|
|
79
|
+
@options[:only_agents] = agent_val.split(",").map { |item| item.strip.downcase }.reject(&:empty?)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
command_val = parse_single_option(args, "--command=")
|
|
83
|
+
if command_val
|
|
84
|
+
@options[:only_commands] = command_val.split(",").map { |item| item.strip.downcase }.reject(&:empty?)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def show_help
|
|
89
|
+
puts <<~HELP
|
|
90
|
+
Usage: agentf install [options]
|
|
91
|
+
|
|
92
|
+
Generates provider-specific agent and command manifests.
|
|
93
|
+
|
|
94
|
+
Options:
|
|
95
|
+
--provider=LIST Providers: opencode,copilot,all (default: opencode)
|
|
96
|
+
--scope=SCOPE Install scope: global|local|all (default: all)
|
|
97
|
+
--global-root=PATH Root for global installs (default: $HOME)
|
|
98
|
+
--local-root=PATH Root for local installs (default: current directory)
|
|
99
|
+
--agent=LIST Only install specific agents (comma-separated)
|
|
100
|
+
--command=LIST Only install specific commands (comma-separated)
|
|
101
|
+
--dry-run Show planned writes without writing files
|
|
102
|
+
|
|
103
|
+
Examples:
|
|
104
|
+
agentf install
|
|
105
|
+
agentf install --provider=opencode,copilot --scope=local
|
|
106
|
+
agentf install --provider=copilot --dry-run
|
|
107
|
+
agentf install --agent=architect,specialist
|
|
108
|
+
HELP
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|