flow_chat 0.8.2 → 0.9.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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/.cliff.toml +74 -0
  3. data/.github/workflows/ci.yml +2 -3
  4. data/.github/workflows/release.yml +56 -0
  5. data/.standard.yml +4 -0
  6. data/CHANGELOG.md +22 -0
  7. data/CLAUDE.md +327 -0
  8. data/CONTRIBUTING.md +134 -0
  9. data/Gemfile +1 -0
  10. data/README.md +290 -105
  11. data/Rakefile +5 -1
  12. data/SECURITY.md +42 -349
  13. data/docs/architecture.md +510 -0
  14. data/docs/async-background-processing.md +298 -0
  15. data/docs/configuration.md +556 -226
  16. data/docs/factory-pattern.md +355 -0
  17. data/docs/gateway-context-variables.md +171 -0
  18. data/docs/gateway-development.md +723 -0
  19. data/docs/getting-started.md +429 -0
  20. data/docs/instrumentation.md +264 -153
  21. data/docs/platforms/telegram.md +1013 -0
  22. data/docs/platforms/ussd.md +693 -0
  23. data/docs/platforms/whatsapp.md +1395 -0
  24. data/docs/testing.md +243 -365
  25. data/examples/custom_session_id_example.rb +119 -0
  26. data/examples/http_controller.rb +11 -11
  27. data/examples/intercom_configuration_example.rb +118 -0
  28. data/examples/intercom_controller.rb +194 -0
  29. data/examples/multi_tenant_whatsapp_controller.rb +4 -4
  30. data/examples/ussd_controller.rb +9 -9
  31. data/examples/whatsapp_controller.rb +2 -2
  32. data/flow_chat.gemspec +4 -0
  33. data/lib/flow_chat/{base_app.rb → app.rb} +16 -9
  34. data/lib/flow_chat/async_job.rb +176 -0
  35. data/lib/flow_chat/config.rb +10 -30
  36. data/lib/flow_chat/{base_executor.rb → executor.rb} +6 -11
  37. data/lib/flow_chat/factory.rb +94 -0
  38. data/lib/flow_chat/gateway_async_support.rb +106 -0
  39. data/lib/flow_chat/generic_async_job.rb +30 -0
  40. data/lib/flow_chat/http/gateway/simple.rb +81 -33
  41. data/lib/flow_chat/http/renderer.rb +3 -3
  42. data/lib/flow_chat/instrumentation/setup.rb +1 -1
  43. data/lib/flow_chat/instrumentation.rb +23 -0
  44. data/lib/flow_chat/intercom/client.rb +155 -0
  45. data/lib/flow_chat/intercom/configuration.rb +149 -0
  46. data/lib/flow_chat/intercom/gateway/intercom_api.rb +396 -0
  47. data/lib/flow_chat/intercom/renderer.rb +71 -0
  48. data/lib/flow_chat/phone_number_util.rb +37 -35
  49. data/lib/flow_chat/processor.rb +188 -0
  50. data/lib/flow_chat/renderers/markdown_support.rb +58 -0
  51. data/lib/flow_chat/session/middleware.rb +25 -9
  52. data/lib/flow_chat/telegram/client.rb +240 -0
  53. data/lib/flow_chat/telegram/configuration.rb +118 -0
  54. data/lib/flow_chat/telegram/gateway/bot_api.rb +300 -0
  55. data/lib/flow_chat/telegram/middleware/choice_mapper.rb +35 -0
  56. data/lib/flow_chat/telegram/renderer.rb +125 -0
  57. data/lib/flow_chat/telegram.rb +7 -0
  58. data/lib/flow_chat/ussd/gateway/nalo.rb +24 -4
  59. data/lib/flow_chat/ussd/middleware/pagination.rb +9 -5
  60. data/lib/flow_chat/ussd/renderer.rb +1 -1
  61. data/lib/flow_chat/version.rb +1 -1
  62. data/lib/flow_chat/whatsapp/client.rb +144 -13
  63. data/lib/flow_chat/whatsapp/configuration.rb +1 -1
  64. data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +132 -96
  65. data/lib/flow_chat/whatsapp/id_generator.rb +124 -0
  66. data/lib/flow_chat/whatsapp/middleware/choice_mapper.rb +147 -0
  67. data/lib/flow_chat/whatsapp/renderer.rb +145 -11
  68. data/lib/flow_chat.rb +11 -1
  69. data/lib/tasks/release.rake +165 -0
  70. metadata +84 -25
  71. data/docs/flows.md +0 -320
  72. data/docs/http-gateway-protocol.md +0 -432
  73. data/docs/images/simulator.png +0 -0
  74. data/docs/media.md +0 -153
  75. data/docs/sessions.md +0 -433
  76. data/docs/ussd-setup.md +0 -322
  77. data/docs/whatsapp-setup.md +0 -162
  78. data/examples/whatsapp_message_job.rb +0 -113
  79. data/lib/flow_chat/base_processor.rb +0 -146
  80. data/lib/flow_chat/http/app.rb +0 -6
  81. data/lib/flow_chat/http/middleware/executor.rb +0 -24
  82. data/lib/flow_chat/http/processor.rb +0 -33
  83. data/lib/flow_chat/session/rails_session_store.rb +0 -68
  84. data/lib/flow_chat/ussd/app.rb +0 -6
  85. data/lib/flow_chat/ussd/gateway/nsano.rb +0 -96
  86. data/lib/flow_chat/ussd/middleware/executor.rb +0 -24
  87. data/lib/flow_chat/ussd/processor.rb +0 -39
  88. data/lib/flow_chat/whatsapp/app.rb +0 -29
  89. data/lib/flow_chat/whatsapp/middleware/executor.rb +0 -24
  90. data/lib/flow_chat/whatsapp/processor.rb +0 -32
  91. data/lib/flow_chat/whatsapp/send_job_support.rb +0 -79
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flow_chat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.2
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Froelich
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-06-28 00:00:00.000000000 Z
11
+ date: 2026-07-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: zeitwerk
@@ -80,6 +80,48 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: 0.4.2
83
+ - !ruby/object:Gem::Dependency
84
+ name: intercom
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '4.2'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '4.2'
97
+ - !ruby/object:Gem::Dependency
98
+ name: reverse_markdown
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: kramdown
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '2.4'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '2.4'
83
125
  description: "FlowChat is a Rails framework for building sophisticated conversational
