openclacky 1.3.6 → 1.3.8
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 +54 -0
- data/lib/clacky/agent/hook_manager.rb +10 -1
- data/lib/clacky/agent/system_prompt_builder.rb +1 -1
- data/lib/clacky/agent.rb +17 -3
- data/lib/clacky/agent_profile.rb +124 -53
- data/lib/clacky/billing/billing_store.rb +3 -2
- data/lib/clacky/brand_config.rb +309 -6
- data/lib/clacky/cli.rb +4 -57
- data/lib/clacky/default_extensions/coding/agents/coding/avatar.png +0 -0
- data/lib/clacky/default_extensions/coding/ext.yml +19 -0
- data/lib/clacky/default_extensions/ext-studio/agents/ext-developer/avatar.png +0 -0
- data/lib/clacky/default_extensions/ext-studio/agents/ext-developer/system_prompt.md +95 -0
- data/lib/clacky/default_extensions/ext-studio/api/handler.rb +254 -0
- data/lib/clacky/default_extensions/ext-studio/ext.yml +33 -0
- data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +1288 -0
- data/lib/clacky/default_extensions/ext-studio/skills/ext-debug/SKILL.md +71 -0
- data/lib/clacky/default_extensions/ext-studio/skills/ext-publish/SKILL.md +74 -0
- data/lib/clacky/default_extensions/ext-studio/skills/ext-scaffold/SKILL.md +65 -0
- data/lib/clacky/default_extensions/general/agents/general/avatar.png +0 -0
- data/lib/clacky/default_extensions/general/ext.yml +19 -0
- data/lib/clacky/default_extensions/git/ext.yml +12 -0
- data/lib/clacky/{default_agents/_panels/git/panel.js → default_extensions/git/panels/git/view.js} +4 -5
- data/lib/clacky/default_extensions/meeting/{handler.rb → api/handler.rb} +78 -26
- data/lib/clacky/default_extensions/meeting/ext.yml +15 -0
- data/lib/clacky/default_extensions/meeting/{meeting.js → panels/meeting/view.js} +266 -90
- data/lib/clacky/default_extensions/meeting/skills/meeting-summarizer/SKILL.md +2 -1
- data/lib/clacky/default_extensions/time_machine/ext.yml +12 -0
- data/lib/clacky/{default_agents/_panels/time_machine/panel.js → default_extensions/time_machine/panels/time_machine/view.js} +58 -47
- data/lib/clacky/default_skills/channel-manager/discord_setup.rb +1 -1
- data/lib/clacky/default_skills/extend-openclacky/SKILL.md +83 -18
- data/lib/clacky/{api_extension.rb → extension/api_extension.rb} +27 -11
- data/lib/clacky/extension/api_loader.rb +136 -0
- data/lib/clacky/extension/cli_commands.rb +226 -0
- data/lib/clacky/{server/api_extension_dispatcher.rb → extension/dispatcher.rb} +22 -9
- data/lib/clacky/extension/hook_loader.rb +77 -0
- data/lib/clacky/extension/loader.rb +489 -0
- data/lib/clacky/extension/packager.rb +226 -0
- data/lib/clacky/{patch_loader.rb → extension/patch_loader.rb} +45 -0
- data/lib/clacky/extension/scaffold/template_renderer.rb +64 -0
- data/lib/clacky/extension/scaffold/templates/full/README.md.erb +48 -0
- data/lib/clacky/extension/scaffold/templates/full/agents/designer.md +5 -0
- data/lib/clacky/extension/scaffold/templates/full/api/handler.rb.erb +16 -0
- data/lib/clacky/extension/scaffold/templates/full/channels/noop.rb.erb +40 -0
- data/lib/clacky/extension/scaffold/templates/full/ext.yml.erb +41 -0
- data/lib/clacky/extension/scaffold/templates/full/hooks/audit.rb +9 -0
- data/lib/clacky/extension/scaffold/templates/full/panels/dashboard/view.js.erb +194 -0
- data/lib/clacky/extension/scaffold/templates/full/patches/audit.rb +15 -0
- data/lib/clacky/extension/scaffold/templates/full/skills/__slug__-skill/SKILL.md.erb +8 -0
- data/lib/clacky/extension/scaffold/templates/hello/api/handler.rb.erb +9 -0
- data/lib/clacky/extension/scaffold/templates/hello/ext.yml.erb +12 -0
- data/lib/clacky/extension/scaffold/templates/hello/panels/hello/view.js.erb +31 -0
- data/lib/clacky/extension/scaffold.rb +55 -0
- data/lib/clacky/extension/verifier.rb +196 -0
- data/lib/clacky/identity.rb +70 -0
- data/lib/clacky/locales/en.rb +19 -1
- data/lib/clacky/locales/zh.rb +19 -1
- data/lib/clacky/platform_http_client.rb +18 -15
- data/lib/clacky/server/channel/extension_adapter_loader.rb +32 -0
- data/lib/clacky/server/channel.rb +6 -0
- data/lib/clacky/server/http_server.rb +439 -146
- data/lib/clacky/server/session_registry.rb +9 -2
- data/lib/clacky/shell_hook_loader.rb +266 -22
- data/lib/clacky/skill_loader.rb +28 -1
- data/lib/clacky/ui2/components/welcome_banner.rb +1 -1
- data/lib/clacky/ui2/ui_controller.rb +9 -6
- data/lib/clacky/utils/workspace_rules.rb +2 -2
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +1034 -675
- data/lib/clacky/web/app.js +63 -15
- data/lib/clacky/web/auth.js +2 -0
- data/lib/clacky/web/components/notify.js +2 -0
- data/lib/clacky/web/components/sidebar.js +1 -5
- data/lib/clacky/web/core/ext.js +432 -89
- data/lib/clacky/web/features/backup/store.js +1 -0
- data/lib/clacky/web/features/backup/view.js +0 -1
- data/lib/clacky/web/features/brand/view.js +13 -5
- data/lib/clacky/web/features/extensions/store.js +184 -0
- data/lib/clacky/web/features/extensions/view.js +390 -0
- data/lib/clacky/web/features/new-session/store.js +199 -0
- data/lib/clacky/web/features/new-session/view.js +619 -0
- data/lib/clacky/web/features/share/view.js +63 -14
- data/lib/clacky/web/features/skills/store.js +3 -1
- data/lib/clacky/web/features/skills/view.js +1 -1
- data/lib/clacky/web/features/tasks/view.js +12 -0
- data/lib/clacky/web/features/trash/store.js +24 -6
- data/lib/clacky/web/features/trash/view.js +48 -12
- data/lib/clacky/web/features/workspace/store.js +2 -0
- data/lib/clacky/web/features/workspace/view.js +0 -1
- data/lib/clacky/web/i18n.js +152 -106
- data/lib/clacky/web/index.html +172 -190
- data/lib/clacky/web/sessions.js +67 -272
- data/lib/clacky/web/settings.js +1 -1
- data/lib/clacky/web/skills.js +88 -48
- data/lib/clacky/web/theme.js +5 -0
- data/lib/clacky/web/ws-dispatcher.js +12 -2
- data/lib/clacky/web/ws.js +2 -0
- data/lib/clacky.rb +21 -10
- metadata +52 -21
- data/benchmark/runner.rb +0 -441
- data/lib/clacky/api_extension_loader.rb +0 -168
- data/lib/clacky/default_agents/SOUL.md +0 -3
- data/lib/clacky/default_agents/USER.md +0 -1
- data/lib/clacky/default_agents/coding/profile.yml +0 -5
- data/lib/clacky/default_agents/coding/webui/.gitkeep +0 -0
- data/lib/clacky/default_agents/general/profile.yml +0 -2
- data/lib/clacky/default_extensions/meeting/meta.yml +0 -3
- data/lib/clacky/web/features/creator/store.js +0 -81
- data/lib/clacky/web/features/creator/view.js +0 -380
- /data/lib/clacky/{default_agents → default_extensions/coding/agents}/coding/system_prompt.md +0 -0
- /data/lib/clacky/{default_agents → default_extensions/general/agents}/general/system_prompt.md +0 -0
- /data/lib/clacky/{default_agents/base_prompt.md → prompts/base.md} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 589d52edb2af8ec7ebea8d596a4b342791230009666a8757f4391b95df22d667
|
|
4
|
+
data.tar.gz: 07c80f79e8a9df57b4232a1317b1a6f8b4557cf136289f7ea1b7a9e93caba5aa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e474dff0d682c03bbd9756a37e9c64a29d13f51c35b73a76440e62eeac4b92d5ce6251e7357493531ce0669a770607a37a17ceb8715eadf5f849a7b04a6f373a
|
|
7
|
+
data.tar.gz: 8863d9a310224162b3fc0dad4513c933ebafcc74be5d2157d890d7a4f2dd667f66c9f658e61d289c078fdf39c5961bb5c0895317553c0f25b5054142f2c127b9
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,60 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.3.8] - 2026-07-07
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Shell hook `type:rewrite` protocol with `updatedInput` support — hooks can now rewrite the command before execution
|
|
12
|
+
- Support for `AGENTS.md` workspace rules — agent reads project-level rules from AGENTS.md automatically
|
|
13
|
+
- Avatar support in Web UI
|
|
14
|
+
- Open first tab automatically on session start
|
|
15
|
+
|
|
16
|
+
### Improved
|
|
17
|
+
- New-session page UI humanized with improved layout and discoverability
|
|
18
|
+
- Session bar slot support for extension customization
|
|
19
|
+
- Extension description metadata in ext-studio
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- Mobile new-session page layout uses fixed positioning correctly
|
|
23
|
+
- Weekly stats period now starts from Monday instead of a rolling 7-day window
|
|
24
|
+
- i18n translations updated and UI refreshes immediately on language change
|
|
25
|
+
- Extension market toolbar layout and back button styling
|
|
26
|
+
- Skeleton screen rendering in ext-studio and extension market
|
|
27
|
+
- Trash skeleton screen restored on loading state
|
|
28
|
+
- Media usage logging now passes session_id correctly for image/video generation
|
|
29
|
+
- Billing writes in tests isolated via `CLACKY_BILLING_DIR` env var
|
|
30
|
+
- Meeting panel no longer overrides aside-panel container class
|
|
31
|
+
|
|
32
|
+
## [1.3.7] - 2026-07-06
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
- Extension ecosystem: install, manage, and publish extensions with Creator Studio
|
|
36
|
+
- Brand extension and brand badge support for extension agents
|
|
37
|
+
- Hook/profile extension type for intercepting and customizing agent behavior
|
|
38
|
+
- Extension upload CLI for publishing extensions from the command line
|
|
39
|
+
- New session page with improved layout and discoverability
|
|
40
|
+
- Session auto-cleanup hint, skeleton loading, and type filter in trash panel
|
|
41
|
+
- Renew button on brand warning bar for expiring subscriptions
|
|
42
|
+
- i18n tooltips for header theme toggle and trash toolbar buttons
|
|
43
|
+
- Refresh button in header to reload sessions
|
|
44
|
+
- xhigh reasoning effort level for supported models
|
|
45
|
+
- i18n file upload error messages
|
|
46
|
+
|
|
47
|
+
### Improved
|
|
48
|
+
- Merge Creator Center into ext-studio as a unified extension hub
|
|
49
|
+
- Extension developer experience: scaffold template improvements, badge and author metadata support
|
|
50
|
+
- Share modal UI polish — theme dots on hover, close button hover effect, layout refinement
|
|
51
|
+
- Unify Clacky.* JS namespace and single-segment extension architecture
|
|
52
|
+
- Migrate default agents to default extensions with unified panel visibility
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
- Ctrl+C now works properly in TUI auto-answer mode
|
|
56
|
+
- Show-system-skills toggle persists correctly and uses theme accent color
|
|
57
|
+
- Cron display handles every-N-hours with weekdays pattern correctly
|
|
58
|
+
- New-session dropdown arrow button responds to both click and keyboard
|
|
59
|
+
- New cron virtual entry inserts at the correct position relative to pinned sessions
|
|
60
|
+
- File upload error messages use i18n consistently
|
|
61
|
+
|
|
8
62
|
## [1.3.6] - 2026-06-30
|
|
9
63
|
|
|
10
64
|
### Added
|
|
@@ -28,7 +28,16 @@ module Clacky
|
|
|
28
28
|
@hooks[event].each do |hook|
|
|
29
29
|
begin
|
|
30
30
|
hook_result = hook.call(*args)
|
|
31
|
-
|
|
31
|
+
next unless hook_result.is_a?(Hash)
|
|
32
|
+
# First deny wins and stops the chain: a weaker later verdict must
|
|
33
|
+
# never clobber a stronger earlier one, and the first deny's reason
|
|
34
|
+
# is the one that reaches the agent. Rewrite hooks mutate `call` in
|
|
35
|
+
# place (chained rewrite), so for non-deny results there's nothing to
|
|
36
|
+
# merge — we just keep going.
|
|
37
|
+
if hook_result[:action] == :deny
|
|
38
|
+
result = hook_result
|
|
39
|
+
break
|
|
40
|
+
end
|
|
32
41
|
rescue StandardError => e
|
|
33
42
|
# Log error but don't fail
|
|
34
43
|
Clacky::Logger.error("Hook error", event: event, error: e)
|
|
@@ -8,7 +8,7 @@ module Clacky
|
|
|
8
8
|
# Builds system prompt by composing layers:
|
|
9
9
|
# 1. Agent-specific system_prompt.md (role & responsibilities)
|
|
10
10
|
# 2. base_prompt.md (universal rules: todo manager, tool usage, etc.)
|
|
11
|
-
# 3. Project rules (.clackyrules / .cursorrules / CLAUDE.md)
|
|
11
|
+
# 3. Project rules (.clackyrules / AGENTS.md / .cursorrules / CLAUDE.md)
|
|
12
12
|
# 4. SOUL.md (agent personality — user override or built-in default)
|
|
13
13
|
# 5. USER.md (user profile — user override or built-in default)
|
|
14
14
|
# 6. Skills context (available skills list)
|
data/lib/clacky/agent.rb
CHANGED
|
@@ -51,7 +51,7 @@ module Clacky
|
|
|
51
51
|
attr_accessor :pinned
|
|
52
52
|
attr_accessor :channel_info
|
|
53
53
|
|
|
54
|
-
REASONING_EFFORTS = %w[low medium high].freeze
|
|
54
|
+
REASONING_EFFORTS = %w[low medium high xhigh].freeze
|
|
55
55
|
|
|
56
56
|
def permission_mode
|
|
57
57
|
@config&.permission_mode&.to_s || ""
|
|
@@ -136,6 +136,8 @@ module Clacky
|
|
|
136
136
|
# Free-mode counterpart: branded but not activated → fetch unencrypted skills
|
|
137
137
|
# via the public endpoint so users get a working install with no serial number.
|
|
138
138
|
@brand_config.sync_free_skills_async!
|
|
139
|
+
# Brand extensions bundled into the activated license's distribution.
|
|
140
|
+
@brand_config.sync_brand_extensions_async!
|
|
139
141
|
|
|
140
142
|
# Initialize Time Machine
|
|
141
143
|
init_time_machine
|
|
@@ -143,8 +145,20 @@ module Clacky
|
|
|
143
145
|
# Register built-in tools
|
|
144
146
|
register_builtin_tools
|
|
145
147
|
|
|
146
|
-
# Load declarative shell hooks from ~/.clacky/hooks.yml
|
|
147
|
-
|
|
148
|
+
# Load declarative shell hooks from ~/.clacky/hooks.yml. Entries with
|
|
149
|
+
# `type: rewrite` use the rich JSON protocol (updatedInput rewrite);
|
|
150
|
+
# entries without `type` use the simple exit-code protocol.
|
|
151
|
+
ShellHookLoader.load_into(
|
|
152
|
+
@hooks,
|
|
153
|
+
session_id_fn: -> { @session_id },
|
|
154
|
+
cwd_fn: -> { @working_dir },
|
|
155
|
+
permission_mode_fn: -> { @config.permission_mode.to_s }
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Copy ext.yml-contributed hook callbacks (contributes.hooks) onto this
|
|
159
|
+
# agent's hook manager. The callbacks were registered process-wide at
|
|
160
|
+
# boot via ExtensionHookLoader.
|
|
161
|
+
ExtensionHookRegistry.apply_to(@hooks)
|
|
148
162
|
|
|
149
163
|
# Ensure user-space parsers are in place (~/.clacky/parsers/)
|
|
150
164
|
Utils::ParserManager.setup!
|
data/lib/clacky/agent_profile.rb
CHANGED
|
@@ -6,107 +6,178 @@ module Clacky
|
|
|
6
6
|
# Loads and represents an agent profile (system prompt + skill whitelist).
|
|
7
7
|
#
|
|
8
8
|
# Lookup order for a profile named "coding":
|
|
9
|
-
# 1. ~/.clacky/agents/coding/
|
|
10
|
-
# 2.
|
|
9
|
+
# 1. ~/.clacky/agents/coding/ (user override, physical dir)
|
|
10
|
+
# 2. extension agent unit with id == "coding" (ext.yml contributes.agents)
|
|
11
11
|
#
|
|
12
|
-
# Each profile directory
|
|
12
|
+
# Each user profile directory (opt-in override) contains:
|
|
13
13
|
# - profile.yml — name, description, skills whitelist
|
|
14
14
|
# - system_prompt.md — agent-specific system prompt content
|
|
15
15
|
#
|
|
16
|
-
# Global files (shared across all agents)
|
|
17
|
-
# - SOUL.md — agent personality/values
|
|
18
|
-
# - USER.md — user profile
|
|
19
|
-
#
|
|
16
|
+
# Global files (shared across all agents) are user-only overrides:
|
|
17
|
+
# - ~/.clacky/agents/SOUL.md — agent personality/values (else DEFAULT_SOUL)
|
|
18
|
+
# - ~/.clacky/agents/USER.md — user profile info (else DEFAULT_USER)
|
|
19
|
+
# The universal behavioural rules (todo manager, tool usage, response style,
|
|
20
|
+
# etc.) live in a bundled resource file at lib/clacky/prompts/base.md.
|
|
20
21
|
class AgentProfile
|
|
21
|
-
DEFAULT_AGENTS_DIR = File.expand_path("../default_agents", __FILE__).freeze
|
|
22
22
|
USER_AGENTS_DIR = File.expand_path("~/.clacky/agents").freeze
|
|
23
|
+
BASE_PROMPT_PATH = File.expand_path("../prompts/base.md", __FILE__).freeze
|
|
24
|
+
|
|
25
|
+
DEFAULT_SOUL = <<~MD.freeze
|
|
26
|
+
You are calm, precise, and helpful. You communicate clearly and concisely.
|
|
27
|
+
You are honest about uncertainty and ask for clarification when needed.
|
|
28
|
+
You take initiative but respect the user's preferences and decisions.
|
|
29
|
+
MD
|
|
30
|
+
|
|
31
|
+
DEFAULT_USER = "(No user profile configured yet. To personalize, create ~/.clacky/agents/USER.md)"
|
|
23
32
|
|
|
24
33
|
attr_reader :name, :description
|
|
25
34
|
|
|
26
35
|
def initialize(name)
|
|
27
36
|
@name = name.to_s
|
|
37
|
+
result = ExtensionLoader.last_result
|
|
38
|
+
@ext_unit = result&.agents&.find { |u| u.id == @name }
|
|
39
|
+
if @ext_unit.nil?
|
|
40
|
+
result = ExtensionLoader.load_all(force: true)
|
|
41
|
+
@ext_unit = result&.agents&.find { |u| u.id == @name }
|
|
42
|
+
end
|
|
28
43
|
profile_data = load_profile_yml
|
|
29
44
|
@description = profile_data["description"] || ""
|
|
30
45
|
@system_prompt_content = load_agent_file("system_prompt.md")
|
|
31
46
|
end
|
|
32
47
|
|
|
33
|
-
# Load a named profile. Raises ArgumentError if profile directory not found.
|
|
34
48
|
# @param name [String, Symbol] profile name (e.g. "coding", "general")
|
|
35
49
|
# @return [AgentProfile]
|
|
36
50
|
def self.load(name)
|
|
37
51
|
new(name)
|
|
38
52
|
end
|
|
39
53
|
|
|
54
|
+
# List all available agent profiles across user + extension layers.
|
|
55
|
+
# Precedence on id collision: user override → extension unit.
|
|
56
|
+
# @return [Array<Hash>] each: { id:, title:, title_zh:, description:, description_zh:, source:, order:, layer:, author:, avatar: }
|
|
57
|
+
def self.all
|
|
58
|
+
out = {}
|
|
59
|
+
|
|
60
|
+
add = lambda do |id, title, title_zh, description, description_zh, source, order, layer, author, avatar|
|
|
61
|
+
next if id.nil? || id.empty?
|
|
62
|
+
out[id] = {
|
|
63
|
+
id: id,
|
|
64
|
+
title: title,
|
|
65
|
+
title_zh: title_zh,
|
|
66
|
+
description: description,
|
|
67
|
+
description_zh: description_zh,
|
|
68
|
+
source: source,
|
|
69
|
+
order: order,
|
|
70
|
+
layer: layer,
|
|
71
|
+
author: author,
|
|
72
|
+
avatar: avatar,
|
|
73
|
+
}
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
ext_result = ExtensionLoader.last_result || ExtensionLoader.load_all
|
|
77
|
+
ext_result&.agents&.each do |unit|
|
|
78
|
+
spec = unit.spec || {}
|
|
79
|
+
title = spec["title"].to_s
|
|
80
|
+
title = unit.id if title.empty?
|
|
81
|
+
avatar = spec["avatar_abs"].to_s.empty? ? nil : "/agent_avatar/#{unit.id}"
|
|
82
|
+
add.call(
|
|
83
|
+
unit.id, title, spec["title_zh"].to_s,
|
|
84
|
+
spec["description"].to_s, spec["description_zh"].to_s,
|
|
85
|
+
"extension", spec["order"], unit.layer.to_s,
|
|
86
|
+
spec["author"].to_s, avatar
|
|
87
|
+
)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
Dir.glob(File.join(USER_AGENTS_DIR, "*")).sort.each do |path|
|
|
91
|
+
next unless File.directory?(path)
|
|
92
|
+
id = File.basename(path)
|
|
93
|
+
next if id.start_with?("_")
|
|
94
|
+
next unless File.file?(File.join(path, "profile.yml"))
|
|
95
|
+
meta = read_profile_yml(File.join(path, "profile.yml"))
|
|
96
|
+
user_avatar = File.file?(File.join(path, "avatar.png")) ? "/agent_avatar/#{id}" : nil
|
|
97
|
+
add.call(
|
|
98
|
+
id, meta["title"] || meta["name"] || id, meta["title_zh"].to_s,
|
|
99
|
+
meta["description"].to_s, meta["description_zh"].to_s,
|
|
100
|
+
"user", meta["order"], "user",
|
|
101
|
+
meta["author"].to_s.empty? ? "You" : meta["author"].to_s, user_avatar
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
source_rank = { "user" => 0, "extension" => 1 }
|
|
106
|
+
out.values.sort_by { |a| [source_rank[a[:source]] || 9, a[:order] || 999, a[:id]] }
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private_class_method def self.read_profile_yml(path)
|
|
110
|
+
return {} unless File.file?(path)
|
|
111
|
+
YAML.safe_load(File.read(path)) || {}
|
|
112
|
+
rescue StandardError
|
|
113
|
+
{}
|
|
114
|
+
end
|
|
115
|
+
|
|
40
116
|
# @return [String] agent-specific system prompt content
|
|
41
117
|
def system_prompt
|
|
42
118
|
@system_prompt_content
|
|
43
119
|
end
|
|
44
120
|
|
|
45
|
-
# @return [String] base prompt shared by all agents
|
|
121
|
+
# @return [String] base prompt shared by all agents (bundled resource)
|
|
46
122
|
def base_prompt
|
|
47
|
-
|
|
123
|
+
return "" unless File.file?(BASE_PROMPT_PATH)
|
|
124
|
+
File.read(BASE_PROMPT_PATH).strip
|
|
48
125
|
end
|
|
49
126
|
|
|
50
|
-
# @return [String] soul content (user override
|
|
127
|
+
# @return [String] soul content (user override, else default)
|
|
51
128
|
def soul
|
|
52
|
-
|
|
129
|
+
user_path = File.join(USER_AGENTS_DIR, "SOUL.md")
|
|
130
|
+
if File.exist?(user_path) && !File.zero?(user_path)
|
|
131
|
+
File.read(user_path).strip
|
|
132
|
+
else
|
|
133
|
+
DEFAULT_SOUL.strip
|
|
134
|
+
end
|
|
53
135
|
end
|
|
54
136
|
|
|
55
|
-
# @return [String] user profile content (user override
|
|
137
|
+
# @return [String] user profile content (user override, else default)
|
|
56
138
|
def user_profile
|
|
57
|
-
|
|
139
|
+
user_path = File.join(USER_AGENTS_DIR, "USER.md")
|
|
140
|
+
if File.exist?(user_path) && !File.zero?(user_path)
|
|
141
|
+
File.read(user_path).strip
|
|
142
|
+
else
|
|
143
|
+
DEFAULT_USER
|
|
144
|
+
end
|
|
58
145
|
end
|
|
59
146
|
|
|
60
147
|
private def load_profile_yml
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
YAML.safe_load(File.read(path)) || {}
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# Load a file from the agent-specific directory (user override → built-in)
|
|
69
|
-
private def load_agent_file(filename)
|
|
70
|
-
path = find_agent_file(filename)
|
|
71
|
-
return "" unless path
|
|
72
|
-
|
|
73
|
-
File.read(path).strip
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
# Load a global file shared across all agents (user override → built-in)
|
|
77
|
-
private def load_global_file(filename)
|
|
78
|
-
user_path = File.join(USER_AGENTS_DIR, filename)
|
|
79
|
-
default_path = File.join(DEFAULT_AGENTS_DIR, filename)
|
|
80
|
-
|
|
81
|
-
path = if File.exist?(user_path) && !File.zero?(user_path)
|
|
82
|
-
user_path
|
|
83
|
-
elsif File.exist?(default_path)
|
|
84
|
-
default_path
|
|
85
|
-
end
|
|
148
|
+
user_yml = File.join(user_agent_dir, "profile.yml")
|
|
149
|
+
if File.file?(user_yml)
|
|
150
|
+
return YAML.safe_load(File.read(user_yml)) || {}
|
|
151
|
+
end
|
|
86
152
|
|
|
87
|
-
|
|
153
|
+
if @ext_unit
|
|
154
|
+
return {
|
|
155
|
+
"name" => @name,
|
|
156
|
+
"description" => @ext_unit.spec["description"],
|
|
157
|
+
"panels" => @ext_unit.spec["panels"],
|
|
158
|
+
"skills" => @ext_unit.spec["skills"],
|
|
159
|
+
}
|
|
160
|
+
end
|
|
88
161
|
|
|
89
|
-
|
|
162
|
+
raise ArgumentError, "Agent profile '#{@name}' not found. " \
|
|
163
|
+
"Looked in #{user_agent_dir} and extension registry."
|
|
90
164
|
end
|
|
91
165
|
|
|
92
|
-
#
|
|
93
|
-
private def
|
|
166
|
+
# Agent-specific file lookup: user override → extension prompt (system_prompt.md only).
|
|
167
|
+
private def load_agent_file(filename)
|
|
94
168
|
user_path = File.join(user_agent_dir, filename)
|
|
95
|
-
|
|
169
|
+
return File.read(user_path).strip if File.exist?(user_path) && !File.zero?(user_path)
|
|
96
170
|
|
|
97
|
-
if
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
default_path
|
|
171
|
+
if @ext_unit && filename == "system_prompt.md"
|
|
172
|
+
prompt_abs = @ext_unit.spec["prompt_abs"]
|
|
173
|
+
return File.read(prompt_abs).strip if prompt_abs && File.file?(prompt_abs)
|
|
101
174
|
end
|
|
175
|
+
|
|
176
|
+
""
|
|
102
177
|
end
|
|
103
178
|
|
|
104
179
|
private def user_agent_dir
|
|
105
180
|
File.join(USER_AGENTS_DIR, @name)
|
|
106
181
|
end
|
|
107
|
-
|
|
108
|
-
private def default_agent_dir
|
|
109
|
-
File.join(DEFAULT_AGENTS_DIR, @name)
|
|
110
|
-
end
|
|
111
182
|
end
|
|
112
183
|
end
|
|
@@ -13,7 +13,7 @@ module Clacky
|
|
|
13
13
|
BILLING_DIR = File.join(Dir.home, ".clacky", "billing")
|
|
14
14
|
|
|
15
15
|
def initialize(billing_dir: nil)
|
|
16
|
-
@billing_dir = billing_dir || BILLING_DIR
|
|
16
|
+
@billing_dir = billing_dir || ENV["CLACKY_BILLING_DIR"] || BILLING_DIR
|
|
17
17
|
ensure_billing_dir
|
|
18
18
|
end
|
|
19
19
|
|
|
@@ -339,7 +339,8 @@ module Clacky
|
|
|
339
339
|
when :day
|
|
340
340
|
Time.new(now.year, now.month, now.day)
|
|
341
341
|
when :week
|
|
342
|
-
now
|
|
342
|
+
days_since_monday = now.wday == 0 ? 6 : now.wday - 1
|
|
343
|
+
Time.new(now.year, now.month, now.day) - (days_since_monday * 24 * 60 * 60)
|
|
343
344
|
when :month
|
|
344
345
|
Time.new(now.year, now.month, 1)
|
|
345
346
|
when :year
|