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.
@@ -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
+ }