84
126
  interfaces across USSD and WhatsApp platforms. \nCreate interactive flows with menus,
85
127
  prompts, validation, media support, and session management. Features include \nmulti-tenancy,
@@ -92,9 +134,15 @@ extensions: []
92
134
  extra_rdoc_files: []
93
135
  files:
94
136
  - ".DS_Store"
137
+ - ".cliff.toml"
95
138
  - ".github/workflows/ci.yml"
139
+ - ".github/workflows/release.yml"
96
140
  - ".gitignore"
97
141
  - ".ruby-version"
142
+ - ".standard.yml"
143
+ - CHANGELOG.md
144
+ - CLAUDE.md
145
+ - CONTRIBUTING.md
98
146
  - Gemfile
99
147
  - LICENSE.txt
100
148
  - README.md
@@ -102,71 +150,82 @@ files:
102
150
  - SECURITY.md
103
151
  - bin/console
104
152
  - bin/setup
153
+ - docs/architecture.md
154
+ - docs/async-background-processing.md
105
155
  - docs/configuration.md
106
- - docs/flows.md
107
- - docs/http-gateway-protocol.md
108
- - docs/images/simulator.png
156
+ - docs/factory-pattern.md
157
+ - docs/gateway-context-variables.md
158
+ - docs/gateway-development.md
159
+ - docs/getting-started.md
109
160
  - docs/instrumentation.md
110
- - docs/media.md
111
- - docs/sessions.md
161
+ - docs/platforms/telegram.md
162
+ - docs/platforms/ussd.md
163
+ - docs/platforms/whatsapp.md
112
164
  - docs/testing.md
113
- - docs/ussd-setup.md
114
- - docs/whatsapp-setup.md
165
+ - examples/custom_session_id_example.rb
115
166
  - examples/http_controller.rb
167
+ - examples/intercom_configuration_example.rb
168
+ - examples/intercom_controller.rb
116
169
  - examples/multi_tenant_whatsapp_controller.rb
117
170
  - examples/simulator_controller.rb
118
171
  - examples/ussd_controller.rb
119
172
  - examples/whatsapp_controller.rb
120
173
  - examples/whatsapp_media_examples.rb
121
- - examples/whatsapp_message_job.rb
122
174
  - flow_chat.gemspec
123
175
  - lib/flow_chat.rb
124
- - lib/flow_chat/base_app.rb
125
- - lib/flow_chat/base_executor.rb
126
- - lib/flow_chat/base_processor.rb
176
+ - lib/flow_chat/app.rb
177
+ - lib/flow_chat/async_job.rb
127
178
  - lib/flow_chat/config.rb
128
179
  - lib/flow_chat/context.rb
180
+ - lib/flow_chat/executor.rb
181
+ - lib/flow_chat/factory.rb
129
182
  - lib/flow_chat/flow.rb
