tinker-agent 1.0.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.
- package/LICENSE +21 -0
- package/README.md +89 -0
- package/agents.rb +457 -0
- package/bin/agent-bridge-tmux +56 -0
- package/bin/install-agent.sh +95 -0
- package/package.json +20 -0
- package/run-tinker-agent.rb +255 -0
- package/setup-agent.rb +399 -0
package/setup-agent.rb
ADDED
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Tinker Agent - Run inside any Docker container with Ruby
|
|
5
|
+
#
|
|
6
|
+
# This script sets up and runs a Tinker agent. It:
|
|
7
|
+
# 1. Generates MCP config from environment variables
|
|
8
|
+
# 2. Creates CLAUDE.md with role-specific instructions
|
|
9
|
+
# 3. Downloads and runs agent-bridge
|
|
10
|
+
#
|
|
11
|
+
# Requirements (install in your container):
|
|
12
|
+
# - Ruby 3.x
|
|
13
|
+
# - Node.js 20+
|
|
14
|
+
# - tmux
|
|
15
|
+
# - git, curl, gh (GitHub CLI)
|
|
16
|
+
# - claude CLI: npm install -g @anthropic-ai/claude-code
|
|
17
|
+
#
|
|
18
|
+
# Environment variables:
|
|
19
|
+
# AGENT_TYPE - worker|planner|reviewer|orchestrator|researcher
|
|
20
|
+
# PROJECT_ID - Your Tinker project ID
|
|
21
|
+
# RAILS_WS_URL - WebSocket URL (wss://tinker.example.com/cable)
|
|
22
|
+
# RAILS_API_URL - API URL (https://tinker.example.com/api/v1)
|
|
23
|
+
# RAILS_API_KEY - MCP API key for this agent type
|
|
24
|
+
# GH_TOKEN - GitHub token (or use GitHub App auth)
|
|
25
|
+
#
|
|
26
|
+
# Usage:
|
|
27
|
+
# curl -fsSL https://raw.githubusercontent.com/RoM4iK/tinker-public/main/tinker-agent.rb | ruby
|
|
28
|
+
|
|
29
|
+
require "json"
|
|
30
|
+
require "fileutils"
|
|
31
|
+
require "open-uri"
|
|
32
|
+
require "openssl"
|
|
33
|
+
require "net/http"
|
|
34
|
+
require "uri"
|
|
35
|
+
require "base64"
|
|
36
|
+
require "time"
|
|
37
|
+
|
|
38
|
+
TINKER_VERSION = ENV["TINKER_VERSION"] || "main"
|
|
39
|
+
TINKER_RAW_URL = "https://raw.githubusercontent.com/RoM4iK/tinker-public/#{TINKER_VERSION}"
|
|
40
|
+
|
|
41
|
+
# Agent banners - role-specific instructions for Claude
|
|
42
|
+
AGENT_BANNERS = {
|
|
43
|
+
"planner" => <<~BANNER,
|
|
44
|
+
╔════════════════════════════════════════════════════════════════════════════╗
|
|
45
|
+
║ TINKER PLANNER - ROLE ENFORCEMENT ║
|
|
46
|
+
╠════════════════════════════════════════════════════════════════════════════╣
|
|
47
|
+
║ YOUR ROLE: INTERACTIVE PLANNING AND TICKET CREATION ║
|
|
48
|
+
║ YOUR MODE: CHAT WITH HUMAN - DISCUSS, PLAN, CREATE TICKETS ║
|
|
49
|
+
╚════════════════════════════════════════════════════════════════════════════╝
|
|
50
|
+
|
|
51
|
+
This session is running as the TINKER PLANNER agent in INTERACTIVE CHAT MODE.
|
|
52
|
+
|
|
53
|
+
CORE RESPONSIBILITIES:
|
|
54
|
+
✓ Discuss feature ideas and requirements with the human
|
|
55
|
+
✓ Break down large features into implementable tickets
|
|
56
|
+
✓ Write clear ticket descriptions with acceptance criteria
|
|
57
|
+
✓ Create tickets using create_ticket MCP tool when plans are confirmed
|
|
58
|
+
BANNER
|
|
59
|
+
|
|
60
|
+
"worker" => <<~BANNER,
|
|
61
|
+
╔════════════════════════════════════════════════════════════════════════════╗
|
|
62
|
+
║ TINKER WORKER - ROLE ENFORCEMENT ║
|
|
63
|
+
╠════════════════════════════════════════════════════════════════════════════╣
|
|
64
|
+
║ YOUR ROLE: AUTONOMOUS CODE IMPLEMENTATION ║
|
|
65
|
+
║ YOUR MODE: WORK AUTONOMOUSLY ON ASSIGNED TICKETS ║
|
|
66
|
+
╚════════════════════════════════════════════════════════════════════════════╝
|
|
67
|
+
|
|
68
|
+
This session is running as the TINKER WORKER agent in AUTONOMOUS MODE.
|
|
69
|
+
|
|
70
|
+
CORE RESPONSIBILITIES:
|
|
71
|
+
✓ Check for assigned tickets using get_my_tickets MCP tool
|
|
72
|
+
✓ Implement code changes according to ticket specifications
|
|
73
|
+
✓ Create branches, commits, and pull requests
|
|
74
|
+
✓ Update ticket status as you progress
|
|
75
|
+
BANNER
|
|
76
|
+
|
|
77
|
+
"reviewer" => <<~BANNER,
|
|
78
|
+
╔════════════════════════════════════════════════════════════════════════════╗
|
|
79
|
+
║ TINKER REVIEWER - ROLE ENFORCEMENT ║
|
|
80
|
+
╠════════════════════════════════════════════════════════════════════════════╣
|
|
81
|
+
║ YOUR ROLE: AUTONOMOUS CODE REVIEW ║
|
|
82
|
+
║ YOUR MODE: REVIEW PULL REQUESTS AND PROVIDE FEEDBACK ║
|
|
83
|
+
╚════════════════════════════════════════════════════════════════════════════╝
|
|
84
|
+
|
|
85
|
+
This session is running as the TINKER REVIEWER agent in AUTONOMOUS MODE.
|
|
86
|
+
|
|
87
|
+
CORE RESPONSIBILITIES:
|
|
88
|
+
✓ Check for PRs awaiting review
|
|
89
|
+
✓ Review code quality, tests, and documentation
|
|
90
|
+
✓ Approve or request changes with clear feedback
|
|
91
|
+
BANNER
|
|
92
|
+
|
|
93
|
+
"orchestrator" => <<~BANNER,
|
|
94
|
+
╔════════════════════════════════════════════════════════════════════════════╗
|
|
95
|
+
║ TINKER ORCHESTRATOR - ROLE ENFORCEMENT ║
|
|
96
|
+
╠════════════════════════════════════════════════════════════════════════════╣
|
|
97
|
+
║ YOUR ROLE: AUTONOMOUS WORK COORDINATION ║
|
|
98
|
+
║ YOUR MODE: ASSIGN TICKETS AND MANAGE WORKFLOW ║
|
|
99
|
+
╚════════════════════════════════════════════════════════════════════════════╝
|
|
100
|
+
|
|
101
|
+
This session is running as the TINKER ORCHESTRATOR agent in AUTONOMOUS MODE.
|
|
102
|
+
|
|
103
|
+
CORE RESPONSIBILITIES:
|
|
104
|
+
✓ Monitor ticket queue and agent availability
|
|
105
|
+
✓ Assign tickets to workers based on capacity
|
|
106
|
+
✓ Track progress and handle blockers
|
|
107
|
+
BANNER
|
|
108
|
+
|
|
109
|
+
"researcher" => <<~BANNER
|
|
110
|
+
╔════════════════════════════════════════════════════════════════════════════╗
|
|
111
|
+
║ TINKER RESEARCHER - ROLE ENFORCEMENT ║
|
|
112
|
+
╠════════════════════════════════════════════════════════════════════════════╣
|
|
113
|
+
║ YOUR ROLE: AUTONOMOUS RESEARCH AND ANALYSIS ║
|
|
114
|
+
║ YOUR MODE: INVESTIGATE CODEBASE AND DOCUMENT FINDINGS ║
|
|
115
|
+
╚════════════════════════════════════════════════════════════════════════════╝
|
|
116
|
+
|
|
117
|
+
This session is running as the TINKER RESEARCHER agent in AUTONOMOUS MODE.
|
|
118
|
+
|
|
119
|
+
CORE RESPONSIBILITIES:
|
|
120
|
+
✓ Analyze codebase architecture and patterns
|
|
121
|
+
✓ Research best practices and solutions
|
|
122
|
+
✓ Document findings in memory for other agents
|
|
123
|
+
BANNER
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
def check_requirements!
|
|
127
|
+
missing = []
|
|
128
|
+
missing << "ruby" unless system("which ruby > /dev/null 2>&1")
|
|
129
|
+
missing << "node" unless system("which node > /dev/null 2>&1")
|
|
130
|
+
missing << "tmux" unless system("which tmux > /dev/null 2>&1")
|
|
131
|
+
missing << "git" unless system("which git > /dev/null 2>&1")
|
|
132
|
+
missing << "claude" unless system("which claude > /dev/null 2>&1")
|
|
133
|
+
|
|
134
|
+
unless missing.empty?
|
|
135
|
+
puts "❌ Missing requirements: #{missing.join(', ')}"
|
|
136
|
+
puts ""
|
|
137
|
+
puts "Install with:"
|
|
138
|
+
puts " apt-get install -y tmux git curl"
|
|
139
|
+
puts " npm install -g @anthropic-ai/claude-code"
|
|
140
|
+
exit 1
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def check_env!
|
|
145
|
+
required = %w[AGENT_TYPE PROJECT_ID RAILS_WS_URL]
|
|
146
|
+
missing = required.select { |var| ENV[var].to_s.empty? }
|
|
147
|
+
|
|
148
|
+
unless missing.empty?
|
|
149
|
+
puts "❌ Missing environment variables: #{missing.join(', ')}"
|
|
150
|
+
puts ""
|
|
151
|
+
puts "Required:"
|
|
152
|
+
puts " AGENT_TYPE - worker|planner|reviewer|orchestrator|researcher"
|
|
153
|
+
puts " PROJECT_ID - Your Tinker project ID"
|
|
154
|
+
puts " RAILS_WS_URL - WebSocket URL (wss://tinker.example.com/cable)"
|
|
155
|
+
puts ""
|
|
156
|
+
puts "Optional:"
|
|
157
|
+
puts " RAILS_API_URL - API URL for MCP tools"
|
|
158
|
+
puts " RAILS_API_KEY - MCP API key"
|
|
159
|
+
puts " GH_TOKEN - GitHub token"
|
|
160
|
+
exit 1
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
agent_type = ENV["AGENT_TYPE"]
|
|
164
|
+
unless AGENT_BANNERS.key?(agent_type)
|
|
165
|
+
puts "❌ Invalid AGENT_TYPE: #{agent_type}"
|
|
166
|
+
puts " Valid types: #{AGENT_BANNERS.keys.join(', ')}"
|
|
167
|
+
exit 1
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def setup_mcp_config!
|
|
172
|
+
# MCP config is project-specific and should be provided by the Dockerfile
|
|
173
|
+
# or mounted at runtime. This script only checks if it exists.
|
|
174
|
+
|
|
175
|
+
agent_type = ENV["AGENT_TYPE"]
|
|
176
|
+
rails_api_url = ENV["RAILS_API_URL"]
|
|
177
|
+
rails_api_key = ENV["RAILS_API_KEY"]
|
|
178
|
+
|
|
179
|
+
# Load existing config if present
|
|
180
|
+
existing_config = {}
|
|
181
|
+
if File.exist?(".mcp.json")
|
|
182
|
+
begin
|
|
183
|
+
existing_config = JSON.parse(File.read(".mcp.json"))
|
|
184
|
+
puts "✅ Found existing .mcp.json, merging configuration..."
|
|
185
|
+
rescue JSON::ParserError
|
|
186
|
+
puts "⚠️ Existing .mcp.json is invalid, starting fresh"
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Ensure mcpServers key exists
|
|
191
|
+
existing_config["mcpServers"] ||= {}
|
|
192
|
+
|
|
193
|
+
if rails_api_url && !rails_api_url.empty? && rails_api_key && !rails_api_key.empty?
|
|
194
|
+
# Use published tinker-mcp package
|
|
195
|
+
tinker_server_config = {
|
|
196
|
+
"command" => "npx",
|
|
197
|
+
"args" => ["-y", "tinker-mcp"],
|
|
198
|
+
"env" => {
|
|
199
|
+
"RAILS_API_URL" => rails_api_url,
|
|
200
|
+
"RAILS_API_KEY" => rails_api_key
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
# Add/Update tinker server config
|
|
205
|
+
existing_config["mcpServers"]["tinker-#{agent_type}"] = tinker_server_config
|
|
206
|
+
|
|
207
|
+
File.write(".mcp.json", JSON.pretty_generate(existing_config))
|
|
208
|
+
puts "📝 Updated .mcp.json with tinker-#{agent_type} server (using tinker-mcp)"
|
|
209
|
+
else
|
|
210
|
+
# Only write if we don't have existing config
|
|
211
|
+
if existing_config["mcpServers"].empty?
|
|
212
|
+
File.write(".mcp.json", JSON.generate({ "mcpServers" => {} }))
|
|
213
|
+
puts "ℹ️ No MCP API credentials - MCP tools disabled"
|
|
214
|
+
else
|
|
215
|
+
puts "ℹ️ No MCP API credentials - keeping existing config"
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def setup_claude_md!
|
|
221
|
+
agent_type = ENV["AGENT_TYPE"]
|
|
222
|
+
banner = AGENT_BANNERS[agent_type]
|
|
223
|
+
|
|
224
|
+
File.write("CLAUDE.md", banner)
|
|
225
|
+
puts "📝 Created CLAUDE.md with #{agent_type} instructions"
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def setup_github_auth!
|
|
229
|
+
app_id = ENV["GITHUB_APP_ID"] || ENV["GITHUB_APP_CLIENT_ID"]
|
|
230
|
+
|
|
231
|
+
if app_id && ENV["GITHUB_APP_INSTALLATION_ID"] && ENV["GITHUB_APP_PRIVATE_KEY_PATH"]
|
|
232
|
+
puts "🔐 Configuring GitHub App authentication..."
|
|
233
|
+
|
|
234
|
+
# Create helper script
|
|
235
|
+
helper_path = "/usr/local/bin/git-auth-helper"
|
|
236
|
+
|
|
237
|
+
# We embed the helper script content here
|
|
238
|
+
helper_content = <<~RUBY
|
|
239
|
+
#!/usr/bin/env ruby
|
|
240
|
+
require 'openssl'
|
|
241
|
+
require 'json'
|
|
242
|
+
require 'net/http'
|
|
243
|
+
require 'uri'
|
|
244
|
+
require 'base64'
|
|
245
|
+
require 'time'
|
|
246
|
+
|
|
247
|
+
def generate_jwt(app_id, private_key_path)
|
|
248
|
+
private_key = OpenSSL::PKey::RSA.new(File.read(private_key_path))
|
|
249
|
+
payload = {
|
|
250
|
+
iat: Time.now.to_i - 60,
|
|
251
|
+
exp: Time.now.to_i + 600,
|
|
252
|
+
iss: app_id
|
|
253
|
+
}
|
|
254
|
+
header = { alg: 'RS256', typ: 'JWT' }
|
|
255
|
+
segments = [
|
|
256
|
+
Base64.urlsafe_encode64(header.to_json, padding: false),
|
|
257
|
+
Base64.urlsafe_encode64(payload.to_json, padding: false)
|
|
258
|
+
]
|
|
259
|
+
signing_input = segments.join('.')
|
|
260
|
+
signature = private_key.sign(OpenSSL::Digest::SHA256.new, signing_input)
|
|
261
|
+
segments << Base64.urlsafe_encode64(signature, padding: false)
|
|
262
|
+
segments.join('.')
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def get_installation_token(jwt, installation_id)
|
|
266
|
+
uri = URI("https://api.github.com/app/installations/\#{installation_id}/access_tokens")
|
|
267
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
268
|
+
http.use_ssl = true
|
|
269
|
+
request = Net::HTTP::Post.new(uri)
|
|
270
|
+
request['Authorization'] = "Bearer \#{jwt}"
|
|
271
|
+
request['Accept'] = 'application/vnd.github+json'
|
|
272
|
+
response = http.request(request)
|
|
273
|
+
data = JSON.parse(response.body)
|
|
274
|
+
{ token: data['token'], expires_at: Time.parse(data['expires_at']) }
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def cached_token(app_id, installation_id, key_path)
|
|
278
|
+
cache_file = '/tmp/github-app-token-cache'
|
|
279
|
+
if File.exist?(cache_file)
|
|
280
|
+
cache = JSON.parse(File.read(cache_file))
|
|
281
|
+
expires_at = Time.parse(cache['expires_at'])
|
|
282
|
+
return cache['token'] if expires_at > Time.now + 300
|
|
283
|
+
end
|
|
284
|
+
jwt = generate_jwt(app_id, key_path)
|
|
285
|
+
token_data = get_installation_token(jwt, installation_id)
|
|
286
|
+
File.write(cache_file, token_data.to_json)
|
|
287
|
+
token_data[:token]
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
app_id = ENV['GITHUB_APP_CLIENT_ID'] || ENV['GITHUB_APP_ID']
|
|
291
|
+
installation_id = ENV['GITHUB_APP_INSTALLATION_ID']
|
|
292
|
+
key_path = ENV['GITHUB_APP_PRIVATE_KEY_PATH']
|
|
293
|
+
|
|
294
|
+
puts cached_token(app_id, installation_id, key_path)
|
|
295
|
+
RUBY
|
|
296
|
+
|
|
297
|
+
# Only write if we have permission (we should as root or if /usr/local/bin is writable)
|
|
298
|
+
# If not, write to /tmp and use that
|
|
299
|
+
if File.writable?("/usr/local/bin")
|
|
300
|
+
File.write(helper_path, helper_content)
|
|
301
|
+
File.chmod(0755, helper_path)
|
|
302
|
+
else
|
|
303
|
+
helper_path = "/tmp/git-auth-helper"
|
|
304
|
+
File.write(helper_path, helper_content)
|
|
305
|
+
File.chmod(0755, helper_path)
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# Configure git
|
|
309
|
+
system("git config --global credential.helper '!f() { test \"$1\" = get && echo \"protocol=https\" && echo \"host=github.com\" && echo \"username=x-access-token\" && echo \"password=$(#{helper_path})\"; }; f'")
|
|
310
|
+
|
|
311
|
+
# Configure gh CLI
|
|
312
|
+
token = `#{helper_path}`.strip
|
|
313
|
+
if token.empty?
|
|
314
|
+
puts "❌ Failed to generate GitHub App token"
|
|
315
|
+
else
|
|
316
|
+
IO.popen("gh auth login --with-token 2>/dev/null", "w") { |io| io.puts token }
|
|
317
|
+
puts "✅ GitHub App authentication configured"
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
elsif ENV["GH_TOKEN"] && !ENV["GH_TOKEN"].empty?
|
|
321
|
+
system("echo '#{ENV['GH_TOKEN']}' | gh auth login --with-token 2>/dev/null")
|
|
322
|
+
puts "🔐 GitHub authentication configured"
|
|
323
|
+
else
|
|
324
|
+
puts "⚠️ No GH_TOKEN or GitHub App config - GitHub operations may fail"
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def download_agent_bridge!
|
|
329
|
+
bridge_url = "#{TINKER_RAW_URL}/bin/agent-bridge"
|
|
330
|
+
bridge_tmux_url = "#{TINKER_RAW_URL}/bin/agent-bridge-tmux"
|
|
331
|
+
target_dir = "/usr/local/bin"
|
|
332
|
+
|
|
333
|
+
# Check if binaries are mounted at /tmp (dev mode)
|
|
334
|
+
if File.exist?("/tmp/agent-bridge")
|
|
335
|
+
puts "🔧 Installing mounted agent-bridge..."
|
|
336
|
+
system("sudo cp /tmp/agent-bridge #{target_dir}/agent-bridge")
|
|
337
|
+
system("sudo chmod +x #{target_dir}/agent-bridge")
|
|
338
|
+
else
|
|
339
|
+
puts "📥 Downloading agent-bridge..."
|
|
340
|
+
system("sudo curl -fsSL #{bridge_url} -o #{target_dir}/agent-bridge")
|
|
341
|
+
system("sudo chmod +x #{target_dir}/agent-bridge")
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
if File.exist?("/tmp/agent-bridge-tmux")
|
|
345
|
+
puts "🔧 Installing mounted agent-bridge-tmux..."
|
|
346
|
+
system("sudo cp /tmp/agent-bridge-tmux #{target_dir}/agent-bridge-tmux")
|
|
347
|
+
system("sudo chmod +x #{target_dir}/agent-bridge-tmux")
|
|
348
|
+
else
|
|
349
|
+
system("sudo curl -fsSL #{bridge_tmux_url} -o #{target_dir}/agent-bridge-tmux")
|
|
350
|
+
system("sudo chmod +x #{target_dir}/agent-bridge-tmux")
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
puts "✅ agent-bridge installed to #{target_dir}"
|
|
354
|
+
|
|
355
|
+
# Patch agent-bridge-tmux to force INSIDE_TMUX=1
|
|
356
|
+
puts "🔧 Patching agent-bridge-tmux to force INSIDE_TMUX=1..."
|
|
357
|
+
|
|
358
|
+
# Read the file (we can read /usr/local/bin files usually)
|
|
359
|
+
content = File.read("#{target_dir}/agent-bridge-tmux")
|
|
360
|
+
|
|
361
|
+
# Replace the command
|
|
362
|
+
new_content = content.gsub(
|
|
363
|
+
"&& agent-bridge\"",
|
|
364
|
+
"&& export INSIDE_TMUX=1 && agent-bridge\""
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
# Write to temp file
|
|
368
|
+
File.write("/tmp/agent-bridge-tmux-patched", new_content)
|
|
369
|
+
|
|
370
|
+
# Move to destination with sudo
|
|
371
|
+
system("sudo mv /tmp/agent-bridge-tmux-patched #{target_dir}/agent-bridge-tmux")
|
|
372
|
+
system("sudo chmod +x #{target_dir}/agent-bridge-tmux")
|
|
373
|
+
|
|
374
|
+
return target_dir
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def run_agent!(bin_dir)
|
|
378
|
+
agent_type = ENV["AGENT_TYPE"]
|
|
379
|
+
puts ""
|
|
380
|
+
puts "🚀 Starting #{agent_type} agent..."
|
|
381
|
+
puts " Press Ctrl+B then D to detach from tmux"
|
|
382
|
+
puts ""
|
|
383
|
+
|
|
384
|
+
# Run agent-bridge-tmux which handles tmux session and status bar
|
|
385
|
+
exec("#{bin_dir}/agent-bridge-tmux")
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
# Main
|
|
389
|
+
puts "🤖 Tinker Agent Setup"
|
|
390
|
+
puts "====================="
|
|
391
|
+
puts ""
|
|
392
|
+
|
|
393
|
+
check_requirements!
|
|
394
|
+
check_env!
|
|
395
|
+
setup_mcp_config!
|
|
396
|
+
setup_claude_md!
|
|
397
|
+
setup_github_auth!
|
|
398
|
+
bin_dir = download_agent_bridge!
|
|
399
|
+
run_agent!(bin_dir)
|