yatfa 1.0.65
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.
- package/LICENSE +21 -0
- package/README.md +252 -0
- package/agents.rb +198 -0
- package/bin/agent-bridge-tmux +63 -0
- package/bin/install-agent.sh +144 -0
- package/bin/run-yatfa-agent.rb +69 -0
- package/lib/yatfa_agent/agent.rb +289 -0
- package/lib/yatfa_agent/config.rb +164 -0
- package/lib/yatfa_agent/docker.rb +72 -0
- package/lib/yatfa_agent/setup.rb +601 -0
- package/package.json +18 -0
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "uri"
|
|
5
|
+
require "json"
|
|
6
|
+
require "readline"
|
|
7
|
+
require "fileutils"
|
|
8
|
+
|
|
9
|
+
module YatfaAgent
|
|
10
|
+
module Setup
|
|
11
|
+
class SetupWizard
|
|
12
|
+
attr_reader :config
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
@config = {}
|
|
16
|
+
@errors = []
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def run
|
|
20
|
+
print_banner
|
|
21
|
+
|
|
22
|
+
# Set default YATFA URL
|
|
23
|
+
@config[:yatfa_url] = "https://yatfa.ai"
|
|
24
|
+
|
|
25
|
+
# Track step number dynamically
|
|
26
|
+
@step_number = 0
|
|
27
|
+
|
|
28
|
+
# Step 1: Project Name
|
|
29
|
+
prompt_project_name
|
|
30
|
+
|
|
31
|
+
# Step 2: Agent API Keys
|
|
32
|
+
prompt_agent_keys
|
|
33
|
+
|
|
34
|
+
# Git configuration (might skip if exists)
|
|
35
|
+
prompt_git_config # This method handles its own step numbering
|
|
36
|
+
|
|
37
|
+
# GitHub configuration
|
|
38
|
+
prompt_github_config
|
|
39
|
+
|
|
40
|
+
# Write configuration
|
|
41
|
+
write_config
|
|
42
|
+
|
|
43
|
+
# Create Dockerfile.sandbox
|
|
44
|
+
create_dockerfile_sandbox
|
|
45
|
+
|
|
46
|
+
print_success
|
|
47
|
+
rescue SetupError => e
|
|
48
|
+
puts "\n❌ #{e.message}"
|
|
49
|
+
exit 1
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def print_banner
|
|
55
|
+
puts ""
|
|
56
|
+
puts "╔═══════════════════════════════════════════════════════════════╗"
|
|
57
|
+
puts "║ ║"
|
|
58
|
+
puts "║ YATFA Agent Setup Wizard ║"
|
|
59
|
+
puts "║ (Yet Another Tool For Agents) ║"
|
|
60
|
+
puts "║ ║"
|
|
61
|
+
puts "║ This wizard will guide you through configuring yatfa-agent ║"
|
|
62
|
+
puts "║ ║"
|
|
63
|
+
puts "╚═══════════════════════════════════════════════════════════════╝"
|
|
64
|
+
puts ""
|
|
65
|
+
puts "You'll need:"
|
|
66
|
+
puts " • Agent API keys (from YATFA web UI)"
|
|
67
|
+
puts " • GitHub credentials (optional, can skip)"
|
|
68
|
+
puts ""
|
|
69
|
+
puts "Press Ctrl+C at any time to cancel."
|
|
70
|
+
puts ""
|
|
71
|
+
puts "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
72
|
+
puts ""
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def prompt_project_name
|
|
76
|
+
@step_number += 1
|
|
77
|
+
puts "📦 Step #{@step_number}: Project Name"
|
|
78
|
+
puts ""
|
|
79
|
+
puts "Enter a name for this project. This will be used as a prefix for"
|
|
80
|
+
puts "agent container names (e.g., 'panoptex-worker')."
|
|
81
|
+
puts ""
|
|
82
|
+
|
|
83
|
+
# Try to get project name from current directory
|
|
84
|
+
dir_name = File.basename(Dir.pwd)
|
|
85
|
+
print "Project name [#{dir_name}]: "
|
|
86
|
+
name = Readline.readline.strip
|
|
87
|
+
name = dir_name if name.empty?
|
|
88
|
+
|
|
89
|
+
# Validate project name (alphanumeric, dashes, underscores only)
|
|
90
|
+
unless name.match?(/\A[a-z0-9_-]+\z/i)
|
|
91
|
+
raise SetupError, "Project name must contain only letters, numbers, dashes, and underscores"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
@config[:project_name] = name.downcase.gsub(/[^a-z0-9_-]/, '_')
|
|
95
|
+
puts "✅ Project name set to: #{@config[:project_name]}"
|
|
96
|
+
puts ""
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def prompt_agent_keys
|
|
100
|
+
@step_number += 1
|
|
101
|
+
puts "🔑 Step #{@step_number}: Agent API Keys"
|
|
102
|
+
puts ""
|
|
103
|
+
puts "Enter API keys for the agents you want to use."
|
|
104
|
+
puts "Get these from the YATFA web UI (Agents section)."
|
|
105
|
+
puts ""
|
|
106
|
+
|
|
107
|
+
@config[:agents] = {}
|
|
108
|
+
|
|
109
|
+
agent_types = ["worker", "planner", "reviewer", "researcher"]
|
|
110
|
+
agent_types.each do |agent_type|
|
|
111
|
+
print "#{agent_type.capitalize} API key (press Enter to skip): "
|
|
112
|
+
key = STDIN.noecho(&:gets).chomp.strip
|
|
113
|
+
puts ""
|
|
114
|
+
|
|
115
|
+
next if key.empty?
|
|
116
|
+
|
|
117
|
+
# Validate API key with backend
|
|
118
|
+
unless validate_api_key(key, @config[:yatfa_url])
|
|
119
|
+
puts "⚠️ Warning: API key validation failed for #{agent_type}"
|
|
120
|
+
print "Still use this key? [y/N]: "
|
|
121
|
+
confirm = Readline.readline.strip.downcase
|
|
122
|
+
next unless confirm == "y"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
@config[:agents][agent_type.to_sym] = {
|
|
126
|
+
mcp_api_key: key,
|
|
127
|
+
container_name: "#{@config[:project_name]}-#{agent_type}"
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
puts "✅ #{agent_type.capitalize} API key validated"
|
|
131
|
+
puts ""
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
if @config[:agents].empty?
|
|
135
|
+
raise SetupError, "At least one agent API key is required"
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def prompt_git_config
|
|
140
|
+
# Try to read from existing yatfa.env.rb
|
|
141
|
+
existing_config = File.join(Dir.pwd, "yatfa.env.rb")
|
|
142
|
+
|
|
143
|
+
if File.exist?(existing_config)
|
|
144
|
+
begin
|
|
145
|
+
loaded = eval(File.read(existing_config), binding, existing_config)
|
|
146
|
+
if loaded[:git] && loaded[:git][:user_name] && loaded[:git][:user_email]
|
|
147
|
+
@config[:git] = {
|
|
148
|
+
user_name: loaded[:git][:user_name],
|
|
149
|
+
user_email: loaded[:git][:user_email]
|
|
150
|
+
}
|
|
151
|
+
return false # Skipped
|
|
152
|
+
end
|
|
153
|
+
rescue StandardError
|
|
154
|
+
# If parsing fails, fall through to prompt
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Set defaults from parent directory if available
|
|
159
|
+
default_name = "YATFA Agent"
|
|
160
|
+
default_email = "yatfa-agent@yatfa.ai"
|
|
161
|
+
|
|
162
|
+
parent_config = File.join(Dir.pwd, "..", "yatfa.env.rb")
|
|
163
|
+
if File.exist?(parent_config)
|
|
164
|
+
begin
|
|
165
|
+
loaded = eval(File.read(parent_config), binding, parent_config)
|
|
166
|
+
if loaded[:git] && loaded[:git][:user_name]
|
|
167
|
+
default_name = loaded[:git][:user_name]
|
|
168
|
+
end
|
|
169
|
+
if loaded[:git] && loaded[:git][:user_email]
|
|
170
|
+
default_email = loaded[:git][:user_email]
|
|
171
|
+
end
|
|
172
|
+
rescue StandardError
|
|
173
|
+
# If parsing fails, use hardcoded defaults
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
@step_number += 1 # Increment before showing this step
|
|
178
|
+
puts "🔧 Step #{@step_number}: Git Configuration"
|
|
179
|
+
puts ""
|
|
180
|
+
puts "Configure git identity for commits made by agents."
|
|
181
|
+
puts ""
|
|
182
|
+
|
|
183
|
+
print "Git user name [#{default_name}]: "
|
|
184
|
+
name = Readline.readline.strip
|
|
185
|
+
name = default_name if name.empty?
|
|
186
|
+
|
|
187
|
+
print "Git user email [#{default_email}]: "
|
|
188
|
+
email = Readline.readline.strip
|
|
189
|
+
email = default_email if email.empty?
|
|
190
|
+
|
|
191
|
+
@config[:git] = {
|
|
192
|
+
user_name: name,
|
|
193
|
+
user_email: email
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
puts "✅ Git configuration set"
|
|
197
|
+
puts ""
|
|
198
|
+
true # Prompted
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def prompt_github_config
|
|
202
|
+
@step_number += 1
|
|
203
|
+
puts "🐙 Step #{@step_number}: GitHub Configuration"
|
|
204
|
+
puts ""
|
|
205
|
+
puts "Choose authentication method:"
|
|
206
|
+
puts " 1) Personal Access Token (recommended for individuals)"
|
|
207
|
+
puts " 2) GitHub App (for organizations)"
|
|
208
|
+
puts " 3) Skip (configure later in yatfa.env.rb)"
|
|
209
|
+
puts ""
|
|
210
|
+
|
|
211
|
+
print "Choice [1]: "
|
|
212
|
+
choice = Readline.readline.strip
|
|
213
|
+
choice = "1" if choice.empty?
|
|
214
|
+
|
|
215
|
+
case choice
|
|
216
|
+
when "1"
|
|
217
|
+
setup_github_token
|
|
218
|
+
when "2"
|
|
219
|
+
setup_github_app
|
|
220
|
+
when "3"
|
|
221
|
+
puts "⊘ Skipped GitHub configuration"
|
|
222
|
+
puts ""
|
|
223
|
+
return
|
|
224
|
+
else
|
|
225
|
+
raise SetupError, "Invalid choice. Please enter 1, 2, or 3."
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Validate GitHub credentials (with retry loop)
|
|
229
|
+
github_valid = false
|
|
230
|
+
until github_valid
|
|
231
|
+
if validate_github_credentials
|
|
232
|
+
github_valid = true
|
|
233
|
+
else
|
|
234
|
+
puts "⚠️ Warning: GitHub credential validation failed"
|
|
235
|
+
print "Still use this configuration? [y/N]: "
|
|
236
|
+
confirm = Readline.readline.strip.downcase
|
|
237
|
+
if confirm == "y"
|
|
238
|
+
github_valid = true
|
|
239
|
+
else
|
|
240
|
+
# Prompt again for GitHub config
|
|
241
|
+
puts ""
|
|
242
|
+
puts "Let's try again..."
|
|
243
|
+
prompt_github_config
|
|
244
|
+
return
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
puts "✅ GitHub configuration validated"
|
|
250
|
+
puts ""
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def setup_github_token
|
|
254
|
+
puts ""
|
|
255
|
+
puts "Create a Personal Access Token at:"
|
|
256
|
+
puts "https://github.com/settings/tokens"
|
|
257
|
+
puts "Required scopes: repo, workflow"
|
|
258
|
+
puts ""
|
|
259
|
+
|
|
260
|
+
print "GitHub token: "
|
|
261
|
+
token = STDIN.noecho(&:gets).chomp.strip
|
|
262
|
+
puts ""
|
|
263
|
+
|
|
264
|
+
if token.empty?
|
|
265
|
+
raise SetupError, "GitHub token cannot be empty"
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
@config[:github] = {
|
|
269
|
+
method: "token",
|
|
270
|
+
token: token
|
|
271
|
+
}
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def setup_github_app
|
|
275
|
+
puts ""
|
|
276
|
+
puts "GitHub App credentials from your organization settings:"
|
|
277
|
+
puts ""
|
|
278
|
+
|
|
279
|
+
print "App Client ID: "
|
|
280
|
+
client_id = Readline.readline.strip
|
|
281
|
+
|
|
282
|
+
print "App Installation ID: "
|
|
283
|
+
installation_id = Readline.readline.strip
|
|
284
|
+
|
|
285
|
+
print "Path to App Private Key (.pem file): "
|
|
286
|
+
key_path = Readline.readline.strip
|
|
287
|
+
|
|
288
|
+
if client_id.empty? || installation_id.empty? || key_path.empty?
|
|
289
|
+
raise SetupError, "All GitHub App fields are required"
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
unless File.exist?(key_path)
|
|
293
|
+
raise SetupError, "Private key file not found: #{key_path}"
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
@config[:github] = {
|
|
297
|
+
method: "app",
|
|
298
|
+
app_client_id: client_id,
|
|
299
|
+
app_installation_id: installation_id,
|
|
300
|
+
app_private_key_path: File.expand_path(key_path)
|
|
301
|
+
}
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def write_config
|
|
305
|
+
puts ""
|
|
306
|
+
@step_number += 1
|
|
307
|
+
puts "📝 Step #{@step_number}: Writing Configuration"
|
|
308
|
+
puts ""
|
|
309
|
+
|
|
310
|
+
config_file = File.join(Dir.pwd, "yatfa.env.rb")
|
|
311
|
+
|
|
312
|
+
# Check if file already exists
|
|
313
|
+
if File.exist?(config_file)
|
|
314
|
+
puts "⚠️ Warning: yatfa.env.rb already exists"
|
|
315
|
+
print "Backup existing file and overwrite? [y/N]: "
|
|
316
|
+
confirm = Readline.readline.strip.downcase
|
|
317
|
+
|
|
318
|
+
unless confirm == "y"
|
|
319
|
+
raise SetupError, "Configuration file already exists. Please remove it first or choose a different location."
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Backup existing file
|
|
323
|
+
backup_path = "#{config_file}.backup.#{Time.now.to_i}"
|
|
324
|
+
FileUtils.cp(config_file, backup_path)
|
|
325
|
+
puts "📦 Backed up existing file to: #{backup_path}"
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# Generate configuration file content
|
|
329
|
+
content = generate_config_content
|
|
330
|
+
|
|
331
|
+
# Write file
|
|
332
|
+
File.write(config_file, content)
|
|
333
|
+
|
|
334
|
+
# Validate syntax
|
|
335
|
+
unless valid_ruby_syntax?(config_file)
|
|
336
|
+
File.delete(config_file)
|
|
337
|
+
raise SetupError, "Generated configuration has invalid Ruby syntax"
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
puts "✅ Configuration written to: #{config_file}"
|
|
341
|
+
puts ""
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def generate_config_content
|
|
345
|
+
lines = []
|
|
346
|
+
lines << "{"
|
|
347
|
+
lines << " yatfa_url: #{@config[:yatfa_url].inspect},"
|
|
348
|
+
lines << ""
|
|
349
|
+
|
|
350
|
+
# Git config
|
|
351
|
+
lines << " git: {"
|
|
352
|
+
lines << " user_name: #{@config[:git][:user_name].inspect},"
|
|
353
|
+
lines << " user_email: #{@config[:git][:user_email].inspect}"
|
|
354
|
+
lines << " },"
|
|
355
|
+
lines << ""
|
|
356
|
+
|
|
357
|
+
# GitHub config (optional)
|
|
358
|
+
if @config[:github]
|
|
359
|
+
lines << " github: {"
|
|
360
|
+
lines << " method: #{@config[:github][:method].inspect},"
|
|
361
|
+
if @config[:github][:method] == "token"
|
|
362
|
+
lines << " token: #{@config[:github][:token].inspect}"
|
|
363
|
+
else
|
|
364
|
+
lines << " app_client_id: #{@config[:github][:app_client_id].inspect},"
|
|
365
|
+
lines << " app_installation_id: #{@config[:github][:app_installation_id].inspect},"
|
|
366
|
+
lines << " app_private_key_path: #{@config[:github][:app_private_key_path].inspect}"
|
|
367
|
+
end
|
|
368
|
+
lines << " },"
|
|
369
|
+
lines << ""
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# Agents config
|
|
373
|
+
lines << " agents: {"
|
|
374
|
+
@config[:agents].each_with_index do |(agent_type, agent_config), idx|
|
|
375
|
+
lines << " #{agent_type}: {"
|
|
376
|
+
lines << " mcp_api_key: #{agent_config[:mcp_api_key].inspect},"
|
|
377
|
+
lines << " container_name: #{agent_config[:container_name].inspect}"
|
|
378
|
+
lines << " }#{',' if idx < @config[:agents].size - 1}"
|
|
379
|
+
end
|
|
380
|
+
lines << " }"
|
|
381
|
+
lines << ""
|
|
382
|
+
|
|
383
|
+
# Environment variables
|
|
384
|
+
if @config[:dot_env]
|
|
385
|
+
lines << " dot_env: <<~ENV"
|
|
386
|
+
lines << @config[:dot_env]
|
|
387
|
+
lines << " ENV"
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
lines << "}"
|
|
391
|
+
|
|
392
|
+
lines.join("\n")
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def create_dockerfile_sandbox
|
|
396
|
+
puts ""
|
|
397
|
+
@step_number += 1
|
|
398
|
+
puts "🐳 Step #{@step_number}: Creating Dockerfile.sandbox"
|
|
399
|
+
puts ""
|
|
400
|
+
|
|
401
|
+
dockerfile_path = File.join(Dir.pwd, "Dockerfile.sandbox")
|
|
402
|
+
|
|
403
|
+
# Check if file already exists
|
|
404
|
+
if File.exist?(dockerfile_path)
|
|
405
|
+
puts "⚠️ Warning: Dockerfile.sandbox already exists"
|
|
406
|
+
print "Backup existing file and overwrite? [y/N]: "
|
|
407
|
+
confirm = Readline.readline.strip.downcase
|
|
408
|
+
|
|
409
|
+
unless confirm == "y"
|
|
410
|
+
puts "⊘ Skipped Dockerfile.sandbox creation (using existing file)"
|
|
411
|
+
return
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
# Backup existing file
|
|
415
|
+
backup_path = "#{dockerfile_path}.backup.#{Time.now.to_i}"
|
|
416
|
+
FileUtils.cp(dockerfile_path, backup_path)
|
|
417
|
+
puts "📦 Backed up existing file to: #{backup_path}"
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
# Generate Dockerfile.sandbox content
|
|
421
|
+
content = generate_dockerfile_content
|
|
422
|
+
|
|
423
|
+
# Write file
|
|
424
|
+
File.write(dockerfile_path, content)
|
|
425
|
+
|
|
426
|
+
puts "✅ Dockerfile.sandbox created: #{dockerfile_path}"
|
|
427
|
+
puts ""
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
def generate_dockerfile_content
|
|
431
|
+
<<~DOCKERFILE
|
|
432
|
+
# syntax=docker/dockerfile:1
|
|
433
|
+
# YATFA SANDBOX Dockerfile
|
|
434
|
+
# This is a standalone sandbox environment for YATFA agents.
|
|
435
|
+
# It does NOT include the yatfa application source code.
|
|
436
|
+
# Agents will clone repositories as needed via the multi-repo setup.
|
|
437
|
+
|
|
438
|
+
ARG RUBY_VERSION=3.4.1
|
|
439
|
+
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base
|
|
440
|
+
|
|
441
|
+
# Workspace directory (repositories will be cloned here)
|
|
442
|
+
WORKDIR /workspace
|
|
443
|
+
|
|
444
|
+
# Install base packages and development tools
|
|
445
|
+
RUN apt-get update -qq -o Acquire::Check-Date=false && \\
|
|
446
|
+
apt-get install --no-install-recommends -y \\
|
|
447
|
+
curl libjemalloc2 libvips postgresql-client \\
|
|
448
|
+
build-essential git libpq-dev libyaml-dev pkg-config unzip python3 \\
|
|
449
|
+
chromium chromium-driver sudo && \\
|
|
450
|
+
ln -s /usr/lib/$(uname -m)-linux-gnu/libjemalloc.so.2 /usr/local/lib/libjemalloc.so && \\
|
|
451
|
+
rm -rf /var/lib/apt/lists /var/cache/apt/archives
|
|
452
|
+
|
|
453
|
+
ENV LD_PRELOAD="/usr/local/lib/libjemalloc.so"
|
|
454
|
+
|
|
455
|
+
# Install Bun (optional, for projects that use it)
|
|
456
|
+
ENV BUN_INSTALL=/usr/local/bun
|
|
457
|
+
ENV PATH=/usr/local/bun/bin:$PATH
|
|
458
|
+
ARG BUN_VERSION=1.2.19
|
|
459
|
+
RUN curl -fsSL https://bun.sh/install | bash -s -- "bun-v${BUN_VERSION}" && \\
|
|
460
|
+
ln -s /usr/local/bun/bin/bun /usr/local/bin/bun && \\
|
|
461
|
+
ln -s /usr/local/bun/bin/bunx /usr/local/bin/bunx
|
|
462
|
+
|
|
463
|
+
# Install Node.js (required for Claude CLI)
|
|
464
|
+
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \\
|
|
465
|
+
apt-get install -y nodejs && \\
|
|
466
|
+
rm -rf /var/lib/apt/lists /var/cache/apt/archives
|
|
467
|
+
|
|
468
|
+
# --- YATFA AGENT SETUP ---
|
|
469
|
+
ARG YATFA_VERSION=main
|
|
470
|
+
ARG USER_ID=1000
|
|
471
|
+
ARG GROUP_ID=1000
|
|
472
|
+
|
|
473
|
+
# Cache busting: only rebuilds when install-agent.sh infrastructure changes
|
|
474
|
+
ADD https://objectstore.fra1.civo.com/yatfa/${YATFA_VERSION}/version.json /tmp/yatfa_version.json
|
|
475
|
+
|
|
476
|
+
# Install YATFA agent infrastructure
|
|
477
|
+
# This layer is cached unless install-agent.sh changes (detected via version.json above)
|
|
478
|
+
# setup-agent.rb, skills, and hooks download fresh on every container startup
|
|
479
|
+
RUN curl -fsSL https://objectstore.fra1.civo.com/yatfa/${YATFA_VERSION}/bin/install-agent.sh | bash
|
|
480
|
+
|
|
481
|
+
# Ensure /workspace is owned by agent user and writable
|
|
482
|
+
# The install-agent.sh script creates a user with USER_ID, so we use that ID directly
|
|
483
|
+
RUN rm -rf /workspace && mkdir -p /workspace && chown -R ${USER_ID}:${GROUP_ID} /home/claude /workspace
|
|
484
|
+
|
|
485
|
+
# Switch to agent user (will be created with USER_ID by install-agent.sh)
|
|
486
|
+
USER ${USER_ID}
|
|
487
|
+
WORKDIR /workspace
|
|
488
|
+
|
|
489
|
+
# Set default environment
|
|
490
|
+
ENV HOME=/home/claude
|
|
491
|
+
ENV PATH=/home/claude/.local/bin:/usr/local/bun/bin:/usr/bin:/bin:$PATH
|
|
492
|
+
|
|
493
|
+
# Default command runs setup-agent wrapper
|
|
494
|
+
# The wrapper downloads the latest setup-agent.rb on every container startup
|
|
495
|
+
# This allows setup-agent.rb, skills, and hooks to update without rebuilding the image
|
|
496
|
+
CMD ["/usr/local/bin/setup-agent"]
|
|
497
|
+
DOCKERFILE
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
def print_success
|
|
501
|
+
puts "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
502
|
+
puts ""
|
|
503
|
+
puts "🎉 Setup completed successfully!"
|
|
504
|
+
puts ""
|
|
505
|
+
puts "Next steps:"
|
|
506
|
+
puts ""
|
|
507
|
+
puts " 1. Ensure 'yatfa.env.rb' is in .gitignore:"
|
|
508
|
+
puts " echo 'yatfa.env.rb' >> .gitignore"
|
|
509
|
+
puts ""
|
|
510
|
+
puts " 2. Build the sandbox Docker image:"
|
|
511
|
+
puts " docker build -f Dockerfile.sandbox -t yatfa-sandbox ."
|
|
512
|
+
puts ""
|
|
513
|
+
puts " 3. Start an agent:"
|
|
514
|
+
if @config[:agents].key?(:worker)
|
|
515
|
+
puts " npx yatfa-agent worker"
|
|
516
|
+
elsif @config[:agents].key?(:planner)
|
|
517
|
+
puts " npx yatfa-agent planner"
|
|
518
|
+
else
|
|
519
|
+
agent = @config[:agents].keys.first
|
|
520
|
+
puts " npx yatfa-agent #{agent}"
|
|
521
|
+
end
|
|
522
|
+
puts ""
|
|
523
|
+
puts "For more information, see: https://github.com/RoM4iK/yatfa-public"
|
|
524
|
+
puts ""
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
# Helper methods
|
|
528
|
+
|
|
529
|
+
def validate_api_key(api_key, yatfa_url)
|
|
530
|
+
# Derive API URL
|
|
531
|
+
api_base = "#{yatfa_url}/api/v1"
|
|
532
|
+
uri = URI("#{api_base}/whoami")
|
|
533
|
+
|
|
534
|
+
begin
|
|
535
|
+
response = Net::HTTP.get_response(
|
|
536
|
+
uri,
|
|
537
|
+
{ "X-API-Key" => api_key, "Accept" => "application/json" }
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
response.code == "200"
|
|
541
|
+
rescue StandardError => e
|
|
542
|
+
@errors << "API key validation failed: #{e.message}"
|
|
543
|
+
false
|
|
544
|
+
end
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
def validate_github_credentials
|
|
548
|
+
# Test GitHub authentication
|
|
549
|
+
begin
|
|
550
|
+
if @config[:github][:method] == "token"
|
|
551
|
+
validate_github_token
|
|
552
|
+
else
|
|
553
|
+
validate_github_app
|
|
554
|
+
end
|
|
555
|
+
rescue StandardError => e
|
|
556
|
+
@errors << "GitHub validation failed: #{e.message}"
|
|
557
|
+
false
|
|
558
|
+
end
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
def validate_github_token
|
|
562
|
+
require "open3"
|
|
563
|
+
|
|
564
|
+
token = @config[:github][:token]
|
|
565
|
+
|
|
566
|
+
# Use gh CLI or direct API call
|
|
567
|
+
_, _, status = Open3.capture3(
|
|
568
|
+
"gh", "auth", "status",
|
|
569
|
+
stdin_data: ""
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
# If gh CLI is not configured, try direct API call
|
|
573
|
+
unless status.success?
|
|
574
|
+
uri = URI("https://api.github.com/user")
|
|
575
|
+
response = Net::HTTP.get_response(
|
|
576
|
+
uri,
|
|
577
|
+
{ "Authorization" => "Bearer #{token}", "Accept" => "application/json" }
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
return response.code == "200"
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
true
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
def validate_github_app
|
|
587
|
+
# GitHub App validation is more complex - just check file exists
|
|
588
|
+
# Actual validation happens when the agent tries to use it
|
|
589
|
+
File.exist?(@config[:github][:app_private_key_path])
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
def valid_ruby_syntax?(file)
|
|
593
|
+
require "open3"
|
|
594
|
+
_, _, status = Open3.capture3("ruby", "-c", file)
|
|
595
|
+
status.success?
|
|
596
|
+
end
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
class SetupError < StandardError; end
|
|
600
|
+
end
|
|
601
|
+
end
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "yatfa",
|
|
3
|
+
"version": "1.0.65",
|
|
4
|
+
"description": "YATFA - Yet Another Tool For Agents",
|
|
5
|
+
"bin": "./bin/run-yatfa-agent.rb",
|
|
6
|
+
"files": [
|
|
7
|
+
"bin/run-yatfa-agent.rb",
|
|
8
|
+
"agents.rb",
|
|
9
|
+
"lib/",
|
|
10
|
+
"bin/agent-bridge-tmux",
|
|
11
|
+
"bin/install-agent.sh"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"test": "ruby spec/setup_spec.rb"
|
|
15
|
+
},
|
|
16
|
+
"author": "",
|
|
17
|
+
"license": "ISC"
|
|
18
|
+
}
|