tsikol 0.1.0

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.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +22 -0
  3. data/CONTRIBUTING.md +84 -0
  4. data/LICENSE +21 -0
  5. data/README.md +579 -0
  6. data/Rakefile +12 -0
  7. data/docs/README.md +69 -0
  8. data/docs/api/middleware.md +721 -0
  9. data/docs/api/prompt.md +858 -0
  10. data/docs/api/resource.md +651 -0
  11. data/docs/api/server.md +509 -0
  12. data/docs/api/test-helpers.md +591 -0
  13. data/docs/api/tool.md +527 -0
  14. data/docs/cookbook/authentication.md +651 -0
  15. data/docs/cookbook/caching.md +877 -0
  16. data/docs/cookbook/dynamic-tools.md +970 -0
  17. data/docs/cookbook/error-handling.md +887 -0
  18. data/docs/cookbook/logging.md +1044 -0
  19. data/docs/cookbook/rate-limiting.md +717 -0
  20. data/docs/examples/code-assistant.md +922 -0
  21. data/docs/examples/complete-server.md +726 -0
  22. data/docs/examples/database-manager.md +1198 -0
  23. data/docs/examples/devops-tools.md +1382 -0
  24. data/docs/examples/echo-server.md +501 -0
  25. data/docs/examples/weather-service.md +822 -0
  26. data/docs/guides/completion.md +472 -0
  27. data/docs/guides/getting-started.md +462 -0
  28. data/docs/guides/middleware.md +823 -0
  29. data/docs/guides/project-structure.md +434 -0
  30. data/docs/guides/prompts.md +920 -0
  31. data/docs/guides/resources.md +720 -0
  32. data/docs/guides/sampling.md +804 -0
  33. data/docs/guides/testing.md +863 -0
  34. data/docs/guides/tools.md +627 -0
  35. data/examples/README.md +92 -0
  36. data/examples/advanced_features.rb +129 -0
  37. data/examples/basic-migrated/app/prompts/weather_chat.rb +44 -0
  38. data/examples/basic-migrated/app/resources/weather_alerts.rb +18 -0
  39. data/examples/basic-migrated/app/tools/get_current_weather.rb +34 -0
  40. data/examples/basic-migrated/app/tools/get_forecast.rb +30 -0
  41. data/examples/basic-migrated/app/tools/get_weather_by_coords.rb +48 -0
  42. data/examples/basic-migrated/server.rb +25 -0
  43. data/examples/basic.rb +73 -0
  44. data/examples/full_featured.rb +175 -0
  45. data/examples/middleware_example.rb +112 -0
  46. data/examples/sampling_example.rb +104 -0
  47. data/examples/weather-service/app/prompts/weather/chat.rb +90 -0
  48. data/examples/weather-service/app/resources/weather/alerts.rb +59 -0
  49. data/examples/weather-service/app/tools/weather/get_current.rb +82 -0
  50. data/examples/weather-service/app/tools/weather/get_forecast.rb +90 -0
  51. data/examples/weather-service/server.rb +28 -0
  52. data/exe/tsikol +6 -0
  53. data/lib/tsikol/cli/templates/Gemfile.erb +10 -0
  54. data/lib/tsikol/cli/templates/README.md.erb +38 -0
  55. data/lib/tsikol/cli/templates/gitignore.erb +49 -0
  56. data/lib/tsikol/cli/templates/prompt.rb.erb +53 -0
  57. data/lib/tsikol/cli/templates/resource.rb.erb +29 -0
  58. data/lib/tsikol/cli/templates/server.rb.erb +24 -0
  59. data/lib/tsikol/cli/templates/tool.rb.erb +60 -0
  60. data/lib/tsikol/cli.rb +203 -0
  61. data/lib/tsikol/error_handler.rb +141 -0
  62. data/lib/tsikol/health.rb +198 -0
  63. data/lib/tsikol/http_transport.rb +72 -0
  64. data/lib/tsikol/lifecycle.rb +149 -0
  65. data/lib/tsikol/middleware.rb +168 -0
  66. data/lib/tsikol/prompt.rb +101 -0
  67. data/lib/tsikol/resource.rb +53 -0
  68. data/lib/tsikol/router.rb +190 -0
  69. data/lib/tsikol/server.rb +660 -0
  70. data/lib/tsikol/stdio_transport.rb +108 -0
  71. data/lib/tsikol/test_helpers.rb +261 -0
  72. data/lib/tsikol/tool.rb +111 -0
  73. data/lib/tsikol/version.rb +5 -0
  74. data/lib/tsikol.rb +72 -0
  75. metadata +219 -0
