openclacky 1.0.0 → 1.0.2
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/CHANGELOG.md +39 -0
- data/README.md +87 -53
- data/lib/clacky/agent/cost_tracker.rb +19 -2
- data/lib/clacky/agent/llm_caller.rb +218 -0
- data/lib/clacky/agent/message_compressor_helper.rb +32 -2
- data/lib/clacky/agent.rb +54 -22
- data/lib/clacky/client.rb +44 -5
- data/lib/clacky/default_parsers/pdf_parser.rb +58 -17
- data/lib/clacky/default_parsers/pdf_parser_ocr.py +103 -0
- data/lib/clacky/default_parsers/pdf_parser_plumber.py +62 -0
- data/lib/clacky/default_skills/deploy/SKILL.md +201 -77
- data/lib/clacky/default_skills/new/SKILL.md +3 -114
- data/lib/clacky/default_skills/onboard/SKILL.md +349 -133
- data/lib/clacky/default_skills/onboard/scripts/import_external_skills.rb +371 -0
- data/lib/clacky/default_skills/onboard/scripts/install_builtin_skills.rb +175 -0
- data/lib/clacky/default_skills/skill-add/scripts/install_from_zip.rb +59 -26
- data/lib/clacky/message_format/anthropic.rb +72 -8
- data/lib/clacky/message_format/bedrock.rb +6 -3
- data/lib/clacky/providers.rb +146 -3
- data/lib/clacky/server/channel/adapters/feishu/adapter.rb +14 -0
- data/lib/clacky/server/channel/adapters/feishu/bot.rb +10 -0
- data/lib/clacky/server/channel/adapters/feishu/message_parser.rb +1 -0
- data/lib/clacky/server/channel/channel_manager.rb +12 -4
- data/lib/clacky/server/channel/channel_ui_controller.rb +8 -2
- data/lib/clacky/server/http_server.rb +746 -13
- data/lib/clacky/server/session_registry.rb +55 -24
- data/lib/clacky/skill.rb +10 -9
- data/lib/clacky/skill_loader.rb +23 -11
- data/lib/clacky/tools/file_reader.rb +232 -127
- data/lib/clacky/tools/security.rb +42 -64
- data/lib/clacky/tools/terminal/persistent_session.rb +15 -4
- data/lib/clacky/tools/terminal/safe_rm.sh +106 -0
- data/lib/clacky/tools/terminal/session_manager.rb +8 -3
- data/lib/clacky/tools/terminal.rb +263 -16
- data/lib/clacky/ui2/layout_manager.rb +8 -1
- data/lib/clacky/ui2/output_buffer.rb +83 -23
- data/lib/clacky/ui2/ui_controller.rb +74 -7
- data/lib/clacky/utils/file_processor.rb +14 -40
- data/lib/clacky/utils/model_pricing.rb +215 -0
- data/lib/clacky/utils/parser_manager.rb +70 -6
- data/lib/clacky/utils/string_matcher.rb +23 -1
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +673 -9
- data/lib/clacky/web/app.js +40 -1608
- data/lib/clacky/web/i18n.js +209 -0
- data/lib/clacky/web/index.html +166 -2
- data/lib/clacky/web/onboard.js +77 -1
- data/lib/clacky/web/profile.js +442 -0
- data/lib/clacky/web/sessions.js +1034 -2
- data/lib/clacky/web/settings.js +127 -6
- data/lib/clacky/web/sidebar.js +39 -0
- data/lib/clacky/web/skills.js +460 -0
- data/lib/clacky/web/trash.js +343 -0
- data/lib/clacky/web/ws-dispatcher.js +255 -0
- data/lib/clacky.rb +5 -3
- metadata +16 -17
- data/lib/clacky/clacky_auth_client.rb +0 -152
- data/lib/clacky/clacky_cloud_config.rb +0 -123
- data/lib/clacky/cloud_project_client.rb +0 -169
- data/lib/clacky/default_skills/deploy/scripts/rails_deploy.rb +0 -1377
- data/lib/clacky/default_skills/deploy/tools/check_health.rb +0 -116
- data/lib/clacky/default_skills/deploy/tools/create_database_service.rb +0 -341
- data/lib/clacky/default_skills/deploy/tools/execute_deployment.rb +0 -99
- data/lib/clacky/default_skills/deploy/tools/fetch_runtime_logs.rb +0 -77
- data/lib/clacky/default_skills/deploy/tools/list_services.rb +0 -67
- data/lib/clacky/default_skills/deploy/tools/report_deploy_status.rb +0 -67
- data/lib/clacky/default_skills/deploy/tools/set_deploy_variables.rb +0 -189
- data/lib/clacky/default_skills/new/scripts/cloud_project_init.sh +0 -74
- data/lib/clacky/deploy_api_client.rb +0 -484
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Clacky
|
|
4
|
-
module DeployTools
|
|
5
|
-
# Report deployment status to user with formatted output
|
|
6
|
-
class ReportDeployStatus
|
|
7
|
-
VALID_STATUSES = %w[analyzing deploying checking success failed].freeze
|
|
8
|
-
|
|
9
|
-
STATUS_ICONS = {
|
|
10
|
-
'analyzing' => '🔍',
|
|
11
|
-
'deploying' => '🚀',
|
|
12
|
-
'checking' => '✅',
|
|
13
|
-
'success' => '🎉',
|
|
14
|
-
'failed' => '❌'
|
|
15
|
-
}.freeze
|
|
16
|
-
|
|
17
|
-
STATUS_COLORS = {
|
|
18
|
-
'analyzing' => :cyan,
|
|
19
|
-
'deploying' => :yellow,
|
|
20
|
-
'checking' => :blue,
|
|
21
|
-
'success' => :green,
|
|
22
|
-
'failed' => :red
|
|
23
|
-
}.freeze
|
|
24
|
-
|
|
25
|
-
# Execute the report_deploy_status command
|
|
26
|
-
#
|
|
27
|
-
# @param status [String] Deployment status (analyzing, deploying, checking, success, failed)
|
|
28
|
-
# @param message [String] Status message to display
|
|
29
|
-
# @return [Hash] Result of the report operation
|
|
30
|
-
def self.execute(status:, message:)
|
|
31
|
-
unless VALID_STATUSES.include?(status)
|
|
32
|
-
return {
|
|
33
|
-
error: "Invalid status",
|
|
34
|
-
details: "Status must be one of: #{VALID_STATUSES.join(', ')}",
|
|
35
|
-
provided: status
|
|
36
|
-
}
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
icon = STATUS_ICONS[status]
|
|
40
|
-
formatted_message = format_message(status, message, icon)
|
|
41
|
-
|
|
42
|
-
# Output to stdout
|
|
43
|
-
puts formatted_message
|
|
44
|
-
|
|
45
|
-
{
|
|
46
|
-
success: true,
|
|
47
|
-
status: status,
|
|
48
|
-
message: message,
|
|
49
|
-
timestamp: Time.now.iso8601
|
|
50
|
-
}
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# Format the status message with icon and styling
|
|
54
|
-
#
|
|
55
|
-
# @param status [String] Deployment status
|
|
56
|
-
# @param message [String] Status message
|
|
57
|
-
# @param icon [String] Emoji icon for status
|
|
58
|
-
# @return [String] Formatted message
|
|
59
|
-
def self.format_message(status, message, icon)
|
|
60
|
-
timestamp = Time.now.strftime("%H:%M:%S")
|
|
61
|
-
status_label = status.upcase.ljust(10)
|
|
62
|
-
|
|
63
|
-
"#{icon} [#{timestamp}] #{status_label} #{message}"
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "open3"
|
|
4
|
-
|
|
5
|
-
module Clacky
|
|
6
|
-
module DeployTools
|
|
7
|
-
# Set environment variables on a Railway service via `railway variables --set`.
|
|
8
|
-
# Uses RAILWAY_TOKEN passed through environment — no clackycli wrapper needed.
|
|
9
|
-
#
|
|
10
|
-
# Supports both normal key=value pairs and Railway inter-service references
|
|
11
|
-
# like ${{postgres.DATABASE_PUBLIC_URL}} (pass raw_value: true to skip escaping).
|
|
12
|
-
class SetDeployVariables
|
|
13
|
-
|
|
14
|
-
SENSITIVE_PATTERNS = [
|
|
15
|
-
/password/i, /secret/i, /api_key/i,
|
|
16
|
-
/token/i, /credential/i, /private_key/i
|
|
17
|
-
].freeze
|
|
18
|
-
|
|
19
|
-
# Maximum number of variables to set in a single batch call
|
|
20
|
-
BATCH_SIZE = 20
|
|
21
|
-
|
|
22
|
-
# Retry config for transient failures
|
|
23
|
-
MAX_RETRIES = 3
|
|
24
|
-
RETRY_DELAY = 2 # seconds
|
|
25
|
-
|
|
26
|
-
# Set one or more environment variables on a Railway service.
|
|
27
|
-
# Batches all variables into a single `railway variables` call to minimize
|
|
28
|
-
# network connections and avoid SSL reset issues.
|
|
29
|
-
#
|
|
30
|
-
# @param service_name [String] Railway service name
|
|
31
|
-
# @param variables [Hash] KEY => VALUE pairs
|
|
32
|
-
# @param platform_token [String] RAILWAY_TOKEN for this deploy task
|
|
33
|
-
# @param raw_value [Boolean] when true, values are passed unquoted
|
|
34
|
-
# (for Railway ${{...}} references)
|
|
35
|
-
# @return [Hash] {
|
|
36
|
-
# success: Boolean,
|
|
37
|
-
# set_variables: Array<String>,
|
|
38
|
-
# errors: Array<Hash>
|
|
39
|
-
# }
|
|
40
|
-
def self.execute(service_name:, variables:, platform_token:, raw_value: false)
|
|
41
|
-
if service_name.nil? || service_name.strip.empty?
|
|
42
|
-
return { success: false, error: "service_name is required" }
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
env = ENV.to_h.merge("RAILWAY_TOKEN" => platform_token)
|
|
46
|
-
|
|
47
|
-
# Log all variables being set
|
|
48
|
-
variables.each do |key, value|
|
|
49
|
-
log_value = sensitive?(key) ? "******" : value
|
|
50
|
-
puts " Setting #{key}=#{log_value}"
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# Split into batches to avoid command line length limits
|
|
54
|
-
set_vars = []
|
|
55
|
-
error_list = []
|
|
56
|
-
var_pairs = variables.map { |k, v| [k.to_s, v.to_s] }
|
|
57
|
-
|
|
58
|
-
var_pairs.each_slice(BATCH_SIZE) do |batch|
|
|
59
|
-
result = set_batch(env, service_name, batch, raw_value: raw_value)
|
|
60
|
-
if result[:success]
|
|
61
|
-
set_vars.concat(batch.map(&:first))
|
|
62
|
-
else
|
|
63
|
-
# Retry logic: attempt individual vars if batch fails
|
|
64
|
-
batch.each do |key, value|
|
|
65
|
-
individual = set_one_with_retry(env, service_name, key, value, raw_value: raw_value)
|
|
66
|
-
if individual[:success]
|
|
67
|
-
set_vars << key
|
|
68
|
-
else
|
|
69
|
-
error_list << { key: key, error: individual[:error] }
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
{
|
|
76
|
-
success: error_list.empty?,
|
|
77
|
-
set_variables: set_vars,
|
|
78
|
-
errors: error_list
|
|
79
|
-
}
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
# Set a batch of variables in a single railway command call.
|
|
83
|
-
#
|
|
84
|
-
# @param env [Hash] environment variables
|
|
85
|
-
# @param service_name [String] Railway service name
|
|
86
|
-
# @param pairs [Array<Array>] [[key, value], ...]
|
|
87
|
-
# @return [Hash] { success: true } or { success: false, error: String }
|
|
88
|
-
def self.set_batch(env, service_name, pairs, raw_value: false)
|
|
89
|
-
# Each --set argument is passed as a separate array element
|
|
90
|
-
set_flags = pairs.flat_map { |key, value| ["--set", "#{key}=#{value}"] }
|
|
91
|
-
cmd = ["railway", "variables", "--service", service_name, "--skip-deploys"] + set_flags
|
|
92
|
-
|
|
93
|
-
# Debug: print the full command being executed
|
|
94
|
-
puts " [DEBUG] Executing Railway CLI command:"
|
|
95
|
-
puts " [DEBUG] Array form: #{cmd.inspect}"
|
|
96
|
-
puts " [DEBUG] Shell form: #{cmd.join(' ')}"
|
|
97
|
-
puts " [DEBUG] with RAILWAY_TOKEN=#{env['RAILWAY_TOKEN']}" if env['RAILWAY_TOKEN']
|
|
98
|
-
$stdout.flush
|
|
99
|
-
|
|
100
|
-
# Use system() instead of Open3.capture3 to avoid stdin/stdout blocking issues
|
|
101
|
-
# system() inherits the current process's stdin/stdout/stderr directly
|
|
102
|
-
require 'timeout'
|
|
103
|
-
|
|
104
|
-
begin
|
|
105
|
-
success = Timeout.timeout(30) do
|
|
106
|
-
# Close stdin, suppress stdout, but keep stderr visible
|
|
107
|
-
system(env, *cmd, in: :close, out: File::NULL)
|
|
108
|
-
end
|
|
109
|
-
rescue Timeout::Error
|
|
110
|
-
return { success: false, error: "Railway CLI command timed out after 30 seconds" }
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
if success
|
|
114
|
-
{ success: true }
|
|
115
|
-
else
|
|
116
|
-
{ success: false, error: "railway variables command failed (exit code: #{$?.exitstatus})" }
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
# Set a single variable with retry logic for transient network errors.
|
|
121
|
-
#
|
|
122
|
-
# @return [Hash] { success: true } or { success: false, error: String }
|
|
123
|
-
def self.set_one_with_retry(env, service_name, key, value, raw_value: false)
|
|
124
|
-
last_error = nil
|
|
125
|
-
|
|
126
|
-
MAX_RETRIES.times do |attempt|
|
|
127
|
-
result = set_one(env, service_name, key, value, raw_value: raw_value)
|
|
128
|
-
return result if result[:success]
|
|
129
|
-
|
|
130
|
-
last_error = result[:error]
|
|
131
|
-
# Only retry on connection/SSL errors
|
|
132
|
-
break unless last_error.to_s =~ /connection|ssl|reset|timeout|network/i
|
|
133
|
-
|
|
134
|
-
puts " ⚠️ Retrying #{key} (attempt #{attempt + 2}/#{MAX_RETRIES})..." if attempt < MAX_RETRIES - 1
|
|
135
|
-
sleep RETRY_DELAY
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
{ success: false, error: last_error }
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
# Set a single variable. Builds the `railway variables --set` command.
|
|
142
|
-
#
|
|
143
|
-
# @return [Hash] { success: true } or { success: false, error: String }
|
|
144
|
-
def self.set_one(env, service_name, key, value, raw_value: false)
|
|
145
|
-
assignment = "#{key}=#{value}"
|
|
146
|
-
|
|
147
|
-
cmd = [
|
|
148
|
-
"railway", "variables",
|
|
149
|
-
"--service", service_name,
|
|
150
|
-
"--skip-deploys",
|
|
151
|
-
"--set", assignment
|
|
152
|
-
]
|
|
153
|
-
|
|
154
|
-
# Debug: print the full command being executed
|
|
155
|
-
puts " [DEBUG] Executing single var Railway CLI command:"
|
|
156
|
-
puts " [DEBUG] Array form: #{cmd.inspect}"
|
|
157
|
-
puts " [DEBUG] Shell form: #{cmd.join(' ')}"
|
|
158
|
-
puts " [DEBUG] with RAILWAY_TOKEN=#{env['RAILWAY_TOKEN']}" if env['RAILWAY_TOKEN']
|
|
159
|
-
$stdout.flush
|
|
160
|
-
|
|
161
|
-
# Use system() instead of Open3.capture3 to avoid stdin/stdout blocking issues
|
|
162
|
-
require 'timeout'
|
|
163
|
-
|
|
164
|
-
begin
|
|
165
|
-
success = Timeout.timeout(30) do
|
|
166
|
-
# Close stdin, suppress stdout, but keep stderr visible
|
|
167
|
-
system(env, *cmd, in: :close, out: File::NULL)
|
|
168
|
-
end
|
|
169
|
-
rescue Timeout::Error
|
|
170
|
-
return { success: false, error: "Railway CLI command timed out after 30 seconds" }
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
if success
|
|
174
|
-
{ success: true }
|
|
175
|
-
else
|
|
176
|
-
{ success: false, error: "railway variables command failed (exit code: #{$?.exitstatus})" }
|
|
177
|
-
end
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
private_class_method def self.sensitive?(key)
|
|
181
|
-
SENSITIVE_PATTERNS.any? { |pat| key.match?(pat) }
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
private_class_method def self.shell_escape(str)
|
|
185
|
-
"'#{str.to_s.gsub("'", "'\\\\''")}'"
|
|
186
|
-
end
|
|
187
|
-
end
|
|
188
|
-
end
|
|
189
|
-
end
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Cloud Project Init Script
|
|
3
|
-
# Connects the local Rails project to the Clacky cloud platform.
|
|
4
|
-
#
|
|
5
|
-
# Usage: cloud_project_init.sh [project_name] [workspace_key] [base_url]
|
|
6
|
-
# - project_name: defaults to current directory name
|
|
7
|
-
# - workspace_key: defaults to value in ~/.clacky/clacky_cloud.yml
|
|
8
|
-
# - base_url: defaults to https://api.clacky.ai
|
|
9
|
-
#
|
|
10
|
-
# Outputs a JSON result on stdout:
|
|
11
|
-
# { "success": true, "project_id": "...", "project_name": "..." }
|
|
12
|
-
# { "success": false, "error": "..." }
|
|
13
|
-
|
|
14
|
-
set -e
|
|
15
|
-
|
|
16
|
-
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
17
|
-
GEM_LIB_DIR="$( cd "$SCRIPT_DIR/../../../.." && pwd )"
|
|
18
|
-
|
|
19
|
-
PROJECT_NAME="${1:-$(basename "$PWD")}"
|
|
20
|
-
WORKSPACE_KEY="${2:-}"
|
|
21
|
-
BASE_URL="${3:-}"
|
|
22
|
-
|
|
23
|
-
# --- Load workspace_key from clacky_cloud.yml if not provided ---
|
|
24
|
-
if [ -z "$WORKSPACE_KEY" ]; then
|
|
25
|
-
PLATFORM_YML="$HOME/.clacky/clacky_cloud.yml"
|
|
26
|
-
if [ -f "$PLATFORM_YML" ]; then
|
|
27
|
-
WORKSPACE_KEY=$(ruby -e "require 'yaml'; y = YAML.safe_load(File.read('$PLATFORM_YML')); print y['workspace_key'].to_s.strip" 2>/dev/null || true)
|
|
28
|
-
fi
|
|
29
|
-
fi
|
|
30
|
-
|
|
31
|
-
if [ -z "$BASE_URL" ]; then
|
|
32
|
-
PLATFORM_YML="$HOME/.clacky/clacky_cloud.yml"
|
|
33
|
-
if [ -f "$PLATFORM_YML" ]; then
|
|
34
|
-
BASE_URL=$(ruby -e "require 'yaml'; y = YAML.safe_load(File.read('$PLATFORM_YML')); print y['base_url'].to_s.strip" 2>/dev/null || true)
|
|
35
|
-
fi
|
|
36
|
-
BASE_URL="${BASE_URL:-https://api.clacky.ai}"
|
|
37
|
-
fi
|
|
38
|
-
|
|
39
|
-
if [ -z "$WORKSPACE_KEY" ]; then
|
|
40
|
-
echo '{"success":false,"error":"No workspace_key found. Please set it in ~/.clacky/clacky_cloud.yml or pass as argument."}'
|
|
41
|
-
exit 0
|
|
42
|
-
fi
|
|
43
|
-
|
|
44
|
-
# --- Call the API via Ruby one-liner using the gem's CloudProjectClient ---
|
|
45
|
-
RUBY_SCRIPT=$(cat <<'RUBY'
|
|
46
|
-
require_relative ENV['GEM_LIB_DIR'] + '/clacky/cloud_project_client'
|
|
47
|
-
require 'json'
|
|
48
|
-
|
|
49
|
-
workspace_key = ENV['WORKSPACE_KEY']
|
|
50
|
-
base_url = ENV['BASE_URL']
|
|
51
|
-
project_name = ENV['PROJECT_NAME']
|
|
52
|
-
|
|
53
|
-
client = Clacky::CloudProjectClient.new(workspace_key, base_url: base_url)
|
|
54
|
-
result = client.create_project(name: project_name)
|
|
55
|
-
|
|
56
|
-
if result[:success]
|
|
57
|
-
project = result[:project]
|
|
58
|
-
puts JSON.generate({
|
|
59
|
-
success: true,
|
|
60
|
-
project_id: project['id'],
|
|
61
|
-
project_name: project['name'],
|
|
62
|
-
categorized_config: project['categorized_config'] || {}
|
|
63
|
-
})
|
|
64
|
-
else
|
|
65
|
-
puts JSON.generate({ success: false, error: result[:error] })
|
|
66
|
-
end
|
|
67
|
-
RUBY
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
GEM_LIB_DIR="$GEM_LIB_DIR" \
|
|
71
|
-
WORKSPACE_KEY="$WORKSPACE_KEY" \
|
|
72
|
-
BASE_URL="$BASE_URL" \
|
|
73
|
-
PROJECT_NAME="$PROJECT_NAME" \
|
|
74
|
-
ruby -e "$RUBY_SCRIPT"
|