skald 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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +620 -0
- data/lib/skald.rb +335 -0
- metadata +106 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 914d138d114eae0c785f900d311427ff2967b6cddae2fc3a47fa4e8307d1c5a9
|
|
4
|
+
data.tar.gz: c394e54190914540797834c4587e10b1d335d481d01450ee4362ad54322b851d
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 7c964cda5f764004bd9b8c2f5d2ce40708d1a164ae684bc94c7a7ba235a2783656cd58571d073ee99049ed0f3c6a2416cb1af6defaa1cd25da9f3661f70d6b65
|
|
7
|
+
data.tar.gz: 369bb564e34ee643b0fb9bd11593f0c583f7526269a8917415d71d58631202f210a9ad4af1ec807b8c68e44aab7b196b5a130c09f53452126ae6848b2ceef0a0
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Skald
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
# Skald Ruby SDK
|
|
2
|
+
|
|
3
|
+
Ruby client library for [Skald](https://useskald.com) - an API platform that allows you to easily push context/knowledge via our API (in the form of memos) and get access to chat, document generation, and semantic search out-of-the-box.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem 'skald'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
And then execute:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bundle install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or install it yourself as:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
gem install skald
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Requirements
|
|
26
|
+
|
|
27
|
+
- Ruby 3.0.0 or higher
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
require 'skald'
|
|
33
|
+
|
|
34
|
+
# Initialize the client
|
|
35
|
+
client = Skald.new('your-api-key')
|
|
36
|
+
|
|
37
|
+
# Create a memo
|
|
38
|
+
client.create_memo(
|
|
39
|
+
title: "Meeting Notes",
|
|
40
|
+
content: "Discussed project timeline..."
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Search your memos
|
|
44
|
+
results = client.search(
|
|
45
|
+
query: "project timeline",
|
|
46
|
+
search_method: "chunk_vector_search"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Chat with your memos
|
|
50
|
+
response = client.chat(query: "What were the main discussion points?")
|
|
51
|
+
puts response[:response]
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Initialization
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
# Default base URL (https://api.useskald.com)
|
|
58
|
+
client = Skald.new('your-api-key')
|
|
59
|
+
|
|
60
|
+
# Custom base URL (for self-hosted or testing)
|
|
61
|
+
client = Skald.new('your-api-key', 'https://custom.api.com')
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Memo Management
|
|
65
|
+
|
|
66
|
+
### Create Memo
|
|
67
|
+
|
|
68
|
+
Create a new memo with content and optional metadata.
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
result = client.create_memo(
|
|
72
|
+
title: "Project Planning Meeting",
|
|
73
|
+
content: "Discussed Q1 goals, resource allocation, and timeline...",
|
|
74
|
+
metadata: { priority: "high", department: "engineering" },
|
|
75
|
+
tags: ["meeting", "planning"],
|
|
76
|
+
source: "notion",
|
|
77
|
+
reference_id: "notion-page-123",
|
|
78
|
+
expiration_date: "2025-12-31T23:59:59Z"
|
|
79
|
+
)
|
|
80
|
+
# => { ok: true }
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Parameters:**
|
|
84
|
+
- `title` (String, required): Memo title (max 255 characters)
|
|
85
|
+
- `content` (String, required): Memo content
|
|
86
|
+
- `metadata` (Hash, optional): Custom JSON metadata
|
|
87
|
+
- `tags` (Array<String>, optional): Array of tags
|
|
88
|
+
- `source` (String, optional): Source system (e.g., "notion", "slack", max 255 chars)
|
|
89
|
+
- `reference_id` (String, optional): External reference ID (max 255 chars)
|
|
90
|
+
- `expiration_date` (String, optional): ISO 8601 expiration timestamp
|
|
91
|
+
|
|
92
|
+
**Response:**
|
|
93
|
+
```ruby
|
|
94
|
+
{ ok: true }
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Get Memo
|
|
98
|
+
|
|
99
|
+
Retrieve a memo by UUID or reference ID.
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
# Get by UUID
|
|
103
|
+
memo = client.get_memo("550e8400-e29b-41d4-a716-446655440000")
|
|
104
|
+
|
|
105
|
+
# Get by reference ID
|
|
106
|
+
memo = client.get_memo("notion-page-123", "reference_id")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Parameters:**
|
|
110
|
+
- `memo_id` (String, required): Memo UUID or reference ID
|
|
111
|
+
- `id_type` (String, optional): `"memo_uuid"` (default) or `"reference_id"`
|
|
112
|
+
|
|
113
|
+
**Response:**
|
|
114
|
+
```ruby
|
|
115
|
+
{
|
|
116
|
+
uuid: "550e8400-e29b-41d4-a716-446655440000",
|
|
117
|
+
created_at: "2025-01-15T10:30:00Z",
|
|
118
|
+
updated_at: "2025-01-15T10:30:00Z",
|
|
119
|
+
title: "Project Planning Meeting",
|
|
120
|
+
content: "Discussed Q1 goals...",
|
|
121
|
+
summary: "AI-generated summary of the memo",
|
|
122
|
+
content_length: 150,
|
|
123
|
+
metadata: { priority: "high" },
|
|
124
|
+
client_reference_id: "notion-page-123",
|
|
125
|
+
source: "notion",
|
|
126
|
+
type: "memo",
|
|
127
|
+
expiration_date: nil,
|
|
128
|
+
archived: false,
|
|
129
|
+
pending: false,
|
|
130
|
+
tags: [
|
|
131
|
+
{ uuid: "tag-uuid", tag: "meeting" }
|
|
132
|
+
],
|
|
133
|
+
chunks: [
|
|
134
|
+
{ uuid: "chunk-uuid", chunk_content: "...", chunk_index: 0 }
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### List Memos
|
|
140
|
+
|
|
141
|
+
List all memos with pagination.
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
# Default pagination (page 1, 20 items)
|
|
145
|
+
response = client.list_memos
|
|
146
|
+
|
|
147
|
+
# Custom pagination
|
|
148
|
+
response = client.list_memos(page: 2, page_size: 50)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Parameters:**
|
|
152
|
+
- `page` (Integer, optional): Page number (default: 1)
|
|
153
|
+
- `page_size` (Integer, optional): Results per page (default: 20, max: 100)
|
|
154
|
+
|
|
155
|
+
**Response:**
|
|
156
|
+
```ruby
|
|
157
|
+
{
|
|
158
|
+
count: 150,
|
|
159
|
+
next: "https://api.useskald.com/api/v1/memo?page=2",
|
|
160
|
+
previous: nil,
|
|
161
|
+
results: [
|
|
162
|
+
{
|
|
163
|
+
uuid: "...",
|
|
164
|
+
title: "Memo 1",
|
|
165
|
+
summary: "...",
|
|
166
|
+
# ... other memo fields
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Update Memo
|
|
173
|
+
|
|
174
|
+
Update an existing memo. Updating `content` triggers reprocessing.
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
# Update by UUID
|
|
178
|
+
client.update_memo(
|
|
179
|
+
"550e8400-e29b-41d4-a716-446655440000",
|
|
180
|
+
{
|
|
181
|
+
title: "Updated Title",
|
|
182
|
+
metadata: { priority: "medium", status: "reviewed" }
|
|
183
|
+
}
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# Update by reference ID
|
|
187
|
+
client.update_memo(
|
|
188
|
+
"notion-page-123",
|
|
189
|
+
{ content: "New content..." },
|
|
190
|
+
"reference_id"
|
|
191
|
+
)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Parameters:**
|
|
195
|
+
- `memo_id` (String, required): Memo UUID or reference ID
|
|
196
|
+
- `update_data` (Hash, required): Fields to update
|
|
197
|
+
- `title` (String, optional): New title
|
|
198
|
+
- `content` (String, optional): New content (triggers reprocessing)
|
|
199
|
+
- `metadata` (Hash, optional): New metadata
|
|
200
|
+
- `client_reference_id` (String, optional): New reference ID
|
|
201
|
+
- `source` (String, optional): New source
|
|
202
|
+
- `expiration_date` (String, optional): New expiration date
|
|
203
|
+
- `id_type` (String, optional): `"memo_uuid"` (default) or `"reference_id"`
|
|
204
|
+
|
|
205
|
+
**Response:**
|
|
206
|
+
```ruby
|
|
207
|
+
{ ok: true }
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Delete Memo
|
|
211
|
+
|
|
212
|
+
Permanently delete a memo and all associated data.
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
# Delete by UUID
|
|
216
|
+
client.delete_memo("550e8400-e29b-41d4-a716-446655440000")
|
|
217
|
+
|
|
218
|
+
# Delete by reference ID
|
|
219
|
+
client.delete_memo("notion-page-123", "reference_id")
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Parameters:**
|
|
223
|
+
- `memo_id` (String, required): Memo UUID or reference ID
|
|
224
|
+
- `id_type` (String, optional): `"memo_uuid"` (default) or `"reference_id"`
|
|
225
|
+
|
|
226
|
+
**Response:**
|
|
227
|
+
```ruby
|
|
228
|
+
nil
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Search
|
|
232
|
+
|
|
233
|
+
Search your memos using semantic search or title matching.
|
|
234
|
+
|
|
235
|
+
### Search Methods
|
|
236
|
+
|
|
237
|
+
1. **`chunk_vector_search`**: Semantic/vector search on memo content chunks
|
|
238
|
+
2. **`title_contains`**: Case-insensitive substring match on titles
|
|
239
|
+
3. **`title_startswith`**: Case-insensitive prefix match on titles
|
|
240
|
+
|
|
241
|
+
### Semantic Search
|
|
242
|
+
|
|
243
|
+
```ruby
|
|
244
|
+
results = client.search(
|
|
245
|
+
query: "project timeline and milestones",
|
|
246
|
+
search_method: "chunk_vector_search",
|
|
247
|
+
limit: 20
|
|
248
|
+
)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Title Search
|
|
252
|
+
|
|
253
|
+
```ruby
|
|
254
|
+
# Contains
|
|
255
|
+
results = client.search(
|
|
256
|
+
query: "meeting",
|
|
257
|
+
search_method: "title_contains"
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
# Starts with
|
|
261
|
+
results = client.search(
|
|
262
|
+
query: "Project",
|
|
263
|
+
search_method: "title_startswith"
|
|
264
|
+
)
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**Parameters:**
|
|
268
|
+
- `query` (String, required): Search query
|
|
269
|
+
- `search_method` (String, required): One of `"chunk_vector_search"`, `"title_contains"`, or `"title_startswith"`
|
|
270
|
+
- `limit` (Integer, optional): Maximum results (1-50, default: 10)
|
|
271
|
+
- `filters` (Array<Hash>, optional): Filters to apply (see [Filters](#filters))
|
|
272
|
+
|
|
273
|
+
**Response:**
|
|
274
|
+
```ruby
|
|
275
|
+
{
|
|
276
|
+
results: [
|
|
277
|
+
{
|
|
278
|
+
uuid: "550e8400-...",
|
|
279
|
+
title: "Project Planning Meeting",
|
|
280
|
+
summary: "Discussed Q1 goals...",
|
|
281
|
+
content_snippet: "...relevant excerpt from the content...",
|
|
282
|
+
distance: 0.45 # For vector search: 0-2, lower is better. nil for title searches
|
|
283
|
+
}
|
|
284
|
+
]
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Chat
|
|
289
|
+
|
|
290
|
+
Ask questions and get answers based on your memos with inline citations.
|
|
291
|
+
|
|
292
|
+
### Non-Streaming Chat
|
|
293
|
+
|
|
294
|
+
```ruby
|
|
295
|
+
response = client.chat(
|
|
296
|
+
query: "What are the main project goals for Q1?"
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
puts response[:response]
|
|
300
|
+
# => "The main goals are: 1) Launch MVP [[1]], 2) Hire team [[2]]..."
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**Parameters:**
|
|
304
|
+
- `query` (String, required): Question to ask
|
|
305
|
+
- `filters` (Array<Hash>, optional): Filters to apply (see [Filters](#filters))
|
|
306
|
+
|
|
307
|
+
**Response:**
|
|
308
|
+
```ruby
|
|
309
|
+
{
|
|
310
|
+
ok: true,
|
|
311
|
+
response: "Answer with inline citations [[1]], [[2]], etc.",
|
|
312
|
+
intermediate_steps: [] # For debugging
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Streaming Chat
|
|
317
|
+
|
|
318
|
+
```ruby
|
|
319
|
+
print "Answer: "
|
|
320
|
+
client.streamed_chat(
|
|
321
|
+
query: "Summarize the meeting notes from last week"
|
|
322
|
+
).each do |event|
|
|
323
|
+
if event[:type] == "token"
|
|
324
|
+
print event[:content]
|
|
325
|
+
elsif event[:type] == "done"
|
|
326
|
+
puts "\nDone!"
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**Parameters:**
|
|
332
|
+
- Same as non-streaming chat
|
|
333
|
+
|
|
334
|
+
**Yields:**
|
|
335
|
+
```ruby
|
|
336
|
+
{ type: "token", content: "Each" }
|
|
337
|
+
{ type: "token", content: " word" }
|
|
338
|
+
{ type: "done" }
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
## Document Generation
|
|
342
|
+
|
|
343
|
+
Generate documents based on your memos with optional style rules.
|
|
344
|
+
|
|
345
|
+
### Non-Streaming Generation
|
|
346
|
+
|
|
347
|
+
```ruby
|
|
348
|
+
response = client.generate_doc(
|
|
349
|
+
prompt: "Create a comprehensive project status report",
|
|
350
|
+
rules: "Use bullet points and maintain a professional tone"
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
puts response[:response]
|
|
354
|
+
# => "# Project Status Report\n\n## Overview\nThe project is on track [[1]]..."
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
**Parameters:**
|
|
358
|
+
- `prompt` (String, required): What document to generate
|
|
359
|
+
- `rules` (String, optional): Style/format rules
|
|
360
|
+
- `filters` (Array<Hash>, optional): Filters to apply (see [Filters](#filters))
|
|
361
|
+
|
|
362
|
+
**Response:**
|
|
363
|
+
```ruby
|
|
364
|
+
{
|
|
365
|
+
ok: true,
|
|
366
|
+
response: "Generated document with citations [[1]], [[2]]...",
|
|
367
|
+
intermediate_steps: []
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Streaming Generation
|
|
372
|
+
|
|
373
|
+
```ruby
|
|
374
|
+
puts "Generated Document:"
|
|
375
|
+
client.streamed_generate_doc(
|
|
376
|
+
prompt: "Write a technical architecture document",
|
|
377
|
+
rules: "Include diagrams descriptions and code examples"
|
|
378
|
+
).each do |event|
|
|
379
|
+
if event[:type] == "token"
|
|
380
|
+
print event[:content]
|
|
381
|
+
elsif event[:type] == "done"
|
|
382
|
+
puts "\nDone!"
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
**Parameters:**
|
|
388
|
+
- Same as non-streaming generation
|
|
389
|
+
|
|
390
|
+
**Yields:**
|
|
391
|
+
```ruby
|
|
392
|
+
{ type: "token", content: "Each" }
|
|
393
|
+
{ type: "token", content: " word" }
|
|
394
|
+
{ type: "done" }
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
## Filters
|
|
398
|
+
|
|
399
|
+
Filters allow you to narrow down which memos are used for search, chat, and document generation. Multiple filters use AND logic (all must match).
|
|
400
|
+
|
|
401
|
+
### Filter Structure
|
|
402
|
+
|
|
403
|
+
```ruby
|
|
404
|
+
{
|
|
405
|
+
field: "field_name",
|
|
406
|
+
operator: "eq",
|
|
407
|
+
value: "value",
|
|
408
|
+
filter_type: "native_field"
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Filter Types
|
|
413
|
+
|
|
414
|
+
1. **`native_field`**: Built-in memo properties
|
|
415
|
+
- `title`: Memo title
|
|
416
|
+
- `source`: Source system
|
|
417
|
+
- `client_reference_id`: Your reference ID
|
|
418
|
+
- `tags`: Memo tags
|
|
419
|
+
|
|
420
|
+
2. **`custom_metadata`**: User-defined metadata fields
|
|
421
|
+
|
|
422
|
+
### Filter Operators
|
|
423
|
+
|
|
424
|
+
| Operator | Description | Value Type |
|
|
425
|
+
|----------|-------------|------------|
|
|
426
|
+
| `eq` | Exact match | String |
|
|
427
|
+
| `neq` | Not equals | String |
|
|
428
|
+
| `contains` | Substring match (case-insensitive) | String |
|
|
429
|
+
| `startswith` | Prefix match (case-insensitive) | String |
|
|
430
|
+
| `endswith` | Suffix match (case-insensitive) | String |
|
|
431
|
+
| `in` | Value is in array | Array<String> |
|
|
432
|
+
| `not_in` | Value not in array | Array<String> |
|
|
433
|
+
|
|
434
|
+
### Examples
|
|
435
|
+
|
|
436
|
+
#### Filter by Native Field
|
|
437
|
+
|
|
438
|
+
```ruby
|
|
439
|
+
results = client.search(
|
|
440
|
+
query: "project update",
|
|
441
|
+
search_method: "chunk_vector_search",
|
|
442
|
+
filters: [
|
|
443
|
+
{
|
|
444
|
+
field: "source",
|
|
445
|
+
operator: "eq",
|
|
446
|
+
value: "notion",
|
|
447
|
+
filter_type: "native_field"
|
|
448
|
+
}
|
|
449
|
+
]
|
|
450
|
+
)
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
#### Filter by Tags
|
|
454
|
+
|
|
455
|
+
```ruby
|
|
456
|
+
results = client.search(
|
|
457
|
+
query: "status",
|
|
458
|
+
search_method: "chunk_vector_search",
|
|
459
|
+
filters: [
|
|
460
|
+
{
|
|
461
|
+
field: "tags",
|
|
462
|
+
operator: "in",
|
|
463
|
+
value: ["project", "important"],
|
|
464
|
+
filter_type: "native_field"
|
|
465
|
+
}
|
|
466
|
+
]
|
|
467
|
+
)
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
#### Filter by Custom Metadata
|
|
471
|
+
|
|
472
|
+
```ruby
|
|
473
|
+
results = client.search(
|
|
474
|
+
query: "urgent tasks",
|
|
475
|
+
search_method: "chunk_vector_search",
|
|
476
|
+
filters: [
|
|
477
|
+
{
|
|
478
|
+
field: "priority",
|
|
479
|
+
operator: "eq",
|
|
480
|
+
value: "high",
|
|
481
|
+
filter_type: "custom_metadata"
|
|
482
|
+
}
|
|
483
|
+
]
|
|
484
|
+
)
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
#### Multiple Filters
|
|
488
|
+
|
|
489
|
+
```ruby
|
|
490
|
+
results = client.search(
|
|
491
|
+
query: "engineering work",
|
|
492
|
+
search_method: "chunk_vector_search",
|
|
493
|
+
filters: [
|
|
494
|
+
{
|
|
495
|
+
field: "source",
|
|
496
|
+
operator: "eq",
|
|
497
|
+
value: "jira",
|
|
498
|
+
filter_type: "native_field"
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
field: "tags",
|
|
502
|
+
operator: "in",
|
|
503
|
+
value: ["backend", "api"],
|
|
504
|
+
filter_type: "native_field"
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
field: "priority",
|
|
508
|
+
operator: "eq",
|
|
509
|
+
value: "high",
|
|
510
|
+
filter_type: "custom_metadata"
|
|
511
|
+
}
|
|
512
|
+
]
|
|
513
|
+
)
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
#### Filters in Chat
|
|
517
|
+
|
|
518
|
+
```ruby
|
|
519
|
+
response = client.chat(
|
|
520
|
+
query: "What are the high priority backend tasks?",
|
|
521
|
+
filters: [
|
|
522
|
+
{
|
|
523
|
+
field: "department",
|
|
524
|
+
operator: "eq",
|
|
525
|
+
value: "engineering",
|
|
526
|
+
filter_type: "custom_metadata"
|
|
527
|
+
}
|
|
528
|
+
]
|
|
529
|
+
)
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
#### Filters in Document Generation
|
|
533
|
+
|
|
534
|
+
```ruby
|
|
535
|
+
response = client.generate_doc(
|
|
536
|
+
prompt: "Create a security audit summary",
|
|
537
|
+
rules: "Focus on key findings",
|
|
538
|
+
filters: [
|
|
539
|
+
{
|
|
540
|
+
field: "tags",
|
|
541
|
+
operator: "in",
|
|
542
|
+
value: ["security", "audit"],
|
|
543
|
+
filter_type: "native_field"
|
|
544
|
+
}
|
|
545
|
+
]
|
|
546
|
+
)
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
## Error Handling
|
|
550
|
+
|
|
551
|
+
The SDK raises exceptions for API errors. Wrap your calls in begin/rescue blocks:
|
|
552
|
+
|
|
553
|
+
```ruby
|
|
554
|
+
begin
|
|
555
|
+
memo = client.get_memo("invalid-id")
|
|
556
|
+
rescue => e
|
|
557
|
+
puts "Error: #{e.message}"
|
|
558
|
+
# => "Skald API error (404): Not Found"
|
|
559
|
+
end
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
All methods may raise `RuntimeError` with the format:
|
|
563
|
+
```
|
|
564
|
+
Skald API error (STATUS_CODE): ERROR_MESSAGE
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
## Examples
|
|
568
|
+
|
|
569
|
+
See the [examples](examples/) directory for complete working examples:
|
|
570
|
+
|
|
571
|
+
- [memo_operations.rb](examples/memo_operations.rb) - Create, read, update, delete memos
|
|
572
|
+
- [search.rb](examples/search.rb) - All search methods and filtering
|
|
573
|
+
- [chat.rb](examples/chat.rb) - Streaming and non-streaming chat
|
|
574
|
+
- [document_generation.rb](examples/document_generation.rb) - Document generation with rules
|
|
575
|
+
- [filters.rb](examples/filters.rb) - Advanced filtering examples
|
|
576
|
+
|
|
577
|
+
## Development
|
|
578
|
+
|
|
579
|
+
```bash
|
|
580
|
+
# Install dependencies
|
|
581
|
+
bundle install
|
|
582
|
+
|
|
583
|
+
# Run tests
|
|
584
|
+
bundle exec rspec
|
|
585
|
+
|
|
586
|
+
# Run tests with coverage
|
|
587
|
+
bundle exec rspec --format documentation
|
|
588
|
+
|
|
589
|
+
# Run linter
|
|
590
|
+
bundle exec rubocop
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
## Testing
|
|
594
|
+
|
|
595
|
+
The SDK includes comprehensive tests with 100% method coverage:
|
|
596
|
+
|
|
597
|
+
```bash
|
|
598
|
+
bundle exec rspec
|
|
599
|
+
# => 54 examples, 0 failures
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
Tests use WebMock to mock HTTP requests, so no actual API calls are made during testing.
|
|
603
|
+
|
|
604
|
+
## API Reference
|
|
605
|
+
|
|
606
|
+
For complete API documentation, visit the [Skald API Documentation](https://docs.useskald.com).
|
|
607
|
+
|
|
608
|
+
## Contributing
|
|
609
|
+
|
|
610
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/skald-org/skald-ruby.
|
|
611
|
+
|
|
612
|
+
## License
|
|
613
|
+
|
|
614
|
+
The gem is available as open source under the terms of the [MIT License](LICENSE).
|
|
615
|
+
|
|
616
|
+
## Support
|
|
617
|
+
|
|
618
|
+
- Documentation: https://docs.useskald.com
|
|
619
|
+
- Email: support@useskald.com
|
|
620
|
+
- GitHub Issues: https://github.com/skald-org/skald-ruby/issues
|
data/lib/skald.rb
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "uri"
|
|
5
|
+
require "json"
|
|
6
|
+
require "cgi"
|
|
7
|
+
|
|
8
|
+
# Skald Ruby SDK
|
|
9
|
+
#
|
|
10
|
+
# A Ruby client library for interacting with the Skald API platform.
|
|
11
|
+
# Provides methods for managing memos, searching, chatting, and generating documents.
|
|
12
|
+
class Skald
|
|
13
|
+
# Base URL for the Skald API
|
|
14
|
+
DEFAULT_BASE_URL = "https://api.useskald.com"
|
|
15
|
+
|
|
16
|
+
# @return [String] the API key used for authentication
|
|
17
|
+
attr_reader :api_key
|
|
18
|
+
|
|
19
|
+
# @return [String] the base URL for API requests
|
|
20
|
+
attr_reader :base_url
|
|
21
|
+
|
|
22
|
+
# Initialize a new Skald client
|
|
23
|
+
#
|
|
24
|
+
# @param api_key [String] Your Skald API key (required)
|
|
25
|
+
# @param base_url [String] Optional custom base URL (defaults to https://api.useskald.com)
|
|
26
|
+
#
|
|
27
|
+
# @example
|
|
28
|
+
# client = Skald.new("your-api-key")
|
|
29
|
+
# # Or with custom base URL
|
|
30
|
+
# client = Skald.new("your-api-key", "https://custom.api.com")
|
|
31
|
+
def initialize(api_key, base_url = DEFAULT_BASE_URL)
|
|
32
|
+
raise ArgumentError, "API key is required" if api_key.nil? || api_key.empty?
|
|
33
|
+
|
|
34
|
+
@api_key = api_key
|
|
35
|
+
@base_url = base_url.gsub(%r{/+$}, "") # Remove trailing slashes
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Create a new memo
|
|
39
|
+
#
|
|
40
|
+
# @param memo_data [Hash] The memo data
|
|
41
|
+
# @option memo_data [String] :title The memo title (required, max 255 chars)
|
|
42
|
+
# @option memo_data [String] :content The memo content (required)
|
|
43
|
+
# @option memo_data [Hash] :metadata Optional custom metadata (JSON object)
|
|
44
|
+
# @option memo_data [String] :reference_id Optional external reference ID (max 255 chars)
|
|
45
|
+
# @option memo_data [Array<String>] :tags Optional array of tags
|
|
46
|
+
# @option memo_data [String] :source Optional source system (e.g., "notion", max 255 chars)
|
|
47
|
+
# @option memo_data [String] :expiration_date Optional ISO 8601 expiration timestamp
|
|
48
|
+
#
|
|
49
|
+
# @return [Hash] Response with :ok key
|
|
50
|
+
#
|
|
51
|
+
# @example
|
|
52
|
+
# result = client.create_memo(
|
|
53
|
+
# title: "Meeting Notes",
|
|
54
|
+
# content: "Discussed project timeline...",
|
|
55
|
+
# metadata: { priority: "high" },
|
|
56
|
+
# tags: ["meeting", "project"]
|
|
57
|
+
# )
|
|
58
|
+
def create_memo(memo_data)
|
|
59
|
+
data = memo_data.dup
|
|
60
|
+
data[:metadata] ||= {}
|
|
61
|
+
|
|
62
|
+
request(:post, "/api/v1/memo", data)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Get a memo by ID
|
|
66
|
+
#
|
|
67
|
+
# @param memo_id [String] The memo UUID or reference ID
|
|
68
|
+
# @param id_type [String] Type of ID: "memo_uuid" or "reference_id" (default: "memo_uuid")
|
|
69
|
+
#
|
|
70
|
+
# @return [Hash] The memo object with all fields
|
|
71
|
+
#
|
|
72
|
+
# @example
|
|
73
|
+
# memo = client.get_memo("550e8400-e29b-41d4-a716-446655440000")
|
|
74
|
+
# # Or by reference ID
|
|
75
|
+
# memo = client.get_memo("my-ref-id", "reference_id")
|
|
76
|
+
def get_memo(memo_id, id_type = "memo_uuid")
|
|
77
|
+
encoded_id = CGI.escape(memo_id)
|
|
78
|
+
params = id_type != "memo_uuid" ? "?id_type=#{id_type}" : ""
|
|
79
|
+
request(:get, "/api/v1/memo/#{encoded_id}#{params}")
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# List memos with pagination
|
|
83
|
+
#
|
|
84
|
+
# @param params [Hash] Optional pagination parameters
|
|
85
|
+
# @option params [Integer] :page Page number (default: 1)
|
|
86
|
+
# @option params [Integer] :page_size Number of results per page (default: 20, max: 100)
|
|
87
|
+
#
|
|
88
|
+
# @return [Hash] Response with :count, :next, :previous, and :results keys
|
|
89
|
+
#
|
|
90
|
+
# @example
|
|
91
|
+
# response = client.list_memos(page: 1, page_size: 50)
|
|
92
|
+
# response[:results].each do |memo|
|
|
93
|
+
# puts memo[:title]
|
|
94
|
+
# end
|
|
95
|
+
def list_memos(params = {})
|
|
96
|
+
query_params = []
|
|
97
|
+
query_params << "page=#{params[:page]}" if params[:page]
|
|
98
|
+
query_params << "page_size=#{params[:page_size]}" if params[:page_size]
|
|
99
|
+
query_string = query_params.empty? ? "" : "?#{query_params.join('&')}"
|
|
100
|
+
|
|
101
|
+
request(:get, "/api/v1/memo#{query_string}")
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Update an existing memo
|
|
105
|
+
#
|
|
106
|
+
# @param memo_id [String] The memo UUID or reference ID
|
|
107
|
+
# @param update_data [Hash] The fields to update
|
|
108
|
+
# @option update_data [String] :title New title
|
|
109
|
+
# @option update_data [String] :content New content (triggers reprocessing)
|
|
110
|
+
# @option update_data [Hash] :metadata New metadata
|
|
111
|
+
# @option update_data [String] :client_reference_id New reference ID
|
|
112
|
+
# @option update_data [String] :source New source
|
|
113
|
+
# @option update_data [String] :expiration_date New expiration date
|
|
114
|
+
# @param id_type [String] Type of ID: "memo_uuid" or "reference_id" (default: "memo_uuid")
|
|
115
|
+
#
|
|
116
|
+
# @return [Hash] Response with :ok key
|
|
117
|
+
#
|
|
118
|
+
# @example
|
|
119
|
+
# client.update_memo(
|
|
120
|
+
# "550e8400-e29b-41d4-a716-446655440000",
|
|
121
|
+
# { title: "Updated Title", metadata: { status: "completed" } }
|
|
122
|
+
# )
|
|
123
|
+
def update_memo(memo_id, update_data, id_type = "memo_uuid")
|
|
124
|
+
encoded_id = CGI.escape(memo_id)
|
|
125
|
+
params = id_type != "memo_uuid" ? "?id_type=#{id_type}" : ""
|
|
126
|
+
request(:patch, "/api/v1/memo/#{encoded_id}#{params}", update_data)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Delete a memo
|
|
130
|
+
#
|
|
131
|
+
# @param memo_id [String] The memo UUID or reference ID
|
|
132
|
+
# @param id_type [String] Type of ID: "memo_uuid" or "reference_id" (default: "memo_uuid")
|
|
133
|
+
#
|
|
134
|
+
# @return [nil]
|
|
135
|
+
#
|
|
136
|
+
# @example
|
|
137
|
+
# client.delete_memo("550e8400-e29b-41d4-a716-446655440000")
|
|
138
|
+
def delete_memo(memo_id, id_type = "memo_uuid")
|
|
139
|
+
encoded_id = CGI.escape(memo_id)
|
|
140
|
+
params = id_type != "memo_uuid" ? "?id_type=#{id_type}" : ""
|
|
141
|
+
request(:delete, "/api/v1/memo/#{encoded_id}#{params}")
|
|
142
|
+
nil
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Search for memos
|
|
146
|
+
#
|
|
147
|
+
# @param search_params [Hash] Search parameters
|
|
148
|
+
# @option search_params [String] :query The search query (required)
|
|
149
|
+
# @option search_params [String] :search_method Search method: "chunk_vector_search", "title_contains", or "title_startswith" (required)
|
|
150
|
+
# @option search_params [Integer] :limit Maximum number of results (1-50, default: 10)
|
|
151
|
+
# @option search_params [Array<Hash>] :filters Optional filters to apply
|
|
152
|
+
#
|
|
153
|
+
# @return [Hash] Response with :results array
|
|
154
|
+
#
|
|
155
|
+
# @example
|
|
156
|
+
# results = client.search(
|
|
157
|
+
# query: "project timeline",
|
|
158
|
+
# search_method: "chunk_vector_search",
|
|
159
|
+
# limit: 20
|
|
160
|
+
# )
|
|
161
|
+
def search(search_params)
|
|
162
|
+
request(:post, "/api/v1/search", search_params)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Chat with your memos (non-streaming)
|
|
166
|
+
#
|
|
167
|
+
# @param chat_params [Hash] Chat parameters
|
|
168
|
+
# @option chat_params [String] :query The question to ask (required)
|
|
169
|
+
# @option chat_params [Array<Hash>] :filters Optional filters to apply
|
|
170
|
+
#
|
|
171
|
+
# @return [Hash] Response with :ok, :response, and :intermediate_steps keys
|
|
172
|
+
#
|
|
173
|
+
# @example
|
|
174
|
+
# response = client.chat(query: "What are the main project goals?")
|
|
175
|
+
# puts response[:response]
|
|
176
|
+
def chat(chat_params)
|
|
177
|
+
params = chat_params.dup
|
|
178
|
+
params[:stream] = false
|
|
179
|
+
request(:post, "/api/v1/chat", params)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Chat with your memos (streaming)
|
|
183
|
+
#
|
|
184
|
+
# @param chat_params [Hash] Chat parameters
|
|
185
|
+
# @option chat_params [String] :query The question to ask (required)
|
|
186
|
+
# @option chat_params [Array<Hash>] :filters Optional filters to apply
|
|
187
|
+
#
|
|
188
|
+
# @return [Enumerator] An enumerator that yields events
|
|
189
|
+
#
|
|
190
|
+
# @example
|
|
191
|
+
# client.streamed_chat(query: "Summarize the project").each do |event|
|
|
192
|
+
# if event[:type] == "token"
|
|
193
|
+
# print event[:content]
|
|
194
|
+
# elsif event[:type] == "done"
|
|
195
|
+
# puts "\nDone!"
|
|
196
|
+
# end
|
|
197
|
+
# end
|
|
198
|
+
def streamed_chat(chat_params)
|
|
199
|
+
params = chat_params.dup
|
|
200
|
+
params[:stream] = true
|
|
201
|
+
stream_request(:post, "/api/v1/chat", params)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Generate a document (non-streaming)
|
|
205
|
+
#
|
|
206
|
+
# @param generate_params [Hash] Generation parameters
|
|
207
|
+
# @option generate_params [String] :prompt What document to generate (required)
|
|
208
|
+
# @option generate_params [String] :rules Optional style/format rules
|
|
209
|
+
# @option generate_params [Array<Hash>] :filters Optional filters to apply
|
|
210
|
+
#
|
|
211
|
+
# @return [Hash] Response with :ok, :response, and :intermediate_steps keys
|
|
212
|
+
#
|
|
213
|
+
# @example
|
|
214
|
+
# doc = client.generate_doc(
|
|
215
|
+
# prompt: "Create a project status report",
|
|
216
|
+
# rules: "Use bullet points and be concise"
|
|
217
|
+
# )
|
|
218
|
+
# puts doc[:response]
|
|
219
|
+
def generate_doc(generate_params)
|
|
220
|
+
params = generate_params.dup
|
|
221
|
+
params[:stream] = false
|
|
222
|
+
request(:post, "/api/v1/generate", params)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Generate a document (streaming)
|
|
226
|
+
#
|
|
227
|
+
# @param generate_params [Hash] Generation parameters
|
|
228
|
+
# @option generate_params [String] :prompt What document to generate (required)
|
|
229
|
+
# @option generate_params [String] :rules Optional style/format rules
|
|
230
|
+
# @option generate_params [Array<Hash>] :filters Optional filters to apply
|
|
231
|
+
#
|
|
232
|
+
# @return [Enumerator] An enumerator that yields events
|
|
233
|
+
#
|
|
234
|
+
# @example
|
|
235
|
+
# client.streamed_generate_doc(prompt: "Write a summary").each do |event|
|
|
236
|
+
# if event[:type] == "token"
|
|
237
|
+
# print event[:content]
|
|
238
|
+
# elsif event[:type] == "done"
|
|
239
|
+
# puts "\nDone!"
|
|
240
|
+
# end
|
|
241
|
+
# end
|
|
242
|
+
def streamed_generate_doc(generate_params)
|
|
243
|
+
params = generate_params.dup
|
|
244
|
+
params[:stream] = true
|
|
245
|
+
stream_request(:post, "/api/v1/generate", params)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
private
|
|
249
|
+
|
|
250
|
+
# Make an HTTP request
|
|
251
|
+
def request(method, path, body = nil)
|
|
252
|
+
uri = URI.join(@base_url, path)
|
|
253
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
254
|
+
http.use_ssl = uri.scheme == "https"
|
|
255
|
+
|
|
256
|
+
request_class = case method
|
|
257
|
+
when :get then Net::HTTP::Get
|
|
258
|
+
when :post then Net::HTTP::Post
|
|
259
|
+
when :patch then Net::HTTP::Patch
|
|
260
|
+
when :delete then Net::HTTP::Delete
|
|
261
|
+
else raise ArgumentError, "Unsupported method: #{method}"
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
request = request_class.new(uri.request_uri)
|
|
265
|
+
request["Authorization"] = "Bearer #{@api_key}"
|
|
266
|
+
request["Content-Type"] = "application/json" if body
|
|
267
|
+
|
|
268
|
+
request.body = body.to_json if body
|
|
269
|
+
|
|
270
|
+
response = http.request(request)
|
|
271
|
+
|
|
272
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
273
|
+
raise "Skald API error (#{response.code}): #{response.body}"
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# DELETE returns empty response
|
|
277
|
+
return nil if response.body.nil? || response.body.empty?
|
|
278
|
+
|
|
279
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Make a streaming HTTP request
|
|
283
|
+
def stream_request(method, path, body)
|
|
284
|
+
Enumerator.new do |yielder|
|
|
285
|
+
uri = URI.join(@base_url, path)
|
|
286
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
287
|
+
http.use_ssl = uri.scheme == "https"
|
|
288
|
+
http.read_timeout = 300 # 5 minutes for streaming
|
|
289
|
+
|
|
290
|
+
request_class = case method
|
|
291
|
+
when :post then Net::HTTP::Post
|
|
292
|
+
else raise ArgumentError, "Unsupported streaming method: #{method}"
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
request = request_class.new(uri.request_uri)
|
|
296
|
+
request["Authorization"] = "Bearer #{@api_key}"
|
|
297
|
+
request["Content-Type"] = "application/json"
|
|
298
|
+
request.body = body.to_json
|
|
299
|
+
|
|
300
|
+
buffer = ""
|
|
301
|
+
|
|
302
|
+
http.request(request) do |response|
|
|
303
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
304
|
+
raise "Skald API error (#{response.code}): #{response.body}"
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
response.read_body do |chunk|
|
|
308
|
+
buffer += chunk
|
|
309
|
+
|
|
310
|
+
# Process complete lines
|
|
311
|
+
while (newline_index = buffer.index("\n"))
|
|
312
|
+
line = buffer[0...newline_index].strip
|
|
313
|
+
buffer = buffer[(newline_index + 1)..-1] || ""
|
|
314
|
+
|
|
315
|
+
next if line.empty?
|
|
316
|
+
next if line.start_with?(": ") # Skip ping events
|
|
317
|
+
|
|
318
|
+
# Parse SSE format (data: {...})
|
|
319
|
+
if line.start_with?("data: ")
|
|
320
|
+
json_str = line[6..-1]
|
|
321
|
+
begin
|
|
322
|
+
event = JSON.parse(json_str, symbolize_names: true)
|
|
323
|
+
yielder << event
|
|
324
|
+
break if event[:type] == "done"
|
|
325
|
+
rescue JSON::ParserError
|
|
326
|
+
# Skip invalid JSON
|
|
327
|
+
next
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: skald
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Skald
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-10-24 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rspec
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '3.13'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '3.13'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: webmock
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '3.23'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '3.23'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rake
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '13.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '13.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rubocop
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '1.21'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '1.21'
|
|
69
|
+
description: Ruby client library for Skald - an API platform for context/knowledge
|
|
70
|
+
management with chat, document generation, and semantic search capabilities
|
|
71
|
+
email:
|
|
72
|
+
- support@useskald.com
|
|
73
|
+
executables: []
|
|
74
|
+
extensions: []
|
|
75
|
+
extra_rdoc_files: []
|
|
76
|
+
files:
|
|
77
|
+
- LICENSE
|
|
78
|
+
- README.md
|
|
79
|
+
- lib/skald.rb
|
|
80
|
+
homepage: https://github.com/skald-org/skald-ruby
|
|
81
|
+
licenses:
|
|
82
|
+
- MIT
|
|
83
|
+
metadata:
|
|
84
|
+
homepage_uri: https://github.com/skald-org/skald-ruby
|
|
85
|
+
source_code_uri: https://github.com/skald-org/skald-ruby
|
|
86
|
+
changelog_uri: https://github.com/skald-org/skald-ruby/blob/main/CHANGELOG.md
|
|
87
|
+
post_install_message:
|
|
88
|
+
rdoc_options: []
|
|
89
|
+
require_paths:
|
|
90
|
+
- lib
|
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: 3.0.0
|
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
97
|
+
requirements:
|
|
98
|
+
- - ">="
|
|
99
|
+
- !ruby/object:Gem::Version
|
|
100
|
+
version: '0'
|
|
101
|
+
requirements: []
|
|
102
|
+
rubygems_version: 3.5.22
|
|
103
|
+
signing_key:
|
|
104
|
+
specification_version: 4
|
|
105
|
+
summary: Ruby SDK for Skald API
|
|
106
|
+
test_files: []
|