openclacky 0.9.26 → 0.9.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.clacky/skills/gem-release/SKILL.md +8 -4
- data/CHANGELOG.md +25 -0
- data/lib/clacky/agent/skill_manager.rb +11 -4
- data/lib/clacky/agent.rb +14 -20
- data/lib/clacky/agent_config.rb +40 -1
- data/lib/clacky/brand_config.rb +36 -82
- data/lib/clacky/client.rb +12 -2
- data/lib/clacky/default_skills/code-explorer/SKILL.md +1 -0
- data/lib/clacky/default_skills/new/SKILL.md +22 -2
- data/lib/clacky/default_skills/new/scripts/create_rails_project.sh +30 -33
- data/lib/clacky/default_skills/personal-website/publish.rb +59 -38
- data/lib/clacky/message_format/anthropic.rb +9 -1
- data/lib/clacky/message_format/bedrock.rb +4 -2
- data/lib/clacky/platform_http_client.rb +199 -0
- data/lib/clacky/providers.rb +19 -0
- data/lib/clacky/server/http_server.rb +18 -2
- data/lib/clacky/server/web_ui_controller.rb +42 -1
- data/lib/clacky/tools/base.rb +3 -0
- data/lib/clacky/tools/browser.rb +3 -12
- data/lib/clacky/tools/file_reader.rb +9 -13
- data/lib/clacky/tools/glob.rb +5 -5
- data/lib/clacky/tools/grep.rb +1 -1
- data/lib/clacky/tools/safe_shell.rb +2 -2
- data/lib/clacky/tools/shell.rb +42 -42
- data/lib/clacky/ui2/ui_controller.rb +34 -30
- data/lib/clacky/ui_interface.rb +1 -0
- data/lib/clacky/utils/file_processor.rb +122 -2
- data/lib/clacky/utils/scripts_manager.rb +1 -0
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +110 -0
- data/lib/clacky/web/app.js +109 -1
- data/lib/clacky/web/i18n.js +6 -0
- data/lib/clacky/web/index.html +19 -0
- data/lib/clacky/web/sessions.js +56 -10
- data/lib/clacky/web/settings.js +14 -2
- data/lib/clacky/web/skills.js +26 -2
- data/lib/clacky.rb +2 -0
- data/scripts/build/build.sh +329 -0
- data/scripts/build/lib/apt.sh +36 -0
- data/scripts/build/lib/brew.sh +89 -0
- data/scripts/build/lib/colors.sh +17 -0
- data/scripts/build/lib/gem.sh +95 -0
- data/scripts/build/lib/mise.sh +125 -0
- data/scripts/build/lib/network.sh +156 -0
- data/scripts/build/lib/os.sh +57 -0
- data/scripts/build/lib/shell.sh +37 -0
- data/scripts/build/src/install.sh.cc +164 -0
- data/scripts/build/src/install_browser.sh.cc +101 -0
- data/scripts/build/src/install_full.sh.cc +290 -0
- data/scripts/build/src/install_rails_deps.sh.cc +145 -0
- data/scripts/build/src/install_system_deps.sh.cc +123 -0
- data/scripts/build/src/uninstall.sh.cc +101 -0
- data/scripts/install.sh +205 -307
- data/scripts/install_browser.sh +313 -114
- data/scripts/install_full.sh +528 -589
- data/scripts/install_rails_deps.sh +725 -0
- data/scripts/install_system_deps.sh +364 -128
- data/scripts/uninstall.sh +213 -89
- metadata +24 -5
- data/lib/clacky/default_skills/new/scripts/rails_env_checker.sh +0 -389
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fc55d904ebe65b0a5d464481ef0075dcb3271e8ccd56143b3ea2b04600100a41
|
|
4
|
+
data.tar.gz: e3aab9ed25ac14fc964eff4b2032322679802bb8f627286e60ab7bdc14d80289
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 361f37d7bcb4546d0ff92783bdc3018068cb47d411b80595fdcd003c8d62f007e9483ccce46292f7b1fe5a9828ef4e9779f7553ad747e21c388bf39de0b12487
|
|
7
|
+
data.tar.gz: 04db6b2d4d939d58e49265dc3c000b82455b857e1634987c4c5218ae716e7ed9d4e7f7950964d56aa484c29ee11290b32721e3000c1bc51022edee14eb417d2b
|
|
@@ -132,10 +132,13 @@ To use this skill, simply say:
|
|
|
132
132
|
|
|
133
133
|
6. **Sync scripts/ to OSS**
|
|
134
134
|
|
|
135
|
-
After updating latest.txt,
|
|
135
|
+
After updating latest.txt, first rebuild all shell scripts from templates, then sync to OSS:
|
|
136
136
|
|
|
137
137
|
```bash
|
|
138
|
-
#
|
|
138
|
+
# Step 1: Rebuild .sh files from .sh.cc templates
|
|
139
|
+
bash scripts/build/build.sh
|
|
140
|
+
|
|
141
|
+
# Step 2: Upload each script file to OSS
|
|
139
142
|
for script in scripts/*; do
|
|
140
143
|
coscli cp "$script" cos://clackyai-1258723534/clacky-ai/openclacky/main/scripts/$(basename "$script")
|
|
141
144
|
done
|
|
@@ -144,7 +147,7 @@ To use this skill, simply say:
|
|
|
144
147
|
curl -fsSL https://oss.1024code.com/clacky-ai/openclacky/main/scripts/install.sh | head -5
|
|
145
148
|
```
|
|
146
149
|
|
|
147
|
-
This ensures `scripts/install.sh`, `scripts/install_simple.sh`, `scripts/install.ps1`, `scripts/uninstall.sh` and any future scripts are
|
|
150
|
+
This ensures `scripts/install.sh`, `scripts/install_simple.sh`, `scripts/install.ps1`, `scripts/uninstall.sh` and any future scripts are compiled from latest templates and mirrored on OSS.
|
|
148
151
|
|
|
149
152
|
> **Prerequisite**: Same `coscli` setup as above
|
|
150
153
|
|
|
@@ -338,7 +341,8 @@ echo "X.Y.Z" > /tmp/latest.txt
|
|
|
338
341
|
coscli cp /tmp/latest.txt cos://clackyai-1258723534/openclacky/latest.txt
|
|
339
342
|
curl -fsSL https://oss.1024code.com/openclacky/latest.txt # verify
|
|
340
343
|
|
|
341
|
-
# Sync scripts/ to OSS
|
|
344
|
+
# Sync scripts/ to OSS (build from templates first)
|
|
345
|
+
bash scripts/build/build.sh
|
|
342
346
|
for script in scripts/*; do
|
|
343
347
|
coscli cp "$script" cos://clackyai-1258723534/clacky-ai/openclacky/main/scripts/$(basename "$script")
|
|
344
348
|
done
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.9.27] - 2026-04-07
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Image understanding in file_reader**: the agent can now read and analyse images (PNG, JPG, GIF, WebP) by sending them to the vision API — just attach or reference an image file
|
|
14
|
+
- **Image auto-resize before upload**: large images are automatically resized to fit within model limits (max 5 MB base64), so vision requests no longer fail on high-resolution files
|
|
15
|
+
- **Rails project installer script**: new `install_rails_deps.sh` script sets up a complete Ruby on Rails development environment (Ruby, Bundler, Node, Yarn, PostgreSQL) in one command
|
|
16
|
+
- **Uninstall script**: new `scripts/uninstall.sh` to cleanly remove the openclacky gem and its associated files
|
|
17
|
+
- **Shell script build system**: `scripts/build/` now contains a template compiler (`.sh.cc` → `.sh`) with dependency checking — install scripts are generated from composable library modules
|
|
18
|
+
- **stdout streaming in Web UI**: agent tool output and shell results are now streamed live to the browser as they arrive, rather than waiting for a full response
|
|
19
|
+
- **Ctrl+O shortcut in CLI**: pressing Ctrl+O in the terminal UI opens a file/output viewer for the current session
|
|
20
|
+
|
|
21
|
+
### Improved
|
|
22
|
+
- **Smart error recovery on 400 responses**: the agent now rolls back its message history when an API request is rejected as malformed (BadRequestError), preventing the same bad message from being replayed on every subsequent turn
|
|
23
|
+
- **Brand skill reliability**: brand skills now auto-retry on transient failures and fall back gracefully if the remote skill is temporarily unavailable
|
|
24
|
+
- **Shell tool RC file loading**: shell commands now correctly source `.bashrc` / `.zshrc` so user-defined aliases and environment variables are available inside tool executions
|
|
25
|
+
- **Shell UTF-8 encoding**: fixed a warning about character encoding when shell output contains non-ASCII characters
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
- **Shell UTF-8 warning suppression**: eliminated noisy encoding warnings that appeared in shell tool output on some macOS setups
|
|
29
|
+
|
|
30
|
+
### More
|
|
31
|
+
- Lite mode configuration groundwork for clackyai platform
|
|
32
|
+
- Rails new-project skill updated with improved environment checker
|
|
33
|
+
- `new` skill improvements: http_server integration and tool category support
|
|
34
|
+
|
|
10
35
|
## [0.9.26] - 2026-04-03
|
|
11
36
|
|
|
12
37
|
### Added
|
|
@@ -379,9 +379,6 @@ module Clacky
|
|
|
379
379
|
# @param arguments [String] Arguments for the skill
|
|
380
380
|
# @return [String] Summary of subagent execution
|
|
381
381
|
def execute_skill_with_subagent(skill, arguments)
|
|
382
|
-
# Log subagent fork
|
|
383
|
-
@ui&.show_info("Subagent start: #{skill.identifier}")
|
|
384
|
-
|
|
385
382
|
# For encrypted brand skills with supporting scripts: decrypt to a tmpdir.
|
|
386
383
|
# Subagent path has a clear boundary (subagent.run returns), so we shred inline
|
|
387
384
|
# rather than registering on the parent agent.
|
|
@@ -406,6 +403,10 @@ module Clacky
|
|
|
406
403
|
system_prompt_suffix: skill_instructions
|
|
407
404
|
)
|
|
408
405
|
|
|
406
|
+
# Log which model the subagent is actually using (may differ from requested
|
|
407
|
+
# when "lite" falls back to default due to no lite model configured)
|
|
408
|
+
@ui&.show_info("Subagent start: #{skill.identifier} [#{subagent.current_model_info[:model]}]")
|
|
409
|
+
|
|
409
410
|
# Run subagent with the actual task as the sole user turn.
|
|
410
411
|
# If the user typed the skill command with no arguments (e.g. "/jade-appraisal"),
|
|
411
412
|
# use a generic trigger phrase so the user message is never empty.
|
|
@@ -446,8 +447,14 @@ module Clacky
|
|
|
446
447
|
m[:skill_name] = skill.identifier
|
|
447
448
|
end
|
|
448
449
|
|
|
450
|
+
# Merge subagent cost into parent agent's total so the sessionbar reflects
|
|
451
|
+
# the real cumulative spend across all subagents
|
|
452
|
+
subagent_cost = result[:total_cost_usd] || 0.0
|
|
453
|
+
@total_cost += subagent_cost
|
|
454
|
+
@ui&.update_sessionbar(cost: @total_cost)
|
|
455
|
+
|
|
449
456
|
# Log completion
|
|
450
|
-
@ui&.show_info("Subagent completed: #{result[:iterations]} iterations, $#{
|
|
457
|
+
@ui&.show_info("Subagent completed: #{result[:iterations]} iterations, $#{subagent_cost.round(4)} (total: $#{@total_cost.round(4)})")
|
|
451
458
|
|
|
452
459
|
# Return summary as the skill execution result
|
|
453
460
|
summary
|
data/lib/clacky/agent.rb
CHANGED
|
@@ -432,6 +432,11 @@ module Clacky
|
|
|
432
432
|
}
|
|
433
433
|
Clacky::Logger.error("agent_run_error", error: e)
|
|
434
434
|
|
|
435
|
+
# 400 errors mean our request was malformed — roll back history so the bad
|
|
436
|
+
# message is not replayed on the next user turn.
|
|
437
|
+
# Other errors (auth, network, etc.) leave history intact for retry.
|
|
438
|
+
@pending_error_rollback = true if e.is_a?(Clacky::BadRequestError)
|
|
439
|
+
|
|
435
440
|
# Build error result for session data, but let CLI handle error display
|
|
436
441
|
result = build_result(:error, error: e.message) # rubocop:disable Lint/UselessAssignment
|
|
437
442
|
raise
|
|
@@ -647,35 +652,24 @@ module Clacky
|
|
|
647
652
|
# Automatic progress display after 2 seconds for any tool execution
|
|
648
653
|
progress_shown = false
|
|
649
654
|
progress_timer = nil
|
|
650
|
-
output_buffer = nil
|
|
651
655
|
|
|
652
656
|
if @ui
|
|
653
657
|
progress_message = build_tool_progress_message(call[:name], args)
|
|
654
658
|
|
|
655
|
-
# For shell
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
+
# For shell/safe_shell: inject on_output callback for real-time stdout streaming.
|
|
660
|
+
# The callback fires immediately on each read_nonblock chunk — no polling delay.
|
|
661
|
+
if (call[:name] == "shell" || call[:name] == "safe_shell") &&
|
|
662
|
+
@ui.respond_to?(:show_tool_stdout)
|
|
663
|
+
args[:on_output] = ->(stream, data) {
|
|
664
|
+
@ui.show_tool_stdout([data]) if stream == :stdout
|
|
665
|
+
}
|
|
659
666
|
end
|
|
660
667
|
|
|
661
668
|
progress_timer = Thread.new do
|
|
662
669
|
sleep 2
|
|
663
|
-
@ui.show_progress(progress_message, prefix_newline: false
|
|
670
|
+
@ui.show_progress(progress_message, prefix_newline: false)
|
|
664
671
|
progress_shown = true
|
|
665
|
-
|
|
666
|
-
# For shell commands: stream new stdout lines to WebUI as they arrive
|
|
667
|
-
if output_buffer && @ui.respond_to?(:show_tool_stdout)
|
|
668
|
-
last_sent_count = 0
|
|
669
|
-
loop do
|
|
670
|
-
sleep 1
|
|
671
|
-
stdout_lines = output_buffer[:stdout_lines]&.to_a || []
|
|
672
|
-
new_lines = stdout_lines[last_sent_count..]
|
|
673
|
-
if new_lines && !new_lines.empty?
|
|
674
|
-
@ui.show_tool_stdout(new_lines)
|
|
675
|
-
last_sent_count = stdout_lines.size
|
|
676
|
-
end
|
|
677
|
-
end
|
|
678
|
-
end
|
|
672
|
+
# Streaming is handled by on_output callback — no polling loop needed here
|
|
679
673
|
end
|
|
680
674
|
end
|
|
681
675
|
|
data/lib/clacky/agent_config.rb
CHANGED
|
@@ -222,9 +222,45 @@ module Clacky
|
|
|
222
222
|
end
|
|
223
223
|
end
|
|
224
224
|
|
|
225
|
+
# Auto-inject lite model from provider preset when:
|
|
226
|
+
# 1. A default model exists
|
|
227
|
+
# 2. No lite model is configured yet (neither in file nor env)
|
|
228
|
+
# 3. The default model's provider has a known lite_model
|
|
229
|
+
# The injected lite model is runtime-only (not persisted to config.yml)
|
|
230
|
+
inject_provider_lite_model(models)
|
|
231
|
+
|
|
225
232
|
new(models: models)
|
|
226
233
|
end
|
|
227
234
|
|
|
235
|
+
# Auto-inject a lite model entry if the default model's provider supports one
|
|
236
|
+
# and no lite model is already present. The injected entry reuses the same
|
|
237
|
+
# api_key and base_url as the default model — only the model name differs.
|
|
238
|
+
# @param models [Array<Hash>] mutable models array (modified in-place)
|
|
239
|
+
private_class_method def self.inject_provider_lite_model(models)
|
|
240
|
+
return if models.any? { |m| m["type"] == "lite" }
|
|
241
|
+
|
|
242
|
+
default_model = models.find { |m| m["type"] == "default" } || models.first
|
|
243
|
+
return unless default_model
|
|
244
|
+
|
|
245
|
+
provider_id = Clacky::Providers.find_by_base_url(default_model["base_url"])
|
|
246
|
+
return unless provider_id
|
|
247
|
+
|
|
248
|
+
lite_model_name = Clacky::Providers.lite_model(provider_id)
|
|
249
|
+
return unless lite_model_name
|
|
250
|
+
|
|
251
|
+
# Don't inject if the default model IS the lite model
|
|
252
|
+
return if default_model["model"] == lite_model_name
|
|
253
|
+
|
|
254
|
+
models << {
|
|
255
|
+
"api_key" => default_model["api_key"],
|
|
256
|
+
"base_url" => default_model["base_url"],
|
|
257
|
+
"model" => lite_model_name,
|
|
258
|
+
"anthropic_format" => default_model["anthropic_format"] || false,
|
|
259
|
+
"type" => "lite",
|
|
260
|
+
"auto_injected" => true # Mark as auto-injected (not saved to file)
|
|
261
|
+
}
|
|
262
|
+
end
|
|
263
|
+
|
|
228
264
|
# Save configuration to file
|
|
229
265
|
# Deep copy — models array contains mutable Hashes, so a shallow dup would
|
|
230
266
|
# let the copy share the same Hash objects with the original, causing
|
|
@@ -244,8 +280,11 @@ module Clacky
|
|
|
244
280
|
end
|
|
245
281
|
|
|
246
282
|
# Convert to YAML format (top-level array)
|
|
283
|
+
# Auto-injected lite models (auto_injected: true) are excluded from persistence —
|
|
284
|
+
# they are regenerated at load time from the provider preset.
|
|
247
285
|
def to_yaml
|
|
248
|
-
|
|
286
|
+
persistable = @models.reject { |m| m["auto_injected"] }
|
|
287
|
+
YAML.dump(persistable)
|
|
249
288
|
end
|
|
250
289
|
|
|
251
290
|
# Check if any model is configured
|
data/lib/clacky/brand_config.rb
CHANGED
|
@@ -33,11 +33,6 @@ module Clacky
|
|
|
33
33
|
CONFIG_DIR = File.join(Dir.home, ".clacky")
|
|
34
34
|
BRAND_FILE = File.join(CONFIG_DIR, "brand.yml")
|
|
35
35
|
|
|
36
|
-
# OpenClacky Cloud API base URL.
|
|
37
|
-
# Override with CLACKY_LICENSE_SERVER env var for local development:
|
|
38
|
-
# CLACKY_LICENSE_SERVER=http://localhost:3000 bundle exec ruby bin/clacky server
|
|
39
|
-
API_BASE_URL = ENV.fetch("CLACKY_LICENSE_SERVER", "https://www.openclacky.com")
|
|
40
|
-
|
|
41
36
|
# How often to send a heartbeat (seconds) — once per day
|
|
42
37
|
HEARTBEAT_INTERVAL = 86_400
|
|
43
38
|
|
|
@@ -285,9 +280,6 @@ module Clacky
|
|
|
285
280
|
return { success: false, error: "License not activated" } unless activated?
|
|
286
281
|
return { success: false, error: "User license required to upload skills" } unless user_licensed?
|
|
287
282
|
|
|
288
|
-
require "net/http"
|
|
289
|
-
require "uri"
|
|
290
|
-
|
|
291
283
|
# The client skills API uses @license_user_id (the platform owner user id),
|
|
292
284
|
# NOT the user_id embedded in the license key structure.
|
|
293
285
|
user_id = @license_user_id.to_s
|
|
@@ -299,23 +291,17 @@ module Clacky
|
|
|
299
291
|
|
|
300
292
|
# POST /api/v1/client/skills → create (first upload)
|
|
301
293
|
# PATCH /api/v1/client/skills/:name → update (force overwrite)
|
|
302
|
-
if force
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
|
309
|
-
http.use_ssl = uri.scheme == "https"
|
|
310
|
-
http.open_timeout = 15
|
|
311
|
-
http.read_timeout = 60
|
|
294
|
+
path = if force
|
|
295
|
+
"/api/v1/client/skills/#{URI.encode_www_form_component(skill_name)}"
|
|
296
|
+
else
|
|
297
|
+
"/api/v1/client/skills"
|
|
298
|
+
end
|
|
312
299
|
|
|
313
300
|
boundary = "----ClackySkillUpload#{SecureRandom.hex(8)}"
|
|
314
301
|
crlf = "\r\n"
|
|
315
302
|
|
|
316
|
-
# Build multipart body as a binary string
|
|
317
|
-
#
|
|
318
|
-
# the body string — avoid by writing all parts as binary and using body_stream.
|
|
303
|
+
# Build multipart body as a binary string so that null bytes in the ZIP
|
|
304
|
+
# data are preserved. All parts are joined as binary before sending.
|
|
319
305
|
parts = []
|
|
320
306
|
fields = {
|
|
321
307
|
"key_hash" => key_hash,
|
|
@@ -339,34 +325,34 @@ module Clacky
|
|
|
339
325
|
parts << "--#{boundary}#{crlf}"
|
|
340
326
|
parts << "Content-Disposition: form-data; name=\"skill_zip\"; filename=\"#{skill_name}.zip\"#{crlf}"
|
|
341
327
|
parts << "Content-Type: application/zip#{crlf}#{crlf}"
|
|
342
|
-
# zip_data is binary — keep as-is
|
|
343
328
|
parts << zip_data.b
|
|
344
329
|
parts << "#{crlf}--#{boundary}--#{crlf}"
|
|
345
330
|
|
|
346
|
-
|
|
347
|
-
body_bytes = parts.map { |p| p.b }.join
|
|
331
|
+
body_bytes = parts.map(&:b).join
|
|
348
332
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
333
|
+
# Delegate sending (with retry + failover) to PlatformHttpClient.
|
|
334
|
+
# Uploads can be slow so we allow a generous 60-second read timeout.
|
|
335
|
+
result = if force
|
|
336
|
+
platform_client.multipart_patch(path, body_bytes, boundary, read_timeout: 60)
|
|
337
|
+
else
|
|
338
|
+
platform_client.multipart_post(path, body_bytes, boundary, read_timeout: 60)
|
|
339
|
+
end
|
|
353
340
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
code_i = response.code.to_i
|
|
358
|
-
if code_i == 200 || code_i == 201
|
|
341
|
+
if result[:success]
|
|
342
|
+
parsed = result[:data]
|
|
359
343
|
{ success: true, skill: parsed["skill"] }
|
|
360
344
|
else
|
|
361
|
-
#
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
msg =
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
# so the caller can offer
|
|
369
|
-
already_exists =
|
|
345
|
+
# Propagate structured error from PlatformHttpClient
|
|
346
|
+
body = result[:data] || {}
|
|
347
|
+
code = body["code"] || body["error"]
|
|
348
|
+
errors = body["errors"]&.join(", ")
|
|
349
|
+
msg = result[:error] || [code, errors].compact.join(": ")
|
|
350
|
+
msg = "Upload failed" if msg.to_s.strip.empty?
|
|
351
|
+
|
|
352
|
+
# Detect "already exists" conflicts so the caller can offer an overwrite option.
|
|
353
|
+
already_exists = body["code"].to_s.include?("name_taken") ||
|
|
354
|
+
body["code"].to_s.include?("already") ||
|
|
355
|
+
result[:error].to_s.include?("HTTP 409")
|
|
370
356
|
{ success: false, error: msg, already_exists: already_exists }
|
|
371
357
|
end
|
|
372
358
|
rescue StandardError => e
|
|
@@ -1158,49 +1144,17 @@ module Clacky
|
|
|
1158
1144
|
nil
|
|
1159
1145
|
end
|
|
1160
1146
|
|
|
1161
|
-
# POST JSON to the API
|
|
1147
|
+
# POST JSON to the platform API with automatic retry and domain failover.
|
|
1148
|
+
# Returns { success:, data:, error: }.
|
|
1162
1149
|
private def api_post(path, payload)
|
|
1163
|
-
|
|
1164
|
-
require "uri"
|
|
1165
|
-
|
|
1166
|
-
uri = URI.parse("#{API_BASE_URL}#{path}")
|
|
1167
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
|
1168
|
-
http.use_ssl = uri.scheme == "https"
|
|
1169
|
-
http.open_timeout = 10
|
|
1170
|
-
http.read_timeout = 15
|
|
1171
|
-
|
|
1172
|
-
request = Net::HTTP::Post.new(uri.path, "Content-Type" => "application/json")
|
|
1173
|
-
request.body = JSON.generate(payload)
|
|
1174
|
-
|
|
1175
|
-
response = http.request(request)
|
|
1176
|
-
body = JSON.parse(response.body) rescue {}
|
|
1177
|
-
|
|
1178
|
-
if response.code.to_i == 200
|
|
1179
|
-
{ success: true, data: body["data"] || body }
|
|
1180
|
-
else
|
|
1181
|
-
error_msg = map_api_error(body["code"])
|
|
1182
|
-
{ success: false, error: error_msg, data: body }
|
|
1183
|
-
end
|
|
1184
|
-
rescue StandardError => e
|
|
1185
|
-
{ success: false, error: "Network error: #{e.message}", data: {} }
|
|
1150
|
+
platform_client.post(path, payload)
|
|
1186
1151
|
end
|
|
1187
1152
|
|
|
1188
|
-
#
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
"timestamp_expired" => "System clock is out of sync. Please adjust your time settings.",
|
|
1194
|
-
"license_revoked" => "This license has been revoked. Please contact support.",
|
|
1195
|
-
"license_expired" => "This license has expired. Please renew to continue.",
|
|
1196
|
-
"device_limit_reached" => "Device limit reached for this license.",
|
|
1197
|
-
"device_revoked" => "This device has been revoked from the license.",
|
|
1198
|
-
"invalid_license" => "License key not found. Please verify the key.",
|
|
1199
|
-
"device_not_found" => "Device not registered. Please re-activate."
|
|
1200
|
-
}.freeze
|
|
1201
|
-
|
|
1202
|
-
private def map_api_error(code)
|
|
1203
|
-
API_ERROR_MESSAGES[code] || "Activation failed (#{code || 'unknown error'}). Please contact support."
|
|
1153
|
+
# Lazy-initialised PlatformHttpClient, respecting CLACKY_LICENSE_SERVER override.
|
|
1154
|
+
private def platform_client
|
|
1155
|
+
@platform_client ||= Clacky::PlatformHttpClient.new(
|
|
1156
|
+
base_url: ENV["CLACKY_LICENSE_SERVER"]
|
|
1157
|
+
)
|
|
1204
1158
|
end
|
|
1205
1159
|
end
|
|
1206
1160
|
end
|
data/lib/clacky/client.rb
CHANGED
|
@@ -291,9 +291,19 @@ module Clacky
|
|
|
291
291
|
|
|
292
292
|
case response.status
|
|
293
293
|
when 400
|
|
294
|
-
|
|
295
|
-
|
|
294
|
+
# Well-behaved APIs (Anthropic, OpenAI) never put quota/availability issues in 400.
|
|
295
|
+
# However, some proxy/relay providers do — so we inspect the message first.
|
|
296
|
+
# Also, Bedrock returns ThrottlingException as 400 instead of 429.
|
|
297
|
+
if error_message.match?(/ThrottlingException|unavailable|quota/i)
|
|
298
|
+
hint = error_message.match?(/quota/i) ? " (possibly out of credits)" : ""
|
|
299
|
+
raise RetryableError, "Rate limit or service issue (400): #{error_message}#{hint}"
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# True bad request — our message was malformed. Roll back history so the
|
|
303
|
+
# broken message is not replayed on the next user turn.
|
|
304
|
+
raise BadRequestError, "API request failed (400): #{error_message}"
|
|
296
305
|
when 401 then raise AgentError, "Invalid API key"
|
|
306
|
+
when 402 then raise AgentError, "Billing or payment issue (possibly out of credits): #{error_message}"
|
|
297
307
|
when 403 then raise AgentError, "Access denied: #{error_message}"
|
|
298
308
|
when 404 then raise AgentError, "API endpoint not found: #{error_message}"
|
|
299
309
|
when 429 then raise RetryableError, "Rate limit exceeded, please wait a moment"
|
|
@@ -3,6 +3,7 @@ name: code-explorer
|
|
|
3
3
|
description: Use this skill when exploring, analyzing, or understanding project/code structure. Required for tasks like "analyze project", "explore codebase", "understand how X works".
|
|
4
4
|
agent: coding
|
|
5
5
|
fork_agent: true
|
|
6
|
+
model: lite
|
|
6
7
|
forbidden_tools:
|
|
7
8
|
- write
|
|
8
9
|
- edit
|
|
@@ -16,6 +16,26 @@ When user wants to create a new Rails project:
|
|
|
16
16
|
|
|
17
17
|
## Process Steps
|
|
18
18
|
|
|
19
|
+
### 0. Ask Project Type and Requirement
|
|
20
|
+
Before doing anything, use `request_user_feedback` to ask the user two things:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
project_type: "demo" or "production"
|
|
24
|
+
requirement: one-sentence description of what they want to build
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Card content:
|
|
28
|
+
- Title: "🚀 New Project"
|
|
29
|
+
- Two options for project type:
|
|
30
|
+
- **⚡ Demo** — no database, AI builds freely, quick prototype
|
|
31
|
+
- **🏗️ Production** — real app, ready to deploy, full Rails setup
|
|
32
|
+
- One text input: "Describe your project in one sentence"
|
|
33
|
+
- Confirm button: "Let's go!"
|
|
34
|
+
|
|
35
|
+
**Based on user's choice:**
|
|
36
|
+
- If **Demo**: do NOT follow the Rails setup steps below. Instead, freely build a simple HTML/CSS/JS (or React) prototype directly in the working directory based on their requirement. Use your creativity.
|
|
37
|
+
- If **Production**: continue with steps 1–3 below (full Rails flow).
|
|
38
|
+
|
|
19
39
|
### 1. Check Directory Before Starting
|
|
20
40
|
Before running the setup script, check if current directory is empty:
|
|
21
41
|
- Use glob tool to check if directory has files: `glob("*", base_path: ".")`
|
|
@@ -38,7 +58,7 @@ The script will automatically:
|
|
|
38
58
|
|
|
39
59
|
**Step 2: Check Environment**
|
|
40
60
|
- Run rails_env_checker.sh to verify dependencies:
|
|
41
|
-
- Ruby >= 3.
|
|
61
|
+
- Ruby >= 3.3.0 (auto-installed via mise if missing or too old — supports CN mirrors)
|
|
42
62
|
- Node.js >= 22.0.0 (will install automatically if missing on macOS/Ubuntu)
|
|
43
63
|
- PostgreSQL (will install automatically if missing on macOS/Ubuntu)
|
|
44
64
|
- Script automatically installs missing dependencies without prompting
|
|
@@ -76,7 +96,7 @@ What would you like to develop next?
|
|
|
76
96
|
## Error Handling
|
|
77
97
|
- Directory not empty → Ask user confirmation, abort if declined
|
|
78
98
|
- Git clone fails → Check network connection, verify repository URL
|
|
79
|
-
- Ruby
|
|
99
|
+
- Ruby < 3.3 or missing → **Automatically installs Ruby 3.3 via mise** (with CN mirror support); exits with instructions if mise install fails
|
|
80
100
|
- Node.js < 22 → Script installs automatically (macOS/Ubuntu)
|
|
81
101
|
- PostgreSQL missing → Script installs automatically (macOS/Ubuntu)
|
|
82
102
|
- bin/setup fails → Show error, suggest running `./bin/setup` manually
|
|
@@ -33,16 +33,13 @@ print_step() {
|
|
|
33
33
|
echo -e "\n${BLUE}==>${NC} $1"
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
# Get script directory
|
|
37
|
-
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
38
|
-
|
|
39
36
|
# Check if current directory is empty
|
|
40
37
|
check_current_directory() {
|
|
41
38
|
print_step "Checking current directory..."
|
|
42
|
-
|
|
39
|
+
|
|
43
40
|
local current_dir=$(pwd)
|
|
44
41
|
print_info "Working in: $current_dir"
|
|
45
|
-
|
|
42
|
+
|
|
46
43
|
# Check if directory is empty (silently continue if not)
|
|
47
44
|
if [ "$(ls -A .)" ]; then
|
|
48
45
|
print_warning "Current directory is not empty - continuing anyway"
|
|
@@ -54,11 +51,11 @@ check_current_directory() {
|
|
|
54
51
|
# Clone template to current directory
|
|
55
52
|
clone_template() {
|
|
56
53
|
print_step "Cloning Rails template..."
|
|
57
|
-
|
|
54
|
+
|
|
58
55
|
# Create temporary directory
|
|
59
56
|
local temp_dir=$(mktemp -d)
|
|
60
57
|
print_info "Using temporary directory: $temp_dir"
|
|
61
|
-
|
|
58
|
+
|
|
62
59
|
# Clone template to temp directory
|
|
63
60
|
print_info "Downloading template from GitHub..."
|
|
64
61
|
if git clone https://github.com/clacky-ai/rails-template-7x-starter.git "$temp_dir" >/dev/null 2>&1; then
|
|
@@ -68,18 +65,18 @@ clone_template() {
|
|
|
68
65
|
rm -rf "$temp_dir"
|
|
69
66
|
exit 1
|
|
70
67
|
fi
|
|
71
|
-
|
|
68
|
+
|
|
72
69
|
# Move all files to current directory
|
|
73
70
|
print_info "Moving files to current directory..."
|
|
74
71
|
mv "$temp_dir"/* "$temp_dir"/.* . 2>/dev/null || true
|
|
75
|
-
|
|
72
|
+
|
|
76
73
|
# Delete .git directory
|
|
77
74
|
rm -rf .git
|
|
78
|
-
|
|
75
|
+
|
|
79
76
|
# Clean up temp directory
|
|
80
77
|
rm -rf "$temp_dir"
|
|
81
78
|
print_success "Template files copied to current directory"
|
|
82
|
-
|
|
79
|
+
|
|
83
80
|
# Initialize new git repository
|
|
84
81
|
print_info "Initializing git repository..."
|
|
85
82
|
git init > /dev/null 2>&1
|
|
@@ -88,37 +85,37 @@ clone_template() {
|
|
|
88
85
|
print_success "Git repository initialized"
|
|
89
86
|
}
|
|
90
87
|
|
|
91
|
-
# Check environment dependencies
|
|
88
|
+
# Check and install environment dependencies
|
|
92
89
|
check_environment() {
|
|
93
90
|
print_step "Checking environment dependencies..."
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if [ -f "$
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
else
|
|
105
|
-
print_warning "rails_env_checker.sh not found, skipping environment check"
|
|
106
|
-
print_info "Please ensure you have Ruby 3.x, Node.js 22+, and PostgreSQL installed"
|
|
91
|
+
|
|
92
|
+
local installer="$HOME/.clacky/scripts/install_rails_deps.sh"
|
|
93
|
+
if [ ! -f "$installer" ]; then
|
|
94
|
+
print_warning "install_rails_deps.sh not found at $installer"
|
|
95
|
+
print_info "Please ensure Ruby 3.3+, Node.js 22+, and PostgreSQL are installed"
|
|
96
|
+
return 1
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
if bash "$installer"; then
|
|
100
|
+
print_success "Environment ready"
|
|
107
101
|
return 0
|
|
102
|
+
else
|
|
103
|
+
print_error "Environment setup failed"
|
|
104
|
+
return 1
|
|
108
105
|
fi
|
|
109
106
|
}
|
|
110
107
|
|
|
111
108
|
# Run project setup
|
|
112
109
|
run_project_setup() {
|
|
113
110
|
print_step "Running project setup..."
|
|
114
|
-
|
|
111
|
+
|
|
115
112
|
if [ ! -f "./bin/setup" ]; then
|
|
116
113
|
print_error "bin/setup not found"
|
|
117
114
|
return 1
|
|
118
115
|
fi
|
|
119
|
-
|
|
116
|
+
|
|
120
117
|
chmod +x ./bin/setup
|
|
121
|
-
|
|
118
|
+
|
|
122
119
|
if ./bin/setup; then
|
|
123
120
|
print_success "Project setup completed"
|
|
124
121
|
return 0
|
|
@@ -139,27 +136,27 @@ main() {
|
|
|
139
136
|
echo "║ ║"
|
|
140
137
|
echo "╚═══════════════════════════════════════════════════════════╝"
|
|
141
138
|
echo ""
|
|
142
|
-
|
|
139
|
+
|
|
143
140
|
# Check current directory
|
|
144
141
|
check_current_directory
|
|
145
|
-
|
|
142
|
+
|
|
146
143
|
# Clone template
|
|
147
144
|
if ! clone_template; then
|
|
148
145
|
exit 1
|
|
149
146
|
fi
|
|
150
|
-
|
|
147
|
+
|
|
151
148
|
# Check environment
|
|
152
149
|
if ! check_environment; then
|
|
153
150
|
print_error "Please fix environment issues and run ./bin/setup manually"
|
|
154
151
|
exit 1
|
|
155
152
|
fi
|
|
156
|
-
|
|
153
|
+
|
|
157
154
|
# Run project setup
|
|
158
155
|
if ! run_project_setup; then
|
|
159
156
|
print_error "Setup failed. You can try running './bin/setup' manually"
|
|
160
157
|
exit 1
|
|
161
158
|
fi
|
|
162
|
-
|
|
159
|
+
|
|
163
160
|
# Project is ready
|
|
164
161
|
echo ""
|
|
165
162
|
echo "╔═══════════════════════════════════════════════════════════╗"
|