130
- - lib/flow_chat/http/app.rb
183
+ - lib/flow_chat/gateway_async_support.rb
184
+ - lib/flow_chat/generic_async_job.rb
131
185
  - lib/flow_chat/http/gateway/simple.rb
132
- - lib/flow_chat/http/middleware/executor.rb
133
- - lib/flow_chat/http/processor.rb
134
186
  - lib/flow_chat/http/renderer.rb
135
187
  - lib/flow_chat/instrumentation.rb
136
188
  - lib/flow_chat/instrumentation/log_subscriber.rb
137
189
  - lib/flow_chat/instrumentation/metrics_collector.rb
138
190
  - lib/flow_chat/instrumentation/setup.rb
191
+ - lib/flow_chat/intercom/client.rb
192
+ - lib/flow_chat/intercom/configuration.rb
193
+ - lib/flow_chat/intercom/gateway/intercom_api.rb
194
+ - lib/flow_chat/intercom/renderer.rb
139
195
  - lib/flow_chat/interrupt.rb
140
196
  - lib/flow_chat/phone_number_util.rb
197
+ - lib/flow_chat/processor.rb
141
198
  - lib/flow_chat/prompt.rb
199
+ - lib/flow_chat/renderers/markdown_support.rb
142
200
  - lib/flow_chat/session/cache_session_store.rb
143
201
  - lib/flow_chat/session/middleware.rb
144
- - lib/flow_chat/session/rails_session_store.rb
145
202
  - lib/flow_chat/simulator/controller.rb
146
203
  - lib/flow_chat/simulator/views/simulator.html.erb
147
- - lib/flow_chat/ussd/app.rb
204
+ - lib/flow_chat/telegram.rb
205
+ - lib/flow_chat/telegram/client.rb
206
+ - lib/flow_chat/telegram/configuration.rb
207
+ - lib/flow_chat/telegram/gateway/bot_api.rb
208
+ - lib/flow_chat/telegram/middleware/choice_mapper.rb
209
+ - lib/flow_chat/telegram/renderer.rb
148
210
  - lib/flow_chat/ussd/gateway/nalo.rb
149
- - lib/flow_chat/ussd/gateway/nsano.rb
150
211
  - lib/flow_chat/ussd/middleware/choice_mapper.rb
151
- - lib/flow_chat/ussd/middleware/executor.rb
152
212
  - lib/flow_chat/ussd/middleware/pagination.rb
153
- - lib/flow_chat/ussd/processor.rb
154
213
  - lib/flow_chat/ussd/renderer.rb
155
214
  - lib/flow_chat/version.rb
156
- - lib/flow_chat/whatsapp/app.rb
157
215
  - lib/flow_chat/whatsapp/client.rb
158
216
  - lib/flow_chat/whatsapp/configuration.rb
159
217
  - lib/flow_chat/whatsapp/gateway/cloud_api.rb
160
- - lib/flow_chat/whatsapp/middleware/executor.rb
161
- - lib/flow_chat/whatsapp/processor.rb
218
+ - lib/flow_chat/whatsapp/id_generator.rb
219
+ - lib/flow_chat/whatsapp/middleware/choice_mapper.rb
162
220
  - lib/flow_chat/whatsapp/renderer.rb
163
- - lib/flow_chat/whatsapp/send_job_support.rb
164
221
  - lib/flow_chat/whatsapp/template_manager.rb
222
+ - lib/tasks/release.rake
165
223
  homepage: https://github.com/radioactive-labs/flow_chat
166
224
  licenses:
167
225
  - MIT
168
226
  metadata:
169
227
  allowed_push_host: https://rubygems.org
228
+ rubygems_mfa_required: 'true'
170
229
  homepage_uri: https://github.com/radioactive-labs/flow_chat
171
230
  source_code_uri: https://github.com/radioactive-labs/flow_chat
172
231
  changelog_uri: https://github.com/radioactive-labs/flow_chat
