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.
@@ -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
- # and querying their message transcripts.
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 List all sessions
14
- # sessions = ClaudeAgent::Session.all(limit: 10)
13
+ # @example Retrieve (raises on not found)
14
+ # session = ClaudeAgent::Session.retrieve("abc-123")
15
15
  #
16
- # @example Query messages with chainable relation
17
- # session.messages.where(limit: 5).each { |m| puts m.type }
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
- sessions = ListSessions.call(dir: dir)
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).unicode_normalize(:nfc)
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.unicode_normalize(:nfc)
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.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClaudeAgent
4
- VERSION = "0.7.15"
4
+ VERSION = "0.7.16"
5
5
  end
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.15
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