@@ -0,0 +1,651 @@
1
+ # Resource API Reference
2
+
3
+ The `Tsikol::Resource` class provides the foundation for creating resources that expose read-only data to AI assistants.
4
+
5
+ ## Class: Tsikol::Resource
6
+
7
+ ### Class Methods
8
+
9
+ #### `.uri(path = nil)`
10
+
11
+ Set or get the resource URI.
12
+
13
+ ```ruby
14
+ class SystemStatus < Tsikol::Resource
15
+ uri "system/status"
16
+ end
17
+ ```
18
+
19
+ #### `.description(text = nil)`
20
+
21
+ Set or get the resource description.
22
+
23
+ ```ruby
24
+ class ConfigResource < Tsikol::Resource
25
+ uri "config"
26
+ description "Application configuration values"
27
+ end
28
+ ```
29
+
30
+ ### Instance Methods
31
+
32
+ #### `#read`
33
+
34
+ Main method to implement resource logic. Must return a string (usually JSON).
35
+
36
+ ```ruby
37
+ class VersionResource < Tsikol::Resource
38
+ uri "version"
39
+ description "Current application version"
40
+
41
+ def read
42
+ {
43
+ version: "1.0.0",
44
+ build: "2024.01.15",
45
+ git_commit: ENV['GIT_COMMIT'] || "unknown"
46
+ }.to_json
47
+ end
48
+ end
49
+ ```
50
+
51
+ #### `#uri`
52
+
53
+ Get the resource URI (instance method).
54
+
55
+ ```ruby
56
+ resource = SystemStatus.new
57
+ resource.uri # => "system/status"
58
+ ```
59
+
60
+ #### `#set_server(server)`
61
+
62
+ Called when resource is registered with a server. Override to access server features.
63
+
64
+ ```ruby
65
+ def set_server(server)
66
+ @server = server
67
+ @start_time = Time.now
68
+
69
+ # Enable logging if available
70
+ if @server.logging_enabled?
71
+ define_singleton_method(:log) do |level, message, data: nil|
72
+ @server.log(level, message, data: data)
73
+ end
74
+ end
75
+ end
76
+ ```
77
+
78
+ ### Resource Registration
79
+
80
+ Resources can be registered in several ways:
81
+
82
+ #### Class Registration
83
+
84
+ ```ruby
85
+ server.register_resource_class(SystemStatus)
86
+ # or
87
+ server.resource SystemStatus
88
+ ```
89
+
90
+ #### Instance Registration
91
+
92
+ ```ruby
93
+ resource = SystemStatus.new
94
+ server.register_resource_instance(resource)
95
+ ```
96
+
97
+ #### Inline Registration
98
+
99
+ ```ruby
100
+ server.register_resource("time") do
101
+ Time.now.iso8601
102
+ end
103
+ # or
104
+ server.resource "time" do
105
+ Time.now.iso8601
106
+ end
107
+ ```
108
+
109
+ ### URI Patterns
110
+
111
+ Resources use URI-style paths for organization:
112
+
113
+ ```ruby
114
+ # Top-level resource
115
+ class RootConfig < Tsikol::Resource
116
+ uri "config"
117
+ end
118
+
119
+ # Nested resource
120
+ class DatabaseConfig < Tsikol::Resource
121
+ uri "config/database"
122
+ end
123
+
124
+ # Deep nesting
125
+ class UserPreferences < Tsikol::Resource
126
+ uri "users/current/preferences"
127
+ end
128
+ ```
129
+
130
+ ### Dynamic Resources
131
+
132
+ Resources can return different data based on context:
133
+
134
+ ```ruby
135
+ class MetricsResource < Tsikol::Resource
136
+ uri "metrics"
137
+ description "Real-time system metrics"
138
+
139
+ def read
140
+ {
141
+ timestamp: Time.now.iso8601,
142
+ cpu_usage: current_cpu_usage,
143
+ memory_usage: current_memory_usage,
144
+ active_connections: connection_count,
145
+ request_rate: calculate_request_rate
146
+ }.to_json
147
+ end
148
+
149
+ private
150
+
151
+ def current_cpu_usage
152
+ # Implementation specific
153
+ "#{rand(0..100)}%"
154
+ end
155
+
156
+ def current_memory_usage
157
+ # Get actual memory usage
158
+ rss = `ps -o rss= -p #{Process.pid}`.to_i
159
+ "#{(rss / 1024.0).round(2)}MB"
160
+ end
161
+
162
+ def connection_count
163
+ Thread.list.select(&:alive?).count
164
+ end
165
+
166
+ def calculate_request_rate
167
+ # Calculate from server metrics
168
+ @server.respond_to?(:request_count) ? @server.request_count : 0
169
+ end
170
+ end
171
+ ```
172
+
173
+ ### Resource Formats
174
+
175
+ While resources typically return JSON, they can return any string format:
176
+
177
+ #### JSON (Most Common)
178
+
179
+ ```ruby
180
+ class JsonResource < Tsikol::Resource
181
+ uri "data/users"
182
+
183
+ def read
184
+ users = fetch_users
185
+ users.map { |u| { id: u.id, name: u.name } }.to_json
186
+ end
187
+ end
188
+ ```
189
+
190
+ #### Plain Text
191
+
192
+ ```ruby
193
+ class TextResource < Tsikol::Resource
194
+ uri "docs/readme"
195
+
196
+ def read
197
+ File.read("README.md")
198
+ end
199
+ end
200
+ ```
201
+
202
+ #### CSV
203
+
204
+ ```ruby
205
+ class CsvResource < Tsikol::Resource
206
+ uri "data/export.csv"
207
+
208
+ def read
209
+ require 'csv'
210
+
211
+ CSV.generate do |csv|
212
+ csv << ["ID", "Name", "Email"]
213
+ users.each do |user|
214
+ csv << [user.id, user.name, user.email]
215
+ end
216
+ end
217
+ end
218
+ end
219
+ ```
220
+
221
+ #### XML
222
+
223
+ ```ruby
224
+ class XmlResource < Tsikol::Resource
225
+ uri "data/config.xml"
226
+
227
+ def read
228
+ builder = Nokogiri::XML::Builder.new do |xml|
229
+ xml.configuration {
230
+ xml.database {
231
+ xml.host ENV['DB_HOST']
232
+ xml.port ENV['DB_PORT']
233
+ }
234
+ }
235
+ end
236
+ builder.to_xml
237
+ end
238
+ end
239
+ ```
240
+
241
+ ### Caching
242
+
243
+ Implement caching for expensive operations:
244
+
245
+ ```ruby
246
+ class CachedResource < Tsikol::Resource
247
+ uri "expensive/data"
248
+ description "Expensive computation (cached)"
249
+
250
+ def initialize
251
+ super
252
+ @cache = nil
253
+ @cache_time = nil
254
+ @cache_ttl = 300 # 5 minutes
255
+ end
256
+
257
+ def read
258
+ if cache_valid?
259
+ log :debug, "Cache hit"
260
+ @cache
261
+ else
262
+ log :debug, "Cache miss, computing..."
263
+ @cache = compute_expensive_data
264
+ @cache_time = Time.now
265
+ @cache
266
+ end
267
+ end
268
+
269
+ private
270
+
271
+ def cache_valid?
272
+ return false unless @cache && @cache_time
273
+ Time.now - @cache_time < @cache_ttl
274
+ end
275
+
276
+ def compute_expensive_data
277
+ # Simulate expensive operation
278
+ sleep(0.5)
279
+
280
+ {
281
+ computed_at: Time.now.iso8601,
282
+ result: perform_calculation,
283
+ valid_for: @cache_ttl
284
+ }.to_json
285
+ end
286
+ end
287
+ ```
288
+
289
+ ### Error Handling
290
+
291
+ Resources should handle errors gracefully:
292
+
293
+ ```ruby
294
+ class SafeResource < Tsikol::Resource
295
+ uri "external/api"
296
+ description "Data from external API"
297
+
298
+ def read
299
+ response = fetch_external_data
300
+ format_response(response)
301
+
302
+ rescue Net::HTTPError => e
303
+ log :error, "HTTP error fetching data", error: e.message
304
+ error_response("External API unavailable")
305
+
306
+ rescue JSON::ParserError => e
307
+ log :error, "Invalid JSON from API", error: e.message
308
+ error_response("Invalid data format")
309
+
310
+ rescue => e
311
+ log :error, "Unexpected error", error: e.class.name
312
+ error_response("An error occurred")
313
+ end
314
+
315
+ private
316
+
317
+ def error_response(message)
318
+ {
319
+ error: message,
320
+ timestamp: Time.now.iso8601
321
+ }.to_json
322
+ end
323
+ end
324
+ ```
325
+
326
+ ### Using Server Features
327
+
328
+ Resources can access server capabilities:
329
+
330
+ ```ruby
331
+ class ServerAwareResource < Tsikol::Resource
332
+ uri "server/info"
333
+ description "Information about this server"
334
+
335
+ def read
336
+ info = {
337
+ name: @server.name,
338
+ version: @server.version,
339
+ uptime: calculate_uptime,
340
+ capabilities: list_capabilities
341
+ }
342
+
343
+ # Add request count if metrics available
344
+ if @server.respond_to?(:metrics)
345
+ info[:total_requests] = @server.metrics[:request_count]
346
+ end
347
+
348
+ info.to_json
349
+ end
350
+
351
+ private
352
+
353
+ def calculate_uptime
354
+ return 0 unless @start_time
355
+ Time.now - @start_time
356
+ end
357
+
358
+ def list_capabilities
359
+ caps = []
360
+ caps << "logging" if @server.logging_enabled?
361
+ caps << "completion" if @server.completion_enabled?
362
+ caps << "sampling" if @server.sampling_enabled?
363
+ caps
364
+ end
365
+
366
+ def set_server(server)
367
+ @server = server
368
+ @start_time = Time.now
369
+ end
370
+ end
371
+ ```
372
+
373
+ ## Complete Example
374
+
375
+ ```ruby
376
+ class SystemMetrics < Tsikol::Resource
377
+ uri "system/metrics"
378
+ description "Comprehensive system metrics and statistics"
379
+
380
+ def initialize
381
+ super
382
+ @metrics_cache = {}
383
+ @last_cpu_check = Time.now
384
+ @last_cpu_value = 0
385
+ end
386
+
387
+ def read
388
+ {
389
+ server: server_metrics,
390
+ system: system_metrics,
391
+ process: process_metrics,
392
+ custom: custom_metrics,
393
+ timestamp: Time.now.iso8601
394
+ }.to_json
395
+ rescue => e
396
+ log :error, "Error collecting metrics", error: e.message
397
+ minimal_metrics.to_json
398
+ end
399
+
400
+ private
401
+
402
+ def server_metrics
403
+ {
404
+ name: @server.name,
405
+ version: @server.version,
406
+ uptime_seconds: uptime,
407
+ total_requests: request_count,
408
+ active_tools: @server.tools.keys.count,
409
+ active_resources: @server.resources.keys.count
410
+ }
411
+ end
412
+
413
+ def system_metrics
414
+ {
415
+ platform: RUBY_PLATFORM,
416
+ ruby_version: RUBY_VERSION,
417
+ cpu_count: cpu_count,
418
+ total_memory: total_memory,
419
+ load_average: load_average
420
+ }
421
+ end
422
+
423
+ def process_metrics
424
+ {
425
+ pid: Process.pid,
426
+ memory_usage_mb: process_memory,
427
+ cpu_usage_percent: process_cpu,
428
+ thread_count: Thread.list.count,
429
+ gc_stats: GC.stat
430
+ }
431
+ end
432
+
433
+ def custom_metrics
434
+ @server.respond_to?(:custom_metrics) ? @server.custom_metrics : {}
435
+ end
436
+
437
+ def uptime
438
+ @start_time ? (Time.now - @start_time).to_i : 0
439
+ end
440
+
441
+ def request_count
442
+ @server.respond_to?(:request_count) ? @server.request_count : 0
443
+ end
444
+
445
+ def cpu_count
446
+ @metrics_cache[:cpu_count] ||= begin
447
+ if RUBY_PLATFORM =~ /linux/
448
+ `nproc`.to_i
449
+ elsif RUBY_PLATFORM =~ /darwin/
450
+ `sysctl -n hw.ncpu`.to_i
451
+ else
452
+ 1
453
+ end
454
+ end
455
+ end
456
+
457
+ def total_memory
458
+ @metrics_cache[:total_memory] ||= begin
459
+ if RUBY_PLATFORM =~ /linux/
460
+ `grep MemTotal /proc/meminfo`.match(/\d+/)[0].to_i / 1024
461
+ elsif RUBY_PLATFORM =~ /darwin/
462
+ `sysctl -n hw.memsize`.to_i / 1024 / 1024
463
+ else
464
+ 0
465
+ end
466
+ end
467
+ end
468
+
469
+ def load_average
470
+ if File.exist?("/proc/loadavg")
471
+ File.read("/proc/loadavg").split.first(3).map(&:to_f)
472
+ else
473
+ [0.0, 0.0, 0.0]
474
+ end
475
+ end
476
+
477
+ def process_memory
478
+ rss = `ps -o rss= -p #{Process.pid}`.to_i
479
+ (rss / 1024.0).round(2)
480
+ end
481
+
482
+ def process_cpu
483
+ # Simple CPU calculation
484
+ now = Time.now
485
+ if now - @last_cpu_check > 1
486
+ # Simplified CPU usage
487
+ @last_cpu_value = rand(0..20)
488
+ @last_cpu_check = now
489
+ end
490
+ @last_cpu_value
491
+ end
492
+
493
+ def minimal_metrics
494
+ {
495
+ error: "Metrics collection failed",
496
+ server_name: @server.name,
497
+ timestamp: Time.now.iso8601
498
+ }
499
+ end
500
+
501
+ def set_server(server)
502
+ @server = server
503
+ @start_time = Time.now
504
+
505
+ if @server.logging_enabled?
506
+ define_singleton_method(:log) do |level, message, data: nil|
507
+ @server.log(level, message, data: data)
508
+ end
509
+ else
510
+ define_singleton_method(:log) do |*args|
511
+ # No-op
512
+ end
513
+ end
514
+ end
515
+ end
516
+ ```
517
+
518
+ ## Testing Resources
519
+
520
+ ```ruby
521
+ require 'minitest/autorun'
522
+ require 'tsikol/test_helpers'
523
+
524
+ class SystemMetricsTest < Minitest::Test
525
+ include Tsikol::TestHelpers::Assertions
526
+
527
+ def setup
528
+ @server = Tsikol::Server.new(name: "test")
529
+ @server.logging true
530
+ @server.register_resource_instance(SystemMetrics.new)
531
+ @client = Tsikol::TestHelpers::TestClient.new(@server)
532
+ @client.initialize_connection
533
+ end
534
+
535
+ def test_read_metrics
536
+ response = @client.read_resource("system/metrics")
537
+
538
+ assert_successful_response(response)
539
+
540
+ content = response.dig(:result, :contents, 0, :text)
541
+ data = JSON.parse(content)
542
+
543
+ assert data["server"]
544
+ assert data["system"]
545
+ assert data["process"]
546
+ assert data["timestamp"]
547
+ end
548
+
549
+ def test_metrics_structure
550
+ response = @client.read_resource("system/metrics")
551
+ content = response.dig(:result, :contents, 0, :text)
552
+ data = JSON.parse(content)
553
+
554
+ # Verify server metrics
555
+ assert_equal "test", data["server"]["name"]
556
+ assert data["server"]["uptime_seconds"] >= 0
557
+
558
+ # Verify system metrics
559
+ assert data["system"]["ruby_version"]
560
+ assert data["system"]["platform"]
561
+
562
+ # Verify process metrics
563
+ assert_equal Process.pid, data["process"]["pid"]
564
+ assert data["process"]["memory_usage_mb"] > 0
565
+ end
566
+
567
+ def test_error_handling
568
+ # Mock an error condition
569
+ resource = SystemMetrics.new
570
+ resource.define_singleton_method(:system_metrics) do
571
+ raise "Simulated error"
572
+ end
573
+
574
+ @server.register_resource_instance(resource)
575
+
576
+ response = @client.read_resource("system/metrics")
577
+
578
+ # Should still return valid JSON
579
+ assert_successful_response(response)
580
+ content = response.dig(:result, :contents, 0, :text)
581
+ data = JSON.parse(content)
582
+
583
+ # Should have minimal metrics on error
584
+ assert data["error"] || data["server"]
585
+ end
586
+ end
587
+ ```
588
+
589
+ ## URI Patterns and Best Practices
590
+
591
+ ### Hierarchical Organization
592
+
593
+ ```ruby
594
+ # Good: Clear hierarchy
595
+ "config" # Top-level config
596
+ "config/database" # Database config
597
+ "config/cache" # Cache config
598
+ "users/current" # Current user
599
+ "users/current/settings" # User settings
600
+
601
+ # Avoid: Flat structure
602
+ "database-config"
603
+ "cache-config"
604
+ "current-user"
605
+ "user-settings"
606
+ ```
607
+
608
+ ### Naming Conventions
609
+
610
+ ```ruby
611
+ # Use lowercase with hyphens or underscores
612
+ "system/health-check" # Good
613
+ "system/health_check" # Good
614
+ "system/HealthCheck" # Avoid
615
+ "SYSTEM/HEALTH" # Avoid
616
+
617
+ # Be descriptive but concise
618
+ "api/endpoints" # Good
619
+ "api/available-endpoints-list" # Too verbose
620
+ ```
621
+
622
+ ### Resource Types
623
+
624
+ ```ruby
625
+ # Status/Info resources
626
+ "system/status"
627
+ "server/info"
628
+ "health/check"
629
+
630
+ # Configuration resources
631
+ "config/app"
632
+ "config/features"
633
+ "settings/user"
634
+
635
+ # Data resources
636
+ "data/users"
637
+ "reports/daily"
638
+ "metrics/performance"
639
+
640
+ # Documentation resources
641
+ "docs/api"
642
+ "help/commands"
643
+ "schema/database"
644
+ ```
645
+
646
+ ## See Also
647
+
648
+ - [Resources Guide](../guides/resources.md)
649
+ - [Server API](server.md)
650
+ - [Testing Guide](../guides/testing.md)
651
+ - [Caching Patterns](../cookbook/caching.md)