data/docs/flows.md DELETED
@@ -1,320 +0,0 @@
1
- # Flow Development Guide
2
-
3
- This guide covers advanced flow patterns, validation techniques, and best practices for building sophisticated conversational workflows.
4
-
5
- ## Flow Architecture
6
-
7
- ### Flow Lifecycle
8
-
9
- Every flow method must result in user interaction:
10
-
11
- ```ruby
12
- class ExampleFlow < FlowChat::Flow
13
- def main_page
14
- # ✅ Always end with user interaction
15
- choice = app.screen(:choice) { |p| p.select "Choose:", ["A", "B"] }
16
-
17
- case choice
18
- when "A"
19
- handle_option_a
20
- app.say "Option A completed!" # Required interaction
21
- when "B"
22
- handle_option_b
23
- app.say "Option B completed!" # Required interaction
24
- end
25
- end
26
- end
27
- ```
28
-
29
- ### Session Management
30
-
31
- FlowChat automatically persists screen results:
32
-
33
- ```ruby
34
- class RegistrationFlow < FlowChat::Flow
35
- def main_page
36
- # These values persist across requests
37
- name = app.screen(:name) { |p| p.ask "Name?" }
38
- email = app.screen(:email) { |p| p.ask "Email?" }
39
-
40
- # Show summary using cached values
41
- confirmed = app.screen(:confirm) do |prompt|
42
- prompt.yes? "Create account for #{name} (#{email})?"
43
- end
44
-
45
- if confirmed
46
- create_user(name: name, email: email)
47
- app.say "Account created!"
48
- end
49
- end
50
- end
51
- ```
52
-
53
- ## Input Validation Patterns
54
-
55
- ### Basic Validation
56
-
57
- ```ruby
58
- age = app.screen(:age) do |prompt|
59
- prompt.ask "Enter your age:",
60
- validate: ->(input) {
61
- return "Age must be a number" unless input.match?(/^\d+$/)
62
- return "Must be 18 or older" unless input.to_i >= 18
63
- nil # Return nil for valid input
64
- },
65
- transform: ->(input) { input.to_i }
66
- end
67
- ```
68
-
69
- ### Complex Validation
70
-
71
- ```ruby
72
- phone = app.screen(:phone) do |prompt|
73
- prompt.ask "Enter phone number:",
74
- validate: ->(input) {
75
- clean = input.gsub(/[\s\-\(\)]/, '')
76
- return "Invalid format" unless clean.match?(/^\+?[\d]{10,15}$/)
77
- return "Must start with country code" unless clean.start_with?('+')
78
- nil
79
- },
80
- transform: ->(input) { input.gsub(/[\s\-\(\)]/, '') }
81
- end
82
- ```
83
-
84
- ### Conditional Validation
85
-
86
- ```ruby
87
- class PaymentFlow < FlowChat::Flow
88
- def collect_payment_method
89
- method = app.screen(:method) do |prompt|
90
- prompt.select "Payment method:", ["card", "mobile_money"]
91
- end
92
-
93
- if method == "card"
94
- collect_card_details
95
- else
96
- collect_mobile_money_details
97
- end
98
- end
99
-
100
- private
101
-
102
- def collect_card_details
103
- card = app.screen(:card) do |prompt|
104
- prompt.ask "Card number (16 digits):",
105
- validate: ->(input) {
106
- clean = input.gsub(/\s/, '')
107
- return "Must be 16 digits" unless clean.length == 16
108
- return "Invalid card number" unless luhn_valid?(clean)
109
- nil
110
- }
111
- end
112
-
113
- app.say "Card ending in #{card[-4..-1]} saved."
114
- end
115
- end
116
- ```
117
-
118
- ## Menu Patterns
119
-
120
- ### Dynamic Menus
121
-
122
- ```ruby
123
- def show_products
124
- products = fetch_available_products
125
-
126
- choice = app.screen(:product) do |prompt|
127
- prompt.select "Choose product:", products.map(&:name)
128
- end
129
-
130
- selected_product = products.find { |p| p.name == choice }
131
- show_product_details(selected_product)
132
- end
133
- ```
134
-
135
- ### Nested Menus
136
-
137
- ```ruby
138
- def main_menu
139
- choice = app.screen(:main) do |prompt|
140
- prompt.select "Main Menu:", {
141
- "products" => "View Products",
142
- "orders" => "My Orders",
143
- "support" => "Customer Support"
144
- }
145
- end
146
-
147
- case choice
148
- when "products"
149
- products_menu
150
- when "orders"
151
- orders_menu
152
- when "support"
153
- support_menu
154
- end
155
- end
156
- ```
157
-
158
- ## Advanced Patterns
159
-
160
- ### Multi-Step Forms
161
-
162
- ```ruby
163
- class CompleteProfileFlow < FlowChat::Flow
164
- def main_page
165
- collect_basic_info
166
- collect_preferences
167
- confirm_and_save
168
- end
169
-
170
- private
171
-
172
- def collect_basic_info
173
- app.screen(:name) { |p| p.ask "Full name:" }
174
- app.screen(:email) { |p| p.ask "Email:" }
175
- app.screen(:phone) { |p| p.ask "Phone:" }
176
- end
177
-
178
- def collect_preferences
179
- app.screen(:language) { |p| p.select "Language:", ["English", "French"] }
180
- app.screen(:notifications) { |p| p.yes? "Enable notifications?" }
181
- end
182
-
183
- def confirm_and_save
184
- summary = build_summary
185
- confirmed = app.screen(:confirm) { |p| p.yes? "Save profile?\n\n#{summary}" }
186
-
187
- if confirmed
188
- save_profile
189
- app.say "Profile saved successfully!"
190
- else
191
- app.say "Profile not saved."
192
- end
193
- end
194
- end
195
- ```
196
-
197
- ### Error Recovery
198
-
199
- ```ruby
200
- def process_payment
201
- begin
202
- amount = app.screen(:amount) do |prompt|
203
- prompt.ask "Amount to pay:",
204
- validate: ->(input) {
205
- return "Invalid amount" unless input.match?(/^\d+(\.\d{2})?$/)
206
- return "Minimum $1.00" unless input.to_f >= 1.0
207
- nil
208
- },
209
- transform: ->(input) { input.to_f }
210
- end
211
-
212
- process_transaction(amount)
213
- app.say "Payment of $#{amount} processed successfully!"
214
-
215
- rescue PaymentError => e
216
- app.say "Payment failed: #{e.message}. Please try again."
217
- process_payment # Retry
218
- end
219
- end
220
- ```
221
-
222
- ## Cross-Platform Considerations
223
-
224
- ### Platform Detection
225
-
226
- ```ruby
227
- def show_help
228
- if app.context["request.gateway"] == :whatsapp_cloud_api
229
- # WhatsApp users get rich media
230
- app.say "Here's how to use our service:",
231
- media: { type: :image, url: "https://example.com/help.jpg" }
232
- else
233
- # USSD users get text with link
234
- app.say "Help guide: https://example.com/help"
235
- end
236
- end
237
- ```
238
-
239
- ### Progressive Enhancement
240
-
241
- ```ruby
242
- def collect_feedback
243
- rating = app.screen(:rating) do |prompt|
244
- if whatsapp?
245
- # Rich interactive buttons for WhatsApp
246
- prompt.select "Rate our service:", {
247
- "5" => "⭐⭐⭐⭐⭐ Excellent",
248
- "4" => "⭐⭐⭐⭐ Good",
249
- "3" => "⭐⭐⭐ Average",
250
- "2" => "⭐⭐ Poor",
251
- "1" => "⭐ Very Poor"
252
- }
253
- else
254
- # Simple numbered list for USSD
255
- prompt.select "Rate our service (1-5):", ["1", "2", "3", "4", "5"]
256
- end
257
- end
258
-
259
- app.say "Thank you for rating us #{rating} stars!"
260
- end
261
-
262
- private
263
-
264
- def whatsapp?
265
- app.context["request.gateway"] == :whatsapp_cloud_api
266
- end
267
- ```
268
-
269
- ## Best Practices
270
-
271
- ### Keep Methods Focused
272
-
273
- ```ruby
274
- # ✅ Good: Single responsibility
275
- def collect_contact_info
276
- name = app.screen(:name) { |p| p.ask "Name:" }
277
- email = app.screen(:email) { |p| p.ask "Email:" }
278
- { name: name, email: email }
279
- end
280
-
281
- # ❌ Avoid: Too much in one method
282
- def handle_everything
283
- # 50+ lines of mixed logic
284
- end
285
- ```
286
-
287
- ### Use Meaningful Screen Names
288
-
289
- ```ruby
290
- # ✅ Good: Descriptive names
291
- app.screen(:billing_address) { |p| p.ask "Billing address:" }
292
- app.screen(:confirm_payment) { |p| p.yes? "Confirm $#{amount}?" }
293
-
294
- # ❌ Avoid: Generic names
295
- app.screen(:input1) { |p| p.ask "Address:" }
296
- app.screen(:confirm) { |p| p.yes? "OK?" }
297
- ```
298
-
299
- ### Handle Edge Cases
300
-
301
- ```ruby
302
- def show_order_history
303
- orders = fetch_user_orders
304
-
305
- if orders.empty?
306
- app.say "You have no previous orders."
307
- return
308
- end
309
-
310
- choice = app.screen(:order) do |prompt|
311
- prompt.select "Select order:", orders.map(&:display_name)
312
- end
313
-
314
- show_order_details(orders.find { |o| o.display_name == choice })
315
- end
316
- ```
317
-
318
- ## Testing Flows
319
-
320
- See [Testing Guide](testing.md) for comprehensive testing strategies.