tinker-agent 1.0.56 → 1.0.59
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/agents.rb +34 -8
- package/bin/install-agent.sh +1 -1
- package/{run-tinker-agent.rb → bin/run-tinker-agent.rb} +7 -4
- package/lib/tinker_agent/agent.rb +69 -8
- package/lib/tinker_agent/config.rb +50 -0
- package/package.json +4 -4
- package/setup-agent.rb +380 -34
package/agents.rb
CHANGED
|
@@ -91,6 +91,29 @@ If blocked (e.g., missing tools, auth errors, ambiguous requirements):
|
|
|
91
91
|
3. **Priority:** High or Critical.
|
|
92
92
|
4. **Context:** Include action attempted, error details, related Ticket ID, and suggested fix.
|
|
93
93
|
|
|
94
|
+
### BUG HANDLING PRIORITY (CRITICAL)
|
|
95
|
+
|
|
96
|
+
When you discover a bug during implementation:
|
|
97
|
+
|
|
98
|
+
**MANDATORY WORKFLOW:**
|
|
99
|
+
1. **FIX IT NOW** - Write code to resolve the issue immediately
|
|
100
|
+
2. **TEST IT** - Add tests to prevent regression and verify the fix
|
|
101
|
+
3. **ESCALATE ONLY** - If you absolutely cannot fix it, create a ticket explaining WHY
|
|
102
|
+
|
|
103
|
+
**FORBIDDEN (ANTI-PATTERNS):**
|
|
104
|
+
* ❌ Writing PR comments or tests that describe bugs without fixing them
|
|
105
|
+
* ❌ Creating documentation for unfixed bugs
|
|
106
|
+
* ❌ Adding TODO comments for bugs you could fix yourself
|
|
107
|
+
* ❌ Marking tests as "KNOWN BUG" or "xfail" to document issues
|
|
108
|
+
|
|
109
|
+
**WHY THIS MATTERS:**
|
|
110
|
+
* **User Trust:** "Documenting bugs" looks like laziness or incompetence
|
|
111
|
+
* **Code Quality:** Production code should not have known bugs
|
|
112
|
+
* **Team Value:** Agentic team should reduce bug count, not catalog it
|
|
113
|
+
* **Reference:** Knowledge Article #36 defines this standard
|
|
114
|
+
|
|
115
|
+
**USER TRUST DEPENDS ON FIXING BUGS, NOT CATALOGING THEM.**
|
|
116
|
+
|
|
94
117
|
### UNIVERSAL TECHNICAL CONSTRAINTS
|
|
95
118
|
1. **Tool Formatting:** Do NOT use a colon before tool calls (e.g., write "Let me search" NOT "Let me search:").
|
|
96
119
|
2. **URL Safety:** NEVER guess or hallucinate URLs. Use only known valid URLs.
|
|
@@ -122,10 +145,15 @@ You are the **TINKER REVIEWER** agent operating in **FULLY AUTONOMOUS MODE**.
|
|
|
122
145
|
2. **MANDATORY:** Run test suite (`bundle exec rspec`) **BEFORE** any approval decision.
|
|
123
146
|
3. **Detect missing specs:** Ensure file changes have corresponding tests.
|
|
124
147
|
4. **Reject (Fail):** If tests fail or specs are missing, you MUST reject.
|
|
125
|
-
5. **
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
148
|
+
5. **CRITICAL:** Reject PRs that document bugs without fixing them.
|
|
149
|
+
* Watch for: Tests marked "KNOWN BUG", "xfail", or similar that intentionally fail
|
|
150
|
+
* Watch for: PR comments describing unfixed issues instead of fixing them
|
|
151
|
+
* Watch for: TODO comments for bugs the worker could have fixed
|
|
152
|
+
* **Standard:** Workers MUST FIX bugs they discover, not catalog them (KB #36)
|
|
153
|
+
6. **Feedback:** Add `code_review` comments with findings.
|
|
154
|
+
7. **Transition:** Use `pass_audit` or `fail_audit`.
|
|
155
|
+
8. **Knowledge:** Search memory for project standards.
|
|
156
|
+
9. **Completion:** Mark idle after completing the review.
|
|
129
157
|
|
|
130
158
|
### FORBIDDEN ACTIONS (STRICT)
|
|
131
159
|
* Do NOT implement new features or functionality.
|
|
@@ -198,7 +226,7 @@ You must use `create_proposal` to suggest actions.
|
|
|
198
226
|
2. **Analysis & Memory**
|
|
199
227
|
- Analyze patterns across tickets and code.
|
|
200
228
|
- Store observations using `store_memory`.
|
|
201
|
-
- Identify stale or incorrect memories and
|
|
229
|
+
- Identify stale or incorrect memories and delete them using `delete_memory` tool (autonomous cleanup).
|
|
202
230
|
|
|
203
231
|
3. **Knowledge Base Maintenance**
|
|
204
232
|
- **Consult First:** Always search for human instructions (`tags: instruction`) and architectural patterns before proposing changes.
|
|
@@ -218,7 +246,7 @@ You must use `create_proposal` to suggest actions.
|
|
|
218
246
|
**Structure:**
|
|
219
247
|
Every proposal must include:
|
|
220
248
|
- `title`: Clear and concise.
|
|
221
|
-
- `proposal_type`: One of [new_ticket,
|
|
249
|
+
- `proposal_type`: One of [new_ticket, refactor, test_gap, feature].
|
|
222
250
|
- `reasoning`: Why this matters.
|
|
223
251
|
- `confidence`: high/medium/low.
|
|
224
252
|
- `priority`: high/medium/low.
|
|
@@ -226,7 +254,6 @@ Every proposal must include:
|
|
|
226
254
|
|
|
227
255
|
**Types:**
|
|
228
256
|
- `new_ticket`: Suggest a new task to be created.
|
|
229
|
-
- `memory_cleanup`: Request deletion of stale/incorrect memories.
|
|
230
257
|
- `refactor`: Identify code requiring refactoring.
|
|
231
258
|
- `test_gap`: Find missing test coverage.
|
|
232
259
|
- `feature`: Suggest new features or improvements.
|
|
@@ -237,7 +264,6 @@ Every proposal must include:
|
|
|
237
264
|
|
|
238
265
|
**ABSOLUTELY FORBIDDEN:**
|
|
239
266
|
- Modifying code, tickets, or files directly.
|
|
240
|
-
- Deleting or modifying memories directly (use `memory_cleanup` proposal).
|
|
241
267
|
- Writing, editing, or refactoring code.
|
|
242
268
|
- Making git commits or pull requests.
|
|
243
269
|
- Sending messages to other agents.
|
package/bin/install-agent.sh
CHANGED
|
@@ -7,7 +7,7 @@ export DEBIAN_FRONTEND=noninteractive
|
|
|
7
7
|
echo 'Acquire::Check-Date "false";' > /etc/apt/apt.conf.d/99no-check-date
|
|
8
8
|
|
|
9
9
|
apt-get update && apt-get install -y \
|
|
10
|
-
git curl tmux sudo unzip wget
|
|
10
|
+
git curl tmux sudo unzip wget jq nano
|
|
11
11
|
|
|
12
12
|
# Install Node.js (required for Claude CLI)
|
|
13
13
|
# Check for existing Node installation
|
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
# - Dockerfile.sandbox in project root
|
|
12
12
|
# - tinker.env.rb in project root (gitignored)
|
|
13
13
|
|
|
14
|
-
require_relative "lib/tinker_agent/config"
|
|
15
|
-
require_relative "lib/tinker_agent/docker"
|
|
16
|
-
require_relative "lib/tinker_agent/agent"
|
|
17
|
-
require_relative "agents"
|
|
14
|
+
require_relative "../lib/tinker_agent/config"
|
|
15
|
+
require_relative "../lib/tinker_agent/docker"
|
|
16
|
+
require_relative "../lib/tinker_agent/agent"
|
|
17
|
+
require_relative "../agents"
|
|
18
18
|
|
|
19
19
|
def show_usage
|
|
20
20
|
puts "Tinker Agent Runner"
|
|
@@ -39,9 +39,12 @@ if command == "attach"
|
|
|
39
39
|
agent_type = ARGV[1]&.downcase
|
|
40
40
|
abort "Usage: npx tinker-agent attach [agent-type]" unless agent_type
|
|
41
41
|
config = TinkerAgent::Config.load
|
|
42
|
+
TinkerAgent::Config.fetch_repositories!(config)
|
|
42
43
|
TinkerAgent::Agent.attach(agent_type, config, AGENT_CONFIGS)
|
|
43
44
|
else
|
|
44
45
|
config = TinkerAgent::Config.load
|
|
46
|
+
# Fetch repositories from Rails API before building
|
|
47
|
+
TinkerAgent::Config.fetch_repositories!(config)
|
|
45
48
|
TinkerAgent::Docker.build_image(config)
|
|
46
49
|
TinkerAgent::Agent.run(command, config, AGENT_CONFIGS)
|
|
47
50
|
end
|
|
@@ -22,7 +22,26 @@ module TinkerAgent
|
|
|
22
22
|
# Write banner to a persistent temp file (not auto-deleted)
|
|
23
23
|
banner_path = "/tmp/tinker-agent-banner-#{agent_type}.txt"
|
|
24
24
|
FileUtils.rm_rf(banner_path)
|
|
25
|
-
|
|
25
|
+
|
|
26
|
+
# Append repository context to banner
|
|
27
|
+
banner_content = agent_def[:banner].dup
|
|
28
|
+
if config["repositories"]&.any?
|
|
29
|
+
sorted_repos = config["repositories"].sort_by { |r| r["position"] || 0 }
|
|
30
|
+
repo_info = sorted_repos.map do |repo|
|
|
31
|
+
name = repo["name"]
|
|
32
|
+
path = repo["path_in_sandbox"] || "/workspace/#{name}"
|
|
33
|
+
" - **#{name}**: #{path}"
|
|
34
|
+
end.join("\n")
|
|
35
|
+
|
|
36
|
+
banner_content += "\n\n## REPOSITORY STRUCTURE\n\n"
|
|
37
|
+
banner_content += "This project has #{sorted_repos.count} repositories:\n\n"
|
|
38
|
+
banner_content += "#{repo_info}\n\n"
|
|
39
|
+
banner_content += "### Repository Switching\n\n"
|
|
40
|
+
banner_content += "Use `switch-to-repo.sh <name>` to change working directories:\n\n"
|
|
41
|
+
banner_content += "```bash\nswitch-to-repo.sh tinker\nswitch-to-repo.sh tinker-public\n```\n"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
File.write(banner_path, banner_content)
|
|
26
45
|
|
|
27
46
|
docker_cmd = build_docker_command(container_name, agent_type, config, agent_config, agent_def, banner_path)
|
|
28
47
|
|
|
@@ -134,6 +153,7 @@ module TinkerAgent
|
|
|
134
153
|
|
|
135
154
|
add_github_auth!(docker_cmd, config)
|
|
136
155
|
add_git_config!(docker_cmd, config)
|
|
156
|
+
add_repository_mounts!(docker_cmd, config)
|
|
137
157
|
add_development_mounts!(docker_cmd)
|
|
138
158
|
|
|
139
159
|
local_setup_script = File.join(File.dirname(__FILE__), "..", "..", "setup-agent.rb")
|
|
@@ -182,26 +202,67 @@ module TinkerAgent
|
|
|
182
202
|
docker_cmd.concat(["-e", "GIT_USER_EMAIL=#{git_config['user_email']}"]) if git_config["user_email"]
|
|
183
203
|
end
|
|
184
204
|
|
|
205
|
+
def self.add_repository_mounts!(docker_cmd, config)
|
|
206
|
+
return unless config["repositories"].is_a?(Array) && config["repositories"].any?
|
|
207
|
+
|
|
208
|
+
puts "📚 Configuring #{config['repositories'].count} repositories..."
|
|
209
|
+
|
|
210
|
+
# Build REPOSITORIES_CONTEXT for sessionstart hook (format: "name:path,name:path")
|
|
211
|
+
sorted_repos = config["repositories"].sort_by { |r| r["position"] || 0 }
|
|
212
|
+
repos_context = sorted_repos.map do |repo|
|
|
213
|
+
repo_name = repo["name"]
|
|
214
|
+
sandbox_path = repo["path_in_sandbox"] || "/workspace/#{repo_name}"
|
|
215
|
+
"#{repo_name}:#{sandbox_path}"
|
|
216
|
+
end.join(",")
|
|
217
|
+
|
|
218
|
+
docker_cmd.concat(["-e", "REPOSITORIES_CONTEXT=#{repos_context}"])
|
|
219
|
+
|
|
220
|
+
# Pass repository config via environment variables for container to clone
|
|
221
|
+
repo_config_json = sorted_repos.to_json
|
|
222
|
+
docker_cmd.concat(["-e", "REPOSITORIES_CONFIG=#{repo_config_json}"])
|
|
223
|
+
|
|
224
|
+
sorted_repos.each do |repo|
|
|
225
|
+
repo_name = repo["name"]
|
|
226
|
+
sandbox_path = repo["path_in_sandbox"] || "/workspace/#{repo_name}"
|
|
227
|
+
puts " - #{repo_name}: #{sandbox_path} (will be cloned in container)"
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
185
231
|
def self.add_development_mounts!(docker_cmd)
|
|
186
232
|
# Check for local setup-agent.rb (for development)
|
|
187
233
|
local_setup_script = File.join(File.dirname(__FILE__), "..", "..", "setup-agent.rb")
|
|
188
|
-
|
|
234
|
+
|
|
189
235
|
# Check for local agent-bridge binaries (for development)
|
|
190
236
|
arch = `uname -m`.strip
|
|
191
237
|
linux_arch = (arch == "x86_64") ? "amd64" : "arm64"
|
|
192
|
-
|
|
193
|
-
|
|
238
|
+
|
|
239
|
+
# Try multiple possible paths for the binary (built Go binaries in dist/)
|
|
240
|
+
possible_paths = [
|
|
241
|
+
File.join(Dir.pwd, "overrides", "agent-bridge-linux-#{linux_arch}"), # TINKER_SANDBOX/overrides
|
|
242
|
+
File.join(Dir.pwd, "tinker-public", "dist", "agent-bridge-linux-#{linux_arch}"), # Running from metafolder
|
|
243
|
+
File.join(Dir.pwd, "..", "tinker-public", "dist", "agent-bridge-linux-#{linux_arch}"), # Running from TINKER_SANDBOX
|
|
244
|
+
File.join(Dir.pwd, "dist", "agent-bridge-linux-#{linux_arch}"), # Running from tinker-public
|
|
245
|
+
]
|
|
246
|
+
|
|
247
|
+
linux_bridge = nil
|
|
248
|
+
possible_paths.each do |path|
|
|
249
|
+
if File.exist?(path)
|
|
250
|
+
linux_bridge = path
|
|
251
|
+
break
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
194
255
|
local_bridge_default = File.join(Dir.pwd, "bin", "agent-bridge")
|
|
195
256
|
local_tmux = File.join(File.dirname(__FILE__), "..", "..", "bin", "agent-bridge-tmux")
|
|
196
|
-
|
|
257
|
+
|
|
197
258
|
if File.exist?(local_setup_script)
|
|
198
259
|
puts "🔧 Using local setup-agent.rb for development"
|
|
199
260
|
docker_cmd.concat(["-v", "#{File.expand_path(local_setup_script)}:/tmp/setup-agent.rb:ro"])
|
|
200
261
|
end
|
|
201
262
|
|
|
202
|
-
if File.exist?(linux_bridge)
|
|
263
|
+
if linux_bridge && File.exist?(linux_bridge)
|
|
203
264
|
puts "🔧 Using local linux binary: #{linux_bridge}"
|
|
204
|
-
docker_cmd.concat(["-v", "#{linux_bridge}:/tmp/agent-bridge:ro"])
|
|
265
|
+
docker_cmd.concat(["-v", "#{File.expand_path(linux_bridge)}:/tmp/agent-bridge:ro"])
|
|
205
266
|
elsif File.exist?(local_bridge_default)
|
|
206
267
|
# Check if it's a binary or script
|
|
207
268
|
is_script = File.read(local_bridge_default, 4) == "#!/b"
|
|
@@ -212,7 +273,7 @@ module TinkerAgent
|
|
|
212
273
|
docker_cmd.concat(["-v", "#{local_bridge_default}:/tmp/agent-bridge:ro"])
|
|
213
274
|
end
|
|
214
275
|
end
|
|
215
|
-
|
|
276
|
+
|
|
216
277
|
if File.exist?(local_tmux)
|
|
217
278
|
docker_cmd.concat(["-v", "#{File.expand_path(local_tmux)}:/tmp/agent-bridge-tmux:ro"])
|
|
218
279
|
end
|
|
@@ -67,6 +67,56 @@ module TinkerAgent
|
|
|
67
67
|
JSON.parse(JSON.generate(config))
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
+
def self.fetch_repositories!(config)
|
|
71
|
+
return unless config["rails_api_url"] && config["project_id"]
|
|
72
|
+
|
|
73
|
+
# Use the first available agent's MCP API key
|
|
74
|
+
mcp_api_key = nil
|
|
75
|
+
if config["agents"].is_a?(Hash)
|
|
76
|
+
config["agents"].each do |_, agent_config|
|
|
77
|
+
if agent_config.is_a?(Hash) && agent_config["mcp_api_key"]
|
|
78
|
+
mcp_api_key = agent_config["mcp_api_key"]
|
|
79
|
+
break
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
return unless mcp_api_key
|
|
85
|
+
|
|
86
|
+
# Fetch repositories from Rails API
|
|
87
|
+
require "net/http"
|
|
88
|
+
require "json"
|
|
89
|
+
require "uri"
|
|
90
|
+
|
|
91
|
+
uri = URI("#{config['rails_api_url']}/projects/#{config['project_id']}/repositories")
|
|
92
|
+
|
|
93
|
+
begin
|
|
94
|
+
response = Net::HTTP.get_response(uri, { "X-API-Key" => mcp_api_key, "Accept" => "application/json" })
|
|
95
|
+
|
|
96
|
+
if response.code == "200"
|
|
97
|
+
repositories = JSON.parse(response.body)
|
|
98
|
+
|
|
99
|
+
if repositories.any?
|
|
100
|
+
config["repositories"] = repositories
|
|
101
|
+
|
|
102
|
+
# Build REPOSITORIES_CONTEXT string: "name:path,name:path"
|
|
103
|
+
repo_context = repositories.map { |r| "#{r['name']}:#{r['path_in_sandbox']}" }.join(",")
|
|
104
|
+
config["env"] ||= {}
|
|
105
|
+
config["env"]["REPOSITORIES_CONTEXT"] = repo_context
|
|
106
|
+
|
|
107
|
+
puts "📚 Loaded #{repositories.count} repositories for project #{config['project_id']}"
|
|
108
|
+
repositories.each do |repo|
|
|
109
|
+
puts " - #{repo['name']}: #{repo['path_in_sandbox']}"
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
else
|
|
113
|
+
puts "⚠️ Unable to fetch repositories (HTTP #{response.code})"
|
|
114
|
+
end
|
|
115
|
+
rescue StandardError => e
|
|
116
|
+
puts "⚠️ Failed to fetch repositories: #{e.message}"
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
70
120
|
def self.image_name(config)
|
|
71
121
|
if config["project_id"]
|
|
72
122
|
"tinker-sandbox-#{config['project_id']}"
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tinker-agent",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.59",
|
|
4
4
|
"description": "Tinker Agent Runner",
|
|
5
5
|
"bin": {
|
|
6
|
-
"tinker-agent": "./run-tinker-agent.rb"
|
|
6
|
+
"tinker-agent": "./bin/run-tinker-agent.rb"
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
|
-
"run-tinker-agent.rb",
|
|
9
|
+
"bin/run-tinker-agent.rb",
|
|
10
10
|
"agents.rb",
|
|
11
11
|
"setup-agent.rb",
|
|
12
12
|
"lib/",
|
|
@@ -18,4 +18,4 @@
|
|
|
18
18
|
},
|
|
19
19
|
"author": "",
|
|
20
20
|
"license": "ISC"
|
|
21
|
-
}
|
|
21
|
+
}
|
package/setup-agent.rb
CHANGED
|
@@ -176,14 +176,24 @@ end
|
|
|
176
176
|
def setup_claude_config!
|
|
177
177
|
home_claude_json = File.expand_path("~/.claude.json")
|
|
178
178
|
|
|
179
|
+
# First, try to copy from mounted config (from host)
|
|
180
|
+
mounted_paths = ["/tmp/cfg/claude.json"]
|
|
181
|
+
mounted_paths.each do |path|
|
|
182
|
+
if File.exist?(path) && !File.exist?(home_claude_json)
|
|
183
|
+
FileUtils.cp(path, home_claude_json)
|
|
184
|
+
puts "📋 Copied claude.json from #{path}"
|
|
185
|
+
break
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
179
189
|
if File.exist?(home_claude_json)
|
|
180
190
|
puts "🔧 Configuring claude.json..."
|
|
181
191
|
begin
|
|
182
192
|
claude_config = JSON.parse(File.read(home_claude_json))
|
|
183
|
-
|
|
193
|
+
|
|
184
194
|
# Add bypass permission at top level
|
|
185
195
|
claude_config["bypassPermissionsModeAccepted"] = true
|
|
186
|
-
|
|
196
|
+
|
|
187
197
|
File.write(home_claude_json, JSON.pretty_generate(claude_config))
|
|
188
198
|
puts "✅ claude.json configured with bypass permissions"
|
|
189
199
|
rescue JSON::ParserError
|
|
@@ -200,9 +210,33 @@ def setup_claude_settings!
|
|
|
200
210
|
home_settings_json = File.join(settings_dir, "settings.json")
|
|
201
211
|
|
|
202
212
|
puts "🔧 Configuring ~/.claude/settings.json..."
|
|
203
|
-
|
|
213
|
+
|
|
204
214
|
settings_config = {}
|
|
205
|
-
|
|
215
|
+
|
|
216
|
+
# First, try to load user's existing settings from mounted config
|
|
217
|
+
# We merge both files: settings.json AND claude.json (for theme/appearance settings)
|
|
218
|
+
mounted_settings_paths = [
|
|
219
|
+
"/tmp/cfg/settings.json",
|
|
220
|
+
"/tmp/cfg/claude.json"
|
|
221
|
+
]
|
|
222
|
+
|
|
223
|
+
mounted_settings = {}
|
|
224
|
+
mounted_settings_paths.each do |path|
|
|
225
|
+
if File.exist?(path)
|
|
226
|
+
begin
|
|
227
|
+
file_settings = JSON.parse(File.read(path))
|
|
228
|
+
# Merge settings - later files override earlier ones for conflicting keys
|
|
229
|
+
mounted_settings = mounted_settings.merge(file_settings)
|
|
230
|
+
puts " (loaded from #{path})"
|
|
231
|
+
rescue JSON::ParserError => e
|
|
232
|
+
puts " ⚠️ Failed to parse #{path}: #{e.message}"
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
if mounted_settings.any?
|
|
238
|
+
settings_config = mounted_settings
|
|
239
|
+
elsif File.exist?(home_settings_json)
|
|
206
240
|
begin
|
|
207
241
|
settings_config = JSON.parse(File.read(home_settings_json))
|
|
208
242
|
rescue JSON::ParserError
|
|
@@ -266,6 +300,42 @@ def setup_claude_settings!
|
|
|
266
300
|
|
|
267
301
|
settings_config["hooks"]["SessionStart"] << session_hook_to_add
|
|
268
302
|
|
|
303
|
+
# Install sessionstart-input.sh for repository context injection
|
|
304
|
+
# This provides repository information to Claude at session start
|
|
305
|
+
input_script_name = "sessionstart-input.sh"
|
|
306
|
+
input_dest_path = File.join(hooks_dir, input_script_name)
|
|
307
|
+
|
|
308
|
+
local_input_paths = [
|
|
309
|
+
File.join(Dir.pwd, "tinker-public", "hooks", input_script_name),
|
|
310
|
+
File.join(Dir.pwd, "hooks", input_script_name)
|
|
311
|
+
]
|
|
312
|
+
|
|
313
|
+
local_input_content = nil
|
|
314
|
+
local_input_paths.each do |path|
|
|
315
|
+
if File.exist?(path)
|
|
316
|
+
local_input_content = File.read(path)
|
|
317
|
+
puts " (using local input hook: #{path})"
|
|
318
|
+
break
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
if local_input_content
|
|
323
|
+
File.write(input_dest_path, local_input_content)
|
|
324
|
+
File.chmod(0755, input_dest_path)
|
|
325
|
+
puts "✅ Installed SessionStart input hook (from local) to #{input_dest_path}"
|
|
326
|
+
else
|
|
327
|
+
puts "📥 Downloading SessionStart input hook..."
|
|
328
|
+
input_hook_url = "#{TINKER_RAW_URL}/hooks/#{input_script_name}"
|
|
329
|
+
begin
|
|
330
|
+
input_hook_content = URI.open(input_hook_url).read
|
|
331
|
+
File.write(input_dest_path, input_hook_content)
|
|
332
|
+
File.chmod(0755, input_dest_path)
|
|
333
|
+
puts "✅ Installed SessionStart input hook (from remote) to #{input_dest_path}"
|
|
334
|
+
rescue OpenURI::HTTPError => e
|
|
335
|
+
puts "⚠️ Failed to download input hook: #{e.message}"
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
|
|
269
339
|
File.write(home_settings_json, JSON.pretty_generate(settings_config))
|
|
270
340
|
puts "✅ ~/.claude/settings.json configured with skill hooks"
|
|
271
341
|
end
|
|
@@ -277,6 +347,37 @@ def setup_system_prompt!
|
|
|
277
347
|
puts "❌ /etc/tinker/system-prompt.txt not found!"
|
|
278
348
|
exit 1
|
|
279
349
|
end
|
|
350
|
+
|
|
351
|
+
# Add repository context to CLAUDE.md for easy reference
|
|
352
|
+
claude_md = File.expand_path("~/CLAUDE.md")
|
|
353
|
+
if ENV["REPOSITORIES_CONTEXT"]
|
|
354
|
+
repo_info = ENV["REPOSITORIES_CONTEXT"].split(',').map do |repo|
|
|
355
|
+
name, path = repo.split(':')
|
|
356
|
+
" - **#{name}**: #{path}"
|
|
357
|
+
end.join("\n")
|
|
358
|
+
|
|
359
|
+
repo_context_md = <<~MARKDOWN
|
|
360
|
+
## Repository Structure
|
|
361
|
+
|
|
362
|
+
This project has #{ENV["REPOSITORIES_CONTEXT"].split(',').count} repositories:
|
|
363
|
+
|
|
364
|
+
#{repo_info}
|
|
365
|
+
|
|
366
|
+
### Repository Switching
|
|
367
|
+
|
|
368
|
+
Use `switch-to-repo.sh <name>` to change working directories:
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
switch-to-repo.sh tinker
|
|
372
|
+
switch-to-repo.sh tinker-public
|
|
373
|
+
```
|
|
374
|
+
MARKDOWN
|
|
375
|
+
|
|
376
|
+
# Append to existing CLAUDE.md or create new one
|
|
377
|
+
existing_content = File.exist?(claude_md) ? File.read(claude_md) : ""
|
|
378
|
+
File.write(claude_md, existing_content + "\n" + repo_context_md)
|
|
379
|
+
puts "📝 Repository context added to ~/CLAUDE.md"
|
|
380
|
+
end
|
|
280
381
|
end
|
|
281
382
|
|
|
282
383
|
|
|
@@ -424,34 +525,124 @@ def setup_github_auth!
|
|
|
424
525
|
|
|
425
526
|
# Configure git
|
|
426
527
|
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'")
|
|
427
|
-
|
|
428
|
-
# Configure gh CLI wrapper for auto-refresh
|
|
429
|
-
real_gh_path
|
|
430
|
-
|
|
431
|
-
wrapper_path = "/usr/local/bin/gh"
|
|
432
|
-
wrapper_content = <<~BASH
|
|
433
|
-
#!/bin/bash
|
|
434
|
-
# Auto-refresh GitHub token using git-auth-helper
|
|
435
|
-
export GH_TOKEN=$(#{helper_path})
|
|
436
|
-
exec #{real_gh_path} "$@"
|
|
437
|
-
BASH
|
|
438
|
-
|
|
439
|
-
File.write("/tmp/gh-wrapper", wrapper_content)
|
|
440
|
-
system("sudo mv /tmp/gh-wrapper #{wrapper_path}")
|
|
441
|
-
system("sudo chmod +x #{wrapper_path}")
|
|
442
|
-
puts "✅ GitHub App authentication configured (with auto-refresh)"
|
|
443
|
-
else
|
|
444
|
-
puts "⚠️ Could not find 'gh' at #{real_gh_path}, skipping wrapper"
|
|
528
|
+
|
|
529
|
+
# Configure gh CLI wrapper for auto-refresh and permission controls
|
|
530
|
+
if install_gh_wrapper!(real_gh_path: "/usr/bin/gh", with_token_refresh: true, token_helper_path: helper_path)
|
|
531
|
+
puts "✅ GitHub App authentication configured (with auto-refresh + permission controls)"
|
|
445
532
|
end
|
|
446
533
|
|
|
447
534
|
elsif ENV["GH_TOKEN"] && !ENV["GH_TOKEN"].empty?
|
|
448
535
|
system("echo '#{ENV['GH_TOKEN']}' | gh auth login --with-token 2>/dev/null")
|
|
449
|
-
|
|
536
|
+
|
|
537
|
+
# Install wrapper for permission controls even with GH_TOKEN
|
|
538
|
+
if install_gh_wrapper!(real_gh_path: "/usr/bin/gh")
|
|
539
|
+
puts "🔐 GitHub authentication configured (with permission controls)"
|
|
540
|
+
else
|
|
541
|
+
puts "🔐 GitHub authentication configured"
|
|
542
|
+
end
|
|
450
543
|
else
|
|
451
544
|
puts "⚠️ No GH_TOKEN or GitHub App config - GitHub operations may fail"
|
|
452
545
|
end
|
|
453
546
|
end
|
|
454
547
|
|
|
548
|
+
def install_gh_wrapper!(real_gh_path:, with_token_refresh: false, token_helper_path: nil)
|
|
549
|
+
unless File.exist?(real_gh_path)
|
|
550
|
+
puts "⚠️ Could not find 'gh' at #{real_gh_path}, skipping wrapper"
|
|
551
|
+
return false
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
# Move real gh to gh.real if not already done
|
|
555
|
+
unless File.exist?("#{real_gh_path}.real")
|
|
556
|
+
system("sudo mv #{real_gh_path} #{real_gh_path}.real")
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
wrapper_path = "/usr/local/bin/gh"
|
|
560
|
+
|
|
561
|
+
# Build token export line if auto-refresh is enabled
|
|
562
|
+
token_export = with_token_refresh ? "export GH_TOKEN=$(#{token_helper_path})\n\n" : ""
|
|
563
|
+
|
|
564
|
+
wrapper_content = <<~BASH
|
|
565
|
+
#!/bin/bash
|
|
566
|
+
# GitHub CLI wrapper with permission controls for agents
|
|
567
|
+
#{token_export}# Check if caller is an agent (read AGENT_TYPE at runtime)
|
|
568
|
+
if [ -n "$AGENT_TYPE" ] && [[ "$AGENT_TYPE" =~ ^(worker|planner|reviewer|researcher)$ ]]; then
|
|
569
|
+
# Block comment-related commands for agents
|
|
570
|
+
if [[ "$1" == "comment" ]] || ([[ "$1" == "pr" ]] && [[ "$2" == "comment" ]]); then
|
|
571
|
+
echo "❌ You cannot use 'gh $*' commands as an agent." >&2
|
|
572
|
+
echo "" >&2
|
|
573
|
+
echo "Tinker has its own comment system via the add_comment MCP tool." >&2
|
|
574
|
+
echo "Please use the add_comment tool instead of gh commands." >&2
|
|
575
|
+
echo "" >&2
|
|
576
|
+
echo "Example:" >&2
|
|
577
|
+
echo " add_comment(ticket_id: 123, content: \\"Your comment here\\", comment_type: \\"note\\")" >&2
|
|
578
|
+
exit 1
|
|
579
|
+
fi
|
|
580
|
+
fi
|
|
581
|
+
|
|
582
|
+
# Execute real gh binary with all arguments
|
|
583
|
+
exec #{real_gh_path}.real "$@"
|
|
584
|
+
BASH
|
|
585
|
+
|
|
586
|
+
File.write("/tmp/gh-wrapper", wrapper_content)
|
|
587
|
+
system("sudo mv /tmp/gh-wrapper #{wrapper_path}")
|
|
588
|
+
system("sudo chmod +x #{wrapper_path}")
|
|
589
|
+
true
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
def clone_repositories!
|
|
593
|
+
return unless ENV["REPOSITORIES_CONFIG"]
|
|
594
|
+
|
|
595
|
+
begin
|
|
596
|
+
repos = JSON.parse(ENV["REPOSITORIES_CONFIG"])
|
|
597
|
+
return unless repos.is_a?(Array) && repos.any?
|
|
598
|
+
|
|
599
|
+
puts "📚 Cloning #{repos.count} repositories..."
|
|
600
|
+
|
|
601
|
+
repos.each do |repo|
|
|
602
|
+
repo_name = repo["name"]
|
|
603
|
+
github_url = repo["github_url"]
|
|
604
|
+
sandbox_path = repo["path_in_sandbox"] || "/workspace/#{repo_name}"
|
|
605
|
+
|
|
606
|
+
# Create parent directory if needed
|
|
607
|
+
parent_dir = File.dirname(sandbox_path)
|
|
608
|
+
FileUtils.mkdir_p(parent_dir) unless File.exist?(parent_dir)
|
|
609
|
+
|
|
610
|
+
# Clone repository if it doesn't exist
|
|
611
|
+
unless File.exist?(sandbox_path)
|
|
612
|
+
puts " 📥 Cloning #{repo_name} to #{sandbox_path}..."
|
|
613
|
+
system("git", "clone", "--depth", "1", github_url, sandbox_path)
|
|
614
|
+
else
|
|
615
|
+
puts " ✓ #{repo_name} already exists at #{sandbox_path}"
|
|
616
|
+
end
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
puts "✅ Repositories cloned"
|
|
620
|
+
|
|
621
|
+
# Write repository context to a file for hooks to read
|
|
622
|
+
# This is more reliable than environment variables in tmux sessions
|
|
623
|
+
if ENV["REPOSITORIES_CONTEXT"]
|
|
624
|
+
repo_context_file = File.expand_path("~/.claude/repos_context.txt")
|
|
625
|
+
File.write(repo_context_file, ENV["REPOSITORIES_CONTEXT"])
|
|
626
|
+
puts "📝 Repository context written to #{repo_context_file}"
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
# Export REPOSITORIES_CONTEXT to bash profile for shell sessions
|
|
630
|
+
if ENV["REPOSITORIES_CONTEXT"]
|
|
631
|
+
profile_file = File.expand_path("~/.bashrc")
|
|
632
|
+
existing_content = File.exist?(profile_file) ? File.read(profile_file) : ""
|
|
633
|
+
|
|
634
|
+
# Remove old export if present
|
|
635
|
+
existing_content.gsub!(/^export REPOSITORIES_CONTEXT=.*$/, "")
|
|
636
|
+
|
|
637
|
+
# Add new export at the end
|
|
638
|
+
File.write(profile_file, existing_content + "\nexport REPOSITORIES_CONTEXT=\"#{ENV['REPOSITORIES_CONTEXT']}\"\n")
|
|
639
|
+
puts "📝 REPOSITORIES_CONTEXT exported to ~/.bashrc"
|
|
640
|
+
end
|
|
641
|
+
rescue JSON::ParserError => e
|
|
642
|
+
puts "⚠️ Failed to parse REPOSITORIES_CONFIG: #{e.message}"
|
|
643
|
+
end
|
|
644
|
+
end
|
|
645
|
+
|
|
455
646
|
def setup_git_config!
|
|
456
647
|
# Configure identity if provided
|
|
457
648
|
if ENV["GIT_USER_NAME"] && !ENV["GIT_USER_NAME"].empty?
|
|
@@ -514,6 +705,71 @@ def setup_git_hooks!
|
|
|
514
705
|
puts "✅ Git hooks installed"
|
|
515
706
|
end
|
|
516
707
|
|
|
708
|
+
def setup_multi_repo_scripts!
|
|
709
|
+
# Install switch-to-repo.sh script and repo-specific setup scripts
|
|
710
|
+
# These are only needed when using multi-repo setup
|
|
711
|
+
repos_dir = File.expand_path("~/repos")
|
|
712
|
+
bin_dir = "/usr/local/bin"
|
|
713
|
+
|
|
714
|
+
puts "📚 Setting up multi-repo scripts..."
|
|
715
|
+
|
|
716
|
+
# Create repos directory for repo-specific setup scripts
|
|
717
|
+
FileUtils.mkdir_p(repos_dir)
|
|
718
|
+
|
|
719
|
+
# Install switch-to-repo.sh script
|
|
720
|
+
switch_script = File.join(bin_dir, "switch-to-repo.sh")
|
|
721
|
+
|
|
722
|
+
# Try local copy first, then download from GitHub
|
|
723
|
+
local_paths = [
|
|
724
|
+
File.join(Dir.pwd, "tinker-public", "hooks", "switch-to-repo.sh"),
|
|
725
|
+
File.join(Dir.pwd, "hooks", "switch-to-repo.sh"),
|
|
726
|
+
"/tmp/hooks/switch-to-repo.sh"
|
|
727
|
+
]
|
|
728
|
+
|
|
729
|
+
script_content = nil
|
|
730
|
+
local_paths.each do |path|
|
|
731
|
+
if File.exist?(path)
|
|
732
|
+
script_content = File.read(path)
|
|
733
|
+
puts " (from local: #{path})"
|
|
734
|
+
break
|
|
735
|
+
end
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
unless script_content
|
|
739
|
+
url = "#{TINKER_RAW_URL}/hooks/switch-to-repo.sh"
|
|
740
|
+
begin
|
|
741
|
+
script_content = URI.open(url).read
|
|
742
|
+
rescue OpenURI::HTTPError => e
|
|
743
|
+
puts "⚠️ Failed to download switch-to-repo.sh: #{e.message}"
|
|
744
|
+
return
|
|
745
|
+
end
|
|
746
|
+
end
|
|
747
|
+
|
|
748
|
+
# Write to temp file first, then sudo mv
|
|
749
|
+
temp_script = "/tmp/switch-to-repo.sh.temp"
|
|
750
|
+
File.write(temp_script, script_content)
|
|
751
|
+
system("sudo mv #{temp_script} #{switch_script}")
|
|
752
|
+
system("sudo chmod +x #{switch_script}")
|
|
753
|
+
puts "✅ Installed switch-to-repo.sh to #{bin_dir}"
|
|
754
|
+
|
|
755
|
+
# Install repo-specific setup scripts
|
|
756
|
+
# These are example scripts that can be customized per repository
|
|
757
|
+
local_repos_dir = File.join(Dir.pwd, "tinker-public", "repos")
|
|
758
|
+
|
|
759
|
+
if File.directory?(local_repos_dir)
|
|
760
|
+
Dir.glob(File.join(local_repos_dir, "*.sh")).each do |script_file|
|
|
761
|
+
script_name = File.basename(script_file)
|
|
762
|
+
dest_script = File.join(repos_dir, script_name)
|
|
763
|
+
FileUtils.cp(script_file, dest_script)
|
|
764
|
+
File.chmod(0755, dest_script)
|
|
765
|
+
puts " - Installed #{script_name}"
|
|
766
|
+
end
|
|
767
|
+
puts "✅ Installed repo-specific setup scripts"
|
|
768
|
+
else
|
|
769
|
+
puts "ℹ️ No repo-specific setup scripts found (skipping)"
|
|
770
|
+
end
|
|
771
|
+
end
|
|
772
|
+
|
|
517
773
|
def download_agent_bridge!
|
|
518
774
|
# Detect architecture
|
|
519
775
|
arch = `uname -m`.strip
|
|
@@ -526,20 +782,109 @@ def download_agent_bridge!
|
|
|
526
782
|
exit 1
|
|
527
783
|
end
|
|
528
784
|
|
|
529
|
-
bridge_url = "#{TINKER_RAW_URL}/bin/agent-bridge-linux-#{arch}"
|
|
530
|
-
bridge_tmux_url = "#{TINKER_RAW_URL}/bin/agent-bridge-tmux"
|
|
531
785
|
target_dir = "/usr/local/bin"
|
|
532
786
|
|
|
533
|
-
# Check
|
|
787
|
+
# 1. Check for local override in /tmp/overrides (development mode)
|
|
788
|
+
override_dir = "/tmp/overrides"
|
|
789
|
+
if Dir.exist?(override_dir)
|
|
790
|
+
local_override = File.join(override_dir, "agent-bridge-linux-#{arch}")
|
|
791
|
+
if File.exist?(local_override)
|
|
792
|
+
puts "🔧 Using local override: #{local_override}"
|
|
793
|
+
system("sudo cp #{local_override} #{target_dir}/agent-bridge")
|
|
794
|
+
system("sudo chmod +x #{target_dir}/agent-bridge")
|
|
795
|
+
puts "✅ agent-bridge installed from local override"
|
|
796
|
+
return download_agent_bridge_tmux!(target_dir)
|
|
797
|
+
end
|
|
798
|
+
end
|
|
799
|
+
|
|
800
|
+
# 2. Check if binaries are mounted at /tmp (dev mode)
|
|
534
801
|
if File.exist?("/tmp/agent-bridge")
|
|
535
802
|
puts "🔧 Installing mounted agent-bridge..."
|
|
536
803
|
system("sudo cp /tmp/agent-bridge #{target_dir}/agent-bridge")
|
|
537
804
|
system("sudo chmod +x #{target_dir}/agent-bridge")
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
805
|
+
puts "✅ agent-bridge installed from mount"
|
|
806
|
+
return download_agent_bridge_tmux!(target_dir)
|
|
807
|
+
end
|
|
808
|
+
|
|
809
|
+
# 3. Try Civo bucket download (production mode)
|
|
810
|
+
if ENV["CIVO_ACCESS_KEY_ID"] && ENV["CIVO_SECRET_KEY"]
|
|
811
|
+
puts "📥 Downloading agent-bridge from Civo bucket..."
|
|
812
|
+
if download_from_civo!(arch, target_dir)
|
|
813
|
+
return download_agent_bridge_tmux!(target_dir)
|
|
814
|
+
else
|
|
815
|
+
puts "⚠️ Civo download failed, falling back to GitHub..."
|
|
816
|
+
end
|
|
817
|
+
end
|
|
818
|
+
|
|
819
|
+
# 4. Fallback to GitHub raw URLs (legacy)
|
|
820
|
+
bridge_url = "#{TINKER_RAW_URL}/bin/agent-bridge-linux-#{arch}"
|
|
821
|
+
puts "📥 Downloading agent-bridge for linux-#{arch} from GitHub..."
|
|
822
|
+
system("sudo curl -fsSL #{bridge_url} -o #{target_dir}/agent-bridge")
|
|
823
|
+
system("sudo chmod +x #{target_dir}/agent-bridge")
|
|
824
|
+
|
|
825
|
+
puts "✅ agent-bridge installed to #{target_dir}"
|
|
826
|
+
download_agent_bridge_tmux!(target_dir)
|
|
827
|
+
end
|
|
828
|
+
|
|
829
|
+
def download_from_civo!(arch, target_dir)
|
|
830
|
+
require "aws-sdk-s3"
|
|
831
|
+
|
|
832
|
+
region = ENV["CIVO_REGION"] || "LON1"
|
|
833
|
+
endpoint = ENV["CIVO_ENDPOINT"] || "https://objectstore.fra1.civo.com"
|
|
834
|
+
bucket = ENV["TINKER_BINARIES_BUCKET"] || "tinker"
|
|
835
|
+
version = ENV["AGENT_BRIDGE_VERSION"] || "latest"
|
|
836
|
+
|
|
837
|
+
binary_name = "agent-bridge-linux-#{arch}"
|
|
838
|
+
key = "agent-bridge/#{version}/#{binary_name}"
|
|
839
|
+
|
|
840
|
+
puts " Region: #{region}"
|
|
841
|
+
puts " Bucket: #{bucket}"
|
|
842
|
+
puts " Version: #{version}"
|
|
843
|
+
puts " Binary: #{binary_name}"
|
|
844
|
+
|
|
845
|
+
begin
|
|
846
|
+
# Configure Civo S3-compatible client
|
|
847
|
+
Aws.config.update({
|
|
848
|
+
region: region,
|
|
849
|
+
endpoint: endpoint,
|
|
850
|
+
access_key_id: ENV["CIVO_ACCESS_KEY_ID"],
|
|
851
|
+
secret_access_key: ENV["CIVO_SECRET_KEY"]
|
|
852
|
+
})
|
|
853
|
+
|
|
854
|
+
s3 = Aws::S3::Resource.new
|
|
855
|
+
obj = s3.bucket(bucket).object(key)
|
|
856
|
+
|
|
857
|
+
# Generate presigned URL (valid for 1 hour)
|
|
858
|
+
url = obj.presigned_url(:get, expires_in: 3600)
|
|
859
|
+
|
|
860
|
+
puts "✅ Generated presigned URL"
|
|
861
|
+
|
|
862
|
+
# Download via curl
|
|
863
|
+
system("sudo curl -fsSL #{url} -o #{target_dir}/agent-bridge")
|
|
864
|
+
|
|
865
|
+
unless $?.success?
|
|
866
|
+
puts "❌ Failed to download agent-bridge from Civo"
|
|
867
|
+
return false
|
|
868
|
+
end
|
|
869
|
+
|
|
541
870
|
system("sudo chmod +x #{target_dir}/agent-bridge")
|
|
871
|
+
puts "✅ Downloaded agent-bridge from Civo"
|
|
872
|
+
return true
|
|
873
|
+
|
|
874
|
+
rescue LoadError
|
|
875
|
+
puts "⚠️ aws-sdk-s3 gem not installed"
|
|
876
|
+
return false
|
|
877
|
+
rescue Aws::S3::Errors::NoSuchKey
|
|
878
|
+
puts "❌ Version #{version} not found in bucket"
|
|
879
|
+
return false
|
|
880
|
+
rescue => e
|
|
881
|
+
puts "❌ Civo download failed: #{e.message}"
|
|
882
|
+
return false
|
|
542
883
|
end
|
|
884
|
+
end
|
|
885
|
+
|
|
886
|
+
def download_agent_bridge_tmux!(target_dir)
|
|
887
|
+
bridge_tmux_url = "#{TINKER_RAW_URL}/bin/agent-bridge-tmux"
|
|
543
888
|
|
|
544
889
|
if File.exist?("/tmp/agent-bridge-tmux")
|
|
545
890
|
puts "🔧 Installing mounted agent-bridge-tmux..."
|
|
@@ -549,8 +894,6 @@ def download_agent_bridge!
|
|
|
549
894
|
system("sudo curl -fsSL #{bridge_tmux_url} -o #{target_dir}/agent-bridge-tmux")
|
|
550
895
|
system("sudo chmod +x #{target_dir}/agent-bridge-tmux")
|
|
551
896
|
end
|
|
552
|
-
|
|
553
|
-
puts "✅ agent-bridge installed to #{target_dir}"
|
|
554
897
|
|
|
555
898
|
# Patch agent-bridge-tmux to force INSIDE_TMUX=1
|
|
556
899
|
# Note: This is now fixed in the repo, but we keep this for backward compatibility
|
|
@@ -602,14 +945,17 @@ puts ""
|
|
|
602
945
|
check_env!
|
|
603
946
|
fetch_agent_id!
|
|
604
947
|
setup_mcp_config!
|
|
948
|
+
setup_system_prompt!
|
|
949
|
+
# Clone repositories BEFORE setting up hooks that depend on them
|
|
950
|
+
setup_github_auth!
|
|
951
|
+
setup_git_config!
|
|
952
|
+
clone_repositories!
|
|
605
953
|
setup_claude_config!
|
|
606
954
|
setup_claude_settings!
|
|
607
|
-
setup_system_prompt!
|
|
608
955
|
# setup_skill_hooks! # Deprecated in favor of global hooks
|
|
609
956
|
setup_skills!
|
|
610
|
-
setup_github_auth!
|
|
611
|
-
setup_git_config!
|
|
612
957
|
setup_git_hooks!
|
|
958
|
+
setup_multi_repo_scripts!
|
|
613
959
|
# prepare_git_state! is now in entrypoint.sh
|
|
614
960
|
bin_dir = download_agent_bridge!
|
|
615
961
|
run_agent!(bin_dir)
|