claude_agent 0.7.15 → 0.7.16
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/.claude/rules/conventions.md +66 -16
- data/CHANGELOG.md +2 -0
- data/CLAUDE.md +24 -4
- data/README.md +52 -1529
- data/docs/architecture.md +339 -0
- data/docs/client.md +526 -0
- data/docs/configuration.md +571 -0
- data/docs/conversations.md +461 -0
- data/docs/errors.md +127 -0
- data/docs/events.md +225 -0
- data/docs/getting-started.md +310 -0
- data/docs/hooks.md +380 -0
- data/docs/logging.md +96 -0
- data/docs/mcp.md +308 -0
- data/docs/messages.md +871 -0
- data/docs/permissions.md +611 -0
- data/docs/queries.md +227 -0
- data/docs/sessions.md +335 -0
- data/lib/claude_agent/configuration.rb +129 -0
- data/lib/claude_agent/conversation.rb +28 -3
- data/lib/claude_agent/errors.rb +3 -0
- data/lib/claude_agent/event_handler.rb +14 -0
- data/lib/claude_agent/hook_registry.rb +110 -0
- data/lib/claude_agent/mcp/server.rb +22 -0
- data/lib/claude_agent/mcp/tool.rb +24 -3
- data/lib/claude_agent/message.rb +93 -0
- data/lib/claude_agent/options.rb +10 -0
- data/lib/claude_agent/permission_policy.rb +174 -0
- data/lib/claude_agent/session.rb +100 -11
- data/lib/claude_agent/session_paths.rb +5 -2
- data/lib/claude_agent/version.rb +1 -1
- data/lib/claude_agent.rb +175 -0
- metadata +19 -1
data/lib/claude_agent/session.rb
CHANGED
|
@@ -1,24 +1,30 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module ClaudeAgent
|
|
4
|
-
# Historical session finder with Rails-like API.
|
|
4
|
+
# Historical session finder with Rails-like API and Stripe-style resource methods.
|
|
5
5
|
#
|
|
6
|
-
# Wraps SessionInfo with a rich interface for finding sessions
|
|
7
|
-
#
|
|
6
|
+
# Wraps SessionInfo with a rich interface for finding sessions,
|
|
7
|
+
# querying their message transcripts, and mutating session metadata.
|
|
8
8
|
#
|
|
9
9
|
# @example Find a session by ID
|
|
10
10
|
# session = ClaudeAgent::Session.find("abc-123")
|
|
11
11
|
# session.summary # => "Fix login bug"
|
|
12
12
|
#
|
|
13
|
-
# @example
|
|
14
|
-
#
|
|
13
|
+
# @example Retrieve (raises on not found)
|
|
14
|
+
# session = ClaudeAgent::Session.retrieve("abc-123")
|
|
15
15
|
#
|
|
16
|
-
# @example
|
|
17
|
-
# session.
|
|
16
|
+
# @example Mutations
|
|
17
|
+
# session.rename("My Session")
|
|
18
|
+
# session.tag("important")
|
|
19
|
+
# forked = session.fork(title: "Branch off")
|
|
20
|
+
#
|
|
21
|
+
# @example Resume a conversation
|
|
22
|
+
# session.resume(model: "opus") { |c| c.say("Continue") }
|
|
18
23
|
#
|
|
19
24
|
class Session
|
|
20
25
|
attr_reader :session_id, :summary, :last_modified, :file_size,
|
|
21
|
-
:custom_title, :first_prompt, :git_branch, :cwd
|
|
26
|
+
:custom_title, :first_prompt, :git_branch, :cwd,
|
|
27
|
+
:tag, :created_at
|
|
22
28
|
|
|
23
29
|
def initialize(session_info)
|
|
24
30
|
@session_id = session_info.session_id
|
|
@@ -29,6 +35,8 @@ module ClaudeAgent
|
|
|
29
35
|
@first_prompt = session_info.first_prompt
|
|
30
36
|
@git_branch = session_info.git_branch
|
|
31
37
|
@cwd = session_info.cwd
|
|
38
|
+
@tag = session_info.tag
|
|
39
|
+
@created_at = session_info.created_at
|
|
32
40
|
@dir = session_info.cwd
|
|
33
41
|
end
|
|
34
42
|
|
|
@@ -39,18 +47,99 @@ module ClaudeAgent
|
|
|
39
47
|
SessionMessageRelation.new(session_id, dir: @dir)
|
|
40
48
|
end
|
|
41
49
|
|
|
50
|
+
# --- Instance Mutation Methods ---
|
|
51
|
+
|
|
52
|
+
# Rename this session.
|
|
53
|
+
#
|
|
54
|
+
# @param title [String] New title
|
|
55
|
+
# @return [self]
|
|
56
|
+
def rename(title)
|
|
57
|
+
SessionMutations.rename_session(session_id, title, dir: @dir)
|
|
58
|
+
@custom_title = title
|
|
59
|
+
self
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Tag this session.
|
|
63
|
+
#
|
|
64
|
+
# @param value [String, nil] Tag value. Pass nil to clear.
|
|
65
|
+
# @return [self]
|
|
66
|
+
def tag_session(value)
|
|
67
|
+
SessionMutations.tag_session(session_id, value, dir: @dir)
|
|
68
|
+
@tag = value
|
|
69
|
+
self
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Fork this session.
|
|
73
|
+
#
|
|
74
|
+
# @param up_to [String, nil] Truncate at this message UUID (inclusive)
|
|
75
|
+
# @param title [String, nil] Title for the forked session
|
|
76
|
+
# @return [Session] The new forked session
|
|
77
|
+
def fork(up_to: nil, title: nil)
|
|
78
|
+
result = ForkSession.call(session_id, up_to_message_id: up_to, title: title, dir: @dir)
|
|
79
|
+
info = GetSessionInfo.call(result.session_id, dir: @dir)
|
|
80
|
+
info ? Session.new(info) : Session.new(SessionInfo.new(
|
|
81
|
+
session_id: result.session_id,
|
|
82
|
+
summary: title || @summary,
|
|
83
|
+
last_modified: Time.now.to_i * 1000,
|
|
84
|
+
file_size: 0
|
|
85
|
+
))
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Re-read session metadata from disk.
|
|
89
|
+
#
|
|
90
|
+
# @return [self]
|
|
91
|
+
def reload
|
|
92
|
+
info = GetSessionInfo.call(session_id, dir: @dir)
|
|
93
|
+
raise NotFoundError, "Session not found: #{session_id}" unless info
|
|
94
|
+
|
|
95
|
+
@summary = info.summary
|
|
96
|
+
@last_modified = info.last_modified
|
|
97
|
+
@file_size = info.file_size
|
|
98
|
+
@custom_title = info.custom_title
|
|
99
|
+
@first_prompt = info.first_prompt
|
|
100
|
+
@git_branch = info.git_branch
|
|
101
|
+
@cwd = info.cwd
|
|
102
|
+
@tag = info.tag
|
|
103
|
+
@created_at = info.created_at
|
|
104
|
+
self
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Resume this session as a Conversation.
|
|
108
|
+
#
|
|
109
|
+
# @param kwargs Options/Conversation keyword arguments
|
|
110
|
+
# @yield [Conversation] Block form with auto-cleanup
|
|
111
|
+
# @return [Conversation, Object]
|
|
112
|
+
def resume(**kwargs, &block)
|
|
113
|
+
if block
|
|
114
|
+
Conversation.open(resume: session_id, **kwargs, &block)
|
|
115
|
+
else
|
|
116
|
+
Conversation.resume(session_id, **kwargs)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
42
120
|
class << self
|
|
43
|
-
# Find a session by its UUID.
|
|
121
|
+
# Find a session by its UUID (targeted lookup, not full scan).
|
|
44
122
|
#
|
|
45
123
|
# @param session_id [String] UUID of the session
|
|
46
124
|
# @param dir [String, nil] Directory to scope the search
|
|
47
125
|
# @return [Session, nil] The session, or nil if not found
|
|
48
126
|
def find(session_id, dir: nil)
|
|
49
|
-
|
|
50
|
-
info = sessions.find { |s| s.session_id == session_id }
|
|
127
|
+
info = GetSessionInfo.call(session_id, dir: dir)
|
|
51
128
|
info ? new(info) : nil
|
|
52
129
|
end
|
|
53
130
|
|
|
131
|
+
# Retrieve a session by UUID. Raises if not found (Stripe convention).
|
|
132
|
+
#
|
|
133
|
+
# @param session_id [String] UUID of the session
|
|
134
|
+
# @param dir [String, nil] Directory to scope the search
|
|
135
|
+
# @return [Session]
|
|
136
|
+
# @raise [NotFoundError] If session is not found
|
|
137
|
+
def retrieve(session_id, dir: nil)
|
|
138
|
+
info = GetSessionInfo.call(session_id, dir: dir)
|
|
139
|
+
raise NotFoundError, "Session not found: #{session_id}" unless info
|
|
140
|
+
new(info)
|
|
141
|
+
end
|
|
142
|
+
|
|
54
143
|
# List all sessions.
|
|
55
144
|
#
|
|
56
145
|
# @return [Array<Session>]
|
|
@@ -93,9 +93,12 @@ module ClaudeAgent
|
|
|
93
93
|
# @param path [String]
|
|
94
94
|
# @return [String]
|
|
95
95
|
def realpath(path)
|
|
96
|
-
File.realpath(path)
|
|
96
|
+
resolved = File.realpath(path)
|
|
97
|
+
resolved = resolved.encode("UTF-8") unless resolved.encoding == Encoding::UTF_8
|
|
98
|
+
resolved.unicode_normalize(:nfc)
|
|
97
99
|
rescue SystemCallError
|
|
98
|
-
path.
|
|
100
|
+
safe = path.encode("UTF-8") rescue path
|
|
101
|
+
safe.unicode_normalize(:nfc) rescue safe
|
|
99
102
|
end
|
|
100
103
|
|
|
101
104
|
# Get git worktree paths for a directory.
|
data/lib/claude_agent/version.rb
CHANGED
data/lib/claude_agent.rb
CHANGED
|
@@ -13,6 +13,9 @@ require_relative "claude_agent/spawn" # Custom spawn support (TypeS
|
|
|
13
13
|
require_relative "claude_agent/options"
|
|
14
14
|
require_relative "claude_agent/content_blocks"
|
|
15
15
|
require_relative "claude_agent/messages"
|
|
16
|
+
require_relative "claude_agent/message" # Shared interface module for all message/block types
|
|
17
|
+
require_relative "claude_agent/permission_policy" # Declarative permission DSL
|
|
18
|
+
require_relative "claude_agent/hook_registry" # Declarative hooks DSL
|
|
16
19
|
require_relative "claude_agent/message_parser"
|
|
17
20
|
require_relative "claude_agent/hooks"
|
|
18
21
|
require_relative "claude_agent/permissions"
|
|
@@ -40,10 +43,135 @@ require_relative "claude_agent/session_mutations" # Session rename/tag
|
|
|
40
43
|
require_relative "claude_agent/get_session_info" # Single session lookup
|
|
41
44
|
require_relative "claude_agent/fork_session" # Session forking (TypeScript SDK v0.2.76 parity)
|
|
42
45
|
require_relative "claude_agent/v2_session" # V2 Session API (unstable)
|
|
46
|
+
require_relative "claude_agent/configuration" # Stripe-style global config
|
|
43
47
|
require_relative "claude_agent/session" # Session finder
|
|
44
48
|
|
|
45
49
|
module ClaudeAgent
|
|
50
|
+
require "forwardable"
|
|
51
|
+
|
|
52
|
+
@config = Configuration.setup
|
|
53
|
+
|
|
46
54
|
class << self
|
|
55
|
+
extend Forwardable
|
|
56
|
+
attr_reader :config
|
|
57
|
+
|
|
58
|
+
# --- Tier 1 delegators (set once at boot) ---
|
|
59
|
+
|
|
60
|
+
def_delegators :@config, :model, :model=,
|
|
61
|
+
:permission_mode, :permission_mode=,
|
|
62
|
+
:max_turns, :max_turns=,
|
|
63
|
+
:max_budget_usd, :max_budget_usd=,
|
|
64
|
+
:system_prompt, :system_prompt=,
|
|
65
|
+
:append_system_prompt, :append_system_prompt=,
|
|
66
|
+
:cli_path, :cli_path=,
|
|
67
|
+
:cwd, :cwd=,
|
|
68
|
+
:sandbox, :sandbox=,
|
|
69
|
+
:debug, :debug=,
|
|
70
|
+
:effort, :effort=,
|
|
71
|
+
:persist_session, :persist_session=,
|
|
72
|
+
:fallback_model, :fallback_model=
|
|
73
|
+
|
|
74
|
+
# Block-based bulk configuration.
|
|
75
|
+
#
|
|
76
|
+
# @example
|
|
77
|
+
# ClaudeAgent.configure do |c|
|
|
78
|
+
# c.model = "opus"
|
|
79
|
+
# c.max_turns = 10
|
|
80
|
+
# end
|
|
81
|
+
#
|
|
82
|
+
# @yield [Configuration]
|
|
83
|
+
# @return [void]
|
|
84
|
+
def configure
|
|
85
|
+
yield @config
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Reset configuration to defaults.
|
|
89
|
+
# @return [Configuration]
|
|
90
|
+
def reset_config!
|
|
91
|
+
@config = Configuration.setup
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# --- Primary entry points ---
|
|
95
|
+
|
|
96
|
+
# One-shot query — the simple path returns a TurnResult.
|
|
97
|
+
#
|
|
98
|
+
# @param prompt [String] The prompt to send to Claude
|
|
99
|
+
# @param options [Options, nil] Pre-built Options (bypasses Configuration merge)
|
|
100
|
+
# @param kwargs Overrides merged with Configuration defaults
|
|
101
|
+
# @yield [Message] Each message as it streams in (optional)
|
|
102
|
+
# @return [TurnResult]
|
|
103
|
+
#
|
|
104
|
+
# @example Simple
|
|
105
|
+
# turn = ClaudeAgent.ask("What is 2+2?")
|
|
106
|
+
# puts turn.text
|
|
107
|
+
#
|
|
108
|
+
# @example With overrides
|
|
109
|
+
# turn = ClaudeAgent.ask("Fix the bug", model: "opus", max_turns: 5)
|
|
110
|
+
#
|
|
111
|
+
# @example With streaming
|
|
112
|
+
# turn = ClaudeAgent.ask("Explain Ruby") { |msg| print msg.text_content }
|
|
113
|
+
#
|
|
114
|
+
def ask(prompt, options: nil, **kwargs, &block)
|
|
115
|
+
callbacks, config_overrides = extract_callbacks(kwargs)
|
|
116
|
+
|
|
117
|
+
opts = options || @config.to_options(**config_overrides)
|
|
118
|
+
events = build_events(callbacks)
|
|
119
|
+
|
|
120
|
+
query_turn(prompt: prompt, options: opts, events: events, &block)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Multi-turn conversation — block form auto-cleans, no block returns Conversation.
|
|
124
|
+
#
|
|
125
|
+
# @param kwargs Overrides merged with Configuration defaults
|
|
126
|
+
# @yield [Conversation] Block form with auto-cleanup
|
|
127
|
+
# @return [Conversation, Object] Conversation (no block) or block return value
|
|
128
|
+
#
|
|
129
|
+
# @example Block form
|
|
130
|
+
# ClaudeAgent.chat(model: "opus") do |c|
|
|
131
|
+
# c.say("Hello")
|
|
132
|
+
# c.say("Goodbye")
|
|
133
|
+
# end
|
|
134
|
+
#
|
|
135
|
+
# @example No block
|
|
136
|
+
# c = ClaudeAgent.chat(model: "opus")
|
|
137
|
+
# c.say("Hello")
|
|
138
|
+
# c.close
|
|
139
|
+
#
|
|
140
|
+
def chat(**kwargs, &block)
|
|
141
|
+
# Merge global config defaults into kwargs for Conversation
|
|
142
|
+
merged = merge_config_into_kwargs(kwargs)
|
|
143
|
+
|
|
144
|
+
if block
|
|
145
|
+
Conversation.open(**merged, &block)
|
|
146
|
+
else
|
|
147
|
+
Conversation.new(**merged)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Set a global permission policy.
|
|
152
|
+
#
|
|
153
|
+
# @yield [PermissionPolicy] DSL block
|
|
154
|
+
# @return [void]
|
|
155
|
+
def permissions(&block)
|
|
156
|
+
@config.default_permissions = PermissionPolicy.new(&block)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Set global hooks.
|
|
160
|
+
#
|
|
161
|
+
# @yield [HookRegistry] DSL block
|
|
162
|
+
# @return [void]
|
|
163
|
+
def hooks(&block)
|
|
164
|
+
@config.default_hooks = HookRegistry.new(&block)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Register a global MCP server.
|
|
168
|
+
#
|
|
169
|
+
# @param server [MCP::Server] Server instance
|
|
170
|
+
# @return [void]
|
|
171
|
+
def register_mcp_server(server)
|
|
172
|
+
@config.default_mcp_servers[server.name] = server.to_config
|
|
173
|
+
end
|
|
174
|
+
|
|
47
175
|
# Create a new Conversation
|
|
48
176
|
#
|
|
49
177
|
# @see Conversation#initialize
|
|
@@ -130,5 +258,52 @@ module ClaudeAgent
|
|
|
130
258
|
def resume_conversation(session_id, **kwargs)
|
|
131
259
|
Conversation.resume(session_id, **kwargs)
|
|
132
260
|
end
|
|
261
|
+
|
|
262
|
+
private
|
|
263
|
+
|
|
264
|
+
# Separate on_* callbacks from config overrides in kwargs.
|
|
265
|
+
def extract_callbacks(kwargs)
|
|
266
|
+
callbacks = {}
|
|
267
|
+
config_overrides = {}
|
|
268
|
+
|
|
269
|
+
kwargs.each do |key, value|
|
|
270
|
+
if key.to_s.start_with?("on_") || key == :can_use_tool
|
|
271
|
+
callbacks[key] = value
|
|
272
|
+
else
|
|
273
|
+
config_overrides[key] = value
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
[ callbacks, config_overrides ]
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# Build an EventHandler from on_* callback kwargs.
|
|
281
|
+
def build_events(callbacks)
|
|
282
|
+
return nil if callbacks.empty?
|
|
283
|
+
|
|
284
|
+
events = EventHandler.new
|
|
285
|
+
callbacks.each do |key, value|
|
|
286
|
+
next unless value
|
|
287
|
+
next if key == :can_use_tool
|
|
288
|
+
|
|
289
|
+
event = Conversation::CALLBACK_ALIASES[key] || key.to_s.delete_prefix("on_").to_sym
|
|
290
|
+
events.on(event, &value)
|
|
291
|
+
end
|
|
292
|
+
events.has_handlers? ? events : nil
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Merge global config defaults into Conversation kwargs.
|
|
296
|
+
def merge_config_into_kwargs(kwargs)
|
|
297
|
+
merged = {}
|
|
298
|
+
|
|
299
|
+
# Apply config defaults for fields Conversation forwards to Options
|
|
300
|
+
Configuration::ALL_FIELDS.each do |field|
|
|
301
|
+
config_val = @config.public_send(field)
|
|
302
|
+
merged[field] = config_val unless config_val.nil?
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# Per-request kwargs override config
|
|
306
|
+
merged.merge(kwargs)
|
|
307
|
+
end
|
|
133
308
|
end
|
|
134
309
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: claude_agent
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.7.
|
|
4
|
+
version: 0.7.16
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Thomas Carr
|
|
@@ -51,10 +51,25 @@ files:
|
|
|
51
51
|
- README.md
|
|
52
52
|
- Rakefile
|
|
53
53
|
- SPEC.md
|
|
54
|
+
- docs/architecture.md
|
|
55
|
+
- docs/client.md
|
|
56
|
+
- docs/configuration.md
|
|
57
|
+
- docs/conversations.md
|
|
58
|
+
- docs/errors.md
|
|
59
|
+
- docs/events.md
|
|
60
|
+
- docs/getting-started.md
|
|
61
|
+
- docs/hooks.md
|
|
62
|
+
- docs/logging.md
|
|
63
|
+
- docs/mcp.md
|
|
64
|
+
- docs/messages.md
|
|
65
|
+
- docs/permissions.md
|
|
66
|
+
- docs/queries.md
|
|
67
|
+
- docs/sessions.md
|
|
54
68
|
- lib/claude_agent.rb
|
|
55
69
|
- lib/claude_agent/abort_controller.rb
|
|
56
70
|
- lib/claude_agent/client.rb
|
|
57
71
|
- lib/claude_agent/client/commands.rb
|
|
72
|
+
- lib/claude_agent/configuration.rb
|
|
58
73
|
- lib/claude_agent/content_blocks.rb
|
|
59
74
|
- lib/claude_agent/content_blocks/generic_block.rb
|
|
60
75
|
- lib/claude_agent/content_blocks/image_content_block.rb
|
|
@@ -77,12 +92,14 @@ files:
|
|
|
77
92
|
- lib/claude_agent/fork_session.rb
|
|
78
93
|
- lib/claude_agent/get_session_info.rb
|
|
79
94
|
- lib/claude_agent/get_session_messages.rb
|
|
95
|
+
- lib/claude_agent/hook_registry.rb
|
|
80
96
|
- lib/claude_agent/hooks.rb
|
|
81
97
|
- lib/claude_agent/list_sessions.rb
|
|
82
98
|
- lib/claude_agent/live_tool_activity.rb
|
|
83
99
|
- lib/claude_agent/logging.rb
|
|
84
100
|
- lib/claude_agent/mcp/server.rb
|
|
85
101
|
- lib/claude_agent/mcp/tool.rb
|
|
102
|
+
- lib/claude_agent/message.rb
|
|
86
103
|
- lib/claude_agent/message_parser.rb
|
|
87
104
|
- lib/claude_agent/messages.rb
|
|
88
105
|
- lib/claude_agent/messages/conversation.rb
|
|
@@ -95,6 +112,7 @@ files:
|
|
|
95
112
|
- lib/claude_agent/messages/tool_lifecycle.rb
|
|
96
113
|
- lib/claude_agent/options.rb
|
|
97
114
|
- lib/claude_agent/options/serializer.rb
|
|
115
|
+
- lib/claude_agent/permission_policy.rb
|
|
98
116
|
- lib/claude_agent/permission_queue.rb
|
|
99
117
|
- lib/claude_agent/permission_request.rb
|
|
100
118
|
- lib/claude_agent/permissions.rb
|