melaya 0.1.4 → 0.2.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 +4 -4
- data/LICENSE +158 -0
- data/README.md +96 -15
- data/lib/melaya/accounts.rb +42 -0
- data/lib/melaya/assistant.rb +41 -0
- data/lib/melaya/auth.rb +137 -0
- data/lib/melaya/backtest.rb +38 -0
- data/lib/melaya/billing.rb +75 -0
- data/lib/melaya/bugs.rb +67 -0
- data/lib/melaya/connectors.rb +71 -0
- data/lib/melaya/credentials.rb +255 -0
- data/lib/melaya/errors.rb +36 -2
- data/lib/melaya/evals.rb +78 -0
- data/lib/melaya/events.rb +425 -0
- data/lib/melaya/hitl.rb +114 -0
- data/lib/melaya/http_client.rb +159 -30
- data/lib/melaya/market.rb +42 -0
- data/lib/melaya/namespaces.rb +115 -0
- data/lib/melaya/phone.rb +75 -0
- data/lib/melaya/pipelines.rb +342 -0
- data/lib/melaya/projects.rb +58 -0
- data/lib/melaya/runner.rb +44 -0
- data/lib/melaya/strategies.rb +15 -0
- data/lib/melaya/stream.rb +5 -3
- data/lib/melaya/team.rb +102 -0
- data/lib/melaya/templates.rb +148 -0
- data/lib/melaya/version.rb +1 -1
- data/lib/melaya.rb +261 -30
- data/melaya.gemspec +10 -5
- metadata +28 -7
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Melaya
|
|
4
|
+
# Pipelines API — overview dashboard, pipeline run listing, traces, and
|
|
5
|
+
# cron-based scheduling.
|
|
6
|
+
#
|
|
7
|
+
# Maps to:
|
|
8
|
+
# /api/v1/private/overview/* — dashboard + run listing
|
|
9
|
+
# /api/v1/private/runs/:id/traces/* — distributed traces
|
|
10
|
+
# /api/v1/private/pipeline-schedule — cron scheduling
|
|
11
|
+
# /api/v1/version — server version (public)
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# runs = melaya.pipelines.list(project: "my-project", limit: 20)
|
|
15
|
+
# melaya.pipelines.upsert_schedule("my-project", "nightly-report",
|
|
16
|
+
# cron: "0 2 * * *")
|
|
17
|
+
class PipelinesAPI
|
|
18
|
+
def initialize(http)
|
|
19
|
+
@http = http
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# ── Overview ───────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
# GET /api/v1/private/overview
|
|
25
|
+
# Dashboard overview: usage stats, active strategies, recent runs.
|
|
26
|
+
def overview
|
|
27
|
+
@http.get("/api/v1/private/overview")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# GET /api/v1/private/overview/model-prices
|
|
31
|
+
# Get pricing data for available AI models.
|
|
32
|
+
def model_prices
|
|
33
|
+
@http.get("/api/v1/private/overview/model-prices")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# GET /api/v1/private/overview/chart
|
|
37
|
+
# Get chart data for overview dashboard (cost/usage over time).
|
|
38
|
+
def chart_data(params = {})
|
|
39
|
+
@http.get("/api/v1/private/overview/chart", params)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# GET /api/v1/private/overview/cost-breakdown
|
|
43
|
+
# Get cost breakdown by model/provider.
|
|
44
|
+
def cost_breakdown(params = {})
|
|
45
|
+
@http.get("/api/v1/private/overview/cost-breakdown", params)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# GET /api/v1/private/overview/pipeline-count
|
|
49
|
+
# Count of pipeline runs grouped by status.
|
|
50
|
+
def count
|
|
51
|
+
@http.get("/api/v1/private/overview/pipeline-count")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# GET /api/v1/private/overview/pipelines
|
|
55
|
+
# Paginated list of pipeline runs.
|
|
56
|
+
# @param project [String, nil]
|
|
57
|
+
# @param pipeline_name [String, nil]
|
|
58
|
+
# @param status [String, nil]
|
|
59
|
+
# @param limit [Integer, nil]
|
|
60
|
+
# @param offset [Integer, nil]
|
|
61
|
+
def list(project: nil, pipeline_name: nil, status: nil, limit: nil, offset: nil)
|
|
62
|
+
@http.get("/api/v1/private/overview/pipelines",
|
|
63
|
+
compact("project" => project, "pipelineName" => pipeline_name,
|
|
64
|
+
"status" => status, "limit" => limit,
|
|
65
|
+
"offset" => offset))
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# GET /api/v1/private/overview/pipelines/recent
|
|
69
|
+
# Most recent pipeline runs for a dashboard widget.
|
|
70
|
+
def recent
|
|
71
|
+
@http.get("/api/v1/private/overview/pipelines/recent")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# ── Traces ─────────────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
# GET /api/v1/private/runs/:runId/traces
|
|
77
|
+
# List traces for a run (paginated).
|
|
78
|
+
#
|
|
79
|
+
# Returns a paginated envelope, NOT a flat array:
|
|
80
|
+
# {
|
|
81
|
+
# "data" => {
|
|
82
|
+
# "list" => Array<Hash>, # trace summaries
|
|
83
|
+
# "total" => Integer, # total matching traces
|
|
84
|
+
# "page" => Integer,
|
|
85
|
+
# "pageSize" => Integer
|
|
86
|
+
# }
|
|
87
|
+
# }
|
|
88
|
+
#
|
|
89
|
+
# @param run_id [String]
|
|
90
|
+
# @param params [Hash] optional pagination / filter params (page:, pageSize:, ...)
|
|
91
|
+
# @return [Hash] paginated envelope as described above
|
|
92
|
+
def traces(run_id, params = {})
|
|
93
|
+
@http.get("/api/v1/private/runs/#{enc(run_id)}/traces", params)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# GET /api/v1/private/runs/:runId/traces/:traceId
|
|
97
|
+
# Get a single trace by ID.
|
|
98
|
+
def trace(run_id, trace_id)
|
|
99
|
+
@http.get("/api/v1/private/runs/#{enc(run_id)}/traces/#{enc(trace_id)}")
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# GET /api/v1/private/runs/:runId/traces/:traceId/stats
|
|
103
|
+
# Get statistics for a specific trace.
|
|
104
|
+
def trace_stats(run_id, trace_id)
|
|
105
|
+
@http.get("/api/v1/private/runs/#{enc(run_id)}/traces/#{enc(trace_id)}/stats")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# DELETE /api/v1/private/runs/:runId/traces
|
|
109
|
+
# Delete all traces (and their spans) for a run by runId.
|
|
110
|
+
# No request body required — the runId in the path identifies the target.
|
|
111
|
+
#
|
|
112
|
+
# @param run_id [String]
|
|
113
|
+
# @return [Hash] { "deletedSpans" => Integer, "requestedTraces" => Integer (optional) }
|
|
114
|
+
def delete_traces(run_id)
|
|
115
|
+
@http.delete("/api/v1/private/runs/#{enc(run_id)}/traces")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# ── Schedule ───────────────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
# GET /api/v1/private/pipeline-schedule
|
|
121
|
+
# List all pipeline schedules accessible to the caller.
|
|
122
|
+
def list_schedules
|
|
123
|
+
@http.get("/api/v1/private/pipeline-schedule")
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# GET /api/v1/private/pipeline-schedule/:project/:pipelineName
|
|
127
|
+
# Get schedule status for a pipeline.
|
|
128
|
+
# @param project [String]
|
|
129
|
+
# @param pipeline_name [String]
|
|
130
|
+
def get_schedule(project, pipeline_name)
|
|
131
|
+
@http.get("/api/v1/private/pipeline-schedule/#{enc(project)}/#{enc(pipeline_name)}")
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# PUT /api/v1/private/pipeline-schedule/:project/:pipelineName
|
|
135
|
+
# Create or update a pipeline schedule (cron expression + optional config).
|
|
136
|
+
# @param project [String]
|
|
137
|
+
# @param pipeline_name [String]
|
|
138
|
+
# @param cron [String] cron expression e.g. "0 2 * * *"
|
|
139
|
+
# @param config [Hash, nil] optional pipeline config overrides
|
|
140
|
+
def upsert_schedule(project, pipeline_name, cron:, config: nil)
|
|
141
|
+
body = compact("cron" => cron, "config" => config)
|
|
142
|
+
@http.put("/api/v1/private/pipeline-schedule/#{enc(project)}/#{enc(pipeline_name)}", body)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# POST /api/v1/private/pipeline-schedule/:project/:pipelineName/pause
|
|
146
|
+
# Pause a pipeline schedule.
|
|
147
|
+
def pause_schedule(project, pipeline_name)
|
|
148
|
+
@http.post("/api/v1/private/pipeline-schedule/#{enc(project)}/#{enc(pipeline_name)}/pause")
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# POST /api/v1/private/pipeline-schedule/:project/:pipelineName/resume
|
|
152
|
+
# Resume a paused pipeline schedule.
|
|
153
|
+
def resume_schedule(project, pipeline_name)
|
|
154
|
+
@http.post("/api/v1/private/pipeline-schedule/#{enc(project)}/#{enc(pipeline_name)}/resume")
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# ── Pipeline lifecycle (CRUD + run + outputs + AI build) ──────────────────
|
|
158
|
+
|
|
159
|
+
# GET /api/v1/private/pipelines
|
|
160
|
+
# List all pipeline configs accessible to the caller.
|
|
161
|
+
# Returns { "pipelines" => [...] }.
|
|
162
|
+
def list_pipelines
|
|
163
|
+
@http.get("/api/v1/private/pipelines")
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# POST /api/v1/private/pipelines
|
|
167
|
+
# Create a new pipeline config.
|
|
168
|
+
# @param name [String] pipeline name
|
|
169
|
+
# @param project [String] owning project
|
|
170
|
+
# @param description [String, nil]
|
|
171
|
+
# @param config [Hash] additional config keys merged into the request body
|
|
172
|
+
# @return [Hash] created pipeline config
|
|
173
|
+
def create(name:, project:, description: nil, **config)
|
|
174
|
+
body = compact(
|
|
175
|
+
"name" => name,
|
|
176
|
+
"project" => project,
|
|
177
|
+
"description" => description
|
|
178
|
+
).merge(stringify_keys(config))
|
|
179
|
+
@http.post("/api/v1/private/pipelines", body)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# GET /api/v1/private/pipelines/:name
|
|
183
|
+
# Fetch a single pipeline config by name.
|
|
184
|
+
# @param name [String] pipeline name
|
|
185
|
+
# @param project [String, nil] owning project (disambiguates when multiple projects share a name)
|
|
186
|
+
# @return [Hash] pipeline config
|
|
187
|
+
def get(name, project: nil)
|
|
188
|
+
params = compact("project" => project)
|
|
189
|
+
@http.get("/api/v1/private/pipelines/#{enc(name)}", params)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# PUT /api/v1/private/pipelines/:name
|
|
193
|
+
# Replace a pipeline's config.
|
|
194
|
+
# @param name [String] pipeline name
|
|
195
|
+
# @param config [Hash] full pipeline config payload
|
|
196
|
+
# @param project [String, nil] owning project
|
|
197
|
+
# @return [Hash] updated pipeline config
|
|
198
|
+
def update(name, config:, project: nil)
|
|
199
|
+
body = compact("config" => config, "project" => project)
|
|
200
|
+
@http.put("/api/v1/private/pipelines/#{enc(name)}", body)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# DELETE /api/v1/private/pipelines/:name
|
|
204
|
+
# Delete a pipeline config.
|
|
205
|
+
# @param name [String] pipeline name
|
|
206
|
+
# @param project [String, nil] owning project
|
|
207
|
+
# @return [Hash] empty hash on success
|
|
208
|
+
def delete_pipeline(name, project: nil)
|
|
209
|
+
params = compact("project" => project)
|
|
210
|
+
@http.delete("/api/v1/private/pipelines/#{enc(name)}", params)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# POST /api/v1/private/pipelines/:name/run
|
|
214
|
+
# Enqueue a pipeline run.
|
|
215
|
+
# @param name [String] pipeline name
|
|
216
|
+
# @param project [String, nil]
|
|
217
|
+
# @param execution_target [String, nil] runner target identifier
|
|
218
|
+
# @param studio_url [String, nil] override studio URL
|
|
219
|
+
# @param env_overrides [Hash, nil] environment variable overrides
|
|
220
|
+
# @return [Hash] { "run_id" => String, "queued" => Boolean }
|
|
221
|
+
def run(name, project: nil, execution_target: nil, studio_url: nil, env_overrides: nil)
|
|
222
|
+
body = compact(
|
|
223
|
+
"project" => project,
|
|
224
|
+
"executionTarget" => execution_target,
|
|
225
|
+
"studio_url" => studio_url,
|
|
226
|
+
"env_overrides" => env_overrides
|
|
227
|
+
)
|
|
228
|
+
@http.post("/api/v1/private/pipelines/#{enc(name)}/run", body.empty? ? nil : body)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# GET /api/v1/private/pipelines/:name/runs
|
|
232
|
+
# List all run IDs for a pipeline.
|
|
233
|
+
# @param name [String] pipeline name
|
|
234
|
+
# @return [Hash] { "run_ids" => Array<String> }
|
|
235
|
+
def run_ids(name)
|
|
236
|
+
@http.get("/api/v1/private/pipelines/#{enc(name)}/runs")
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# GET /api/v1/private/pipelines/:name/runs/:run_id
|
|
240
|
+
# Get the status of a specific pipeline run.
|
|
241
|
+
# @param name [String] pipeline name
|
|
242
|
+
# @param run_id [String] run identifier
|
|
243
|
+
# @return [Hash] { "runId", "status", "createdAt", "executionTarget", "cost" }
|
|
244
|
+
def run_status(name, run_id)
|
|
245
|
+
@http.get("/api/v1/private/pipelines/#{enc(name)}/runs/#{enc(run_id)}")
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# DELETE /api/v1/private/pipelines/:name/runs/:run_id
|
|
249
|
+
# Cancel an in-progress pipeline run.
|
|
250
|
+
# @param name [String] pipeline name
|
|
251
|
+
# @param run_id [String] run identifier
|
|
252
|
+
# @return [Hash] empty hash on success
|
|
253
|
+
def cancel_run(name, run_id)
|
|
254
|
+
@http.delete("/api/v1/private/pipelines/#{enc(name)}/runs/#{enc(run_id)}")
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# GET /api/v1/private/pipelines/:name/outputs
|
|
258
|
+
# List all output artifacts produced by a pipeline.
|
|
259
|
+
# @param name [String] pipeline name
|
|
260
|
+
# @return artifacts listing
|
|
261
|
+
def outputs(name)
|
|
262
|
+
@http.get("/api/v1/private/pipelines/#{enc(name)}/outputs")
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# GET /api/v1/private/pipelines/:name/outputs/:path
|
|
266
|
+
# Fetch a specific output artifact. Each segment of +path+ is individually
|
|
267
|
+
# URL-encoded so slashes in segment values are preserved as separators.
|
|
268
|
+
# @param name [String] pipeline name
|
|
269
|
+
# @param path [String] artifact path, e.g. "reports/2024-01/summary.json"
|
|
270
|
+
# @param download [Boolean] if true, adds +?download=1+ to trigger a download response
|
|
271
|
+
# @return artifact content or download redirect
|
|
272
|
+
def output(name, path, download: false)
|
|
273
|
+
encoded_path = path.to_s.split("/").map { |seg| enc(seg) }.join("/")
|
|
274
|
+
params = download ? { "download" => 1 } : {}
|
|
275
|
+
@http.get("/api/v1/private/pipelines/#{enc(name)}/outputs/#{encoded_path}", params)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# POST /api/v1/private/pipelines/preview-code
|
|
279
|
+
# Generate a code preview for a pipeline config without saving it.
|
|
280
|
+
# @param config [Hash] pipeline config to preview
|
|
281
|
+
# @return preview payload
|
|
282
|
+
def preview_code(config)
|
|
283
|
+
@http.post("/api/v1/private/pipelines/preview-code", config)
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# GET /api/v1/private/pipelines/tools
|
|
287
|
+
# Fetch the registry of tools available to pipeline steps.
|
|
288
|
+
# @return [Hash] tool registry
|
|
289
|
+
def tools
|
|
290
|
+
@http.get("/api/v1/private/pipelines/tools")
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# GET /api/v1/private/pipelines/subagents
|
|
294
|
+
# Fetch the registry of sub-agent definitions available to pipelines.
|
|
295
|
+
# @return [Hash] sub-agent registry
|
|
296
|
+
def subagents
|
|
297
|
+
@http.get("/api/v1/private/pipelines/subagents")
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# POST /api/v1/private/templates/:template_id/instantiate
|
|
301
|
+
# Instantiate a platform template into a new pipeline config.
|
|
302
|
+
# @param template_id [String] the template to instantiate
|
|
303
|
+
# @param name [String] name for the resulting pipeline
|
|
304
|
+
# @param project [String] target project
|
|
305
|
+
# @param overrides [Hash, nil] optional config overrides applied on top of the template defaults
|
|
306
|
+
# @return [Hash] { "pipeline" => config }
|
|
307
|
+
def instantiate_template(template_id, name:, project:, overrides: nil)
|
|
308
|
+
body = compact("name" => name, "project" => project, "overrides" => overrides)
|
|
309
|
+
@http.post("/api/v1/private/templates/#{enc(template_id)}/instantiate", body)
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# POST /api/v1/private/ai/build-pipeline/sync
|
|
313
|
+
# Generate a pipeline config from a natural-language brief using AI.
|
|
314
|
+
# @param brief [Hash] free-form brief payload sent to the AI builder
|
|
315
|
+
# @return [Hash] generated pipeline config
|
|
316
|
+
def build_with_ai(brief)
|
|
317
|
+
@http.post("/api/v1/private/ai/build-pipeline/sync", brief)
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# ── Misc ───────────────────────────────────────────────────────────────────
|
|
321
|
+
|
|
322
|
+
# GET /api/v1/version (public)
|
|
323
|
+
# Get current server version string.
|
|
324
|
+
def server_version
|
|
325
|
+
@http.get("/api/v1/version")
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
private
|
|
329
|
+
|
|
330
|
+
def enc(s)
|
|
331
|
+
URI.encode_www_form_component(s.to_s)
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def compact(hash)
|
|
335
|
+
hash.reject { |_, v| v.nil? }
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def stringify_keys(hash)
|
|
339
|
+
hash.transform_keys(&:to_s)
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Melaya
|
|
4
|
+
# Projects API — create and list agent projects.
|
|
5
|
+
#
|
|
6
|
+
# Projects are the top-level namespace for pipelines, connectors, and team
|
|
7
|
+
# membership in the Melaya platform.
|
|
8
|
+
#
|
|
9
|
+
# Maps to /api/v1/private/projects/*.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# projects = melaya.projects.list
|
|
13
|
+
# project = melaya.projects.create(name: "my-project", description: "...")
|
|
14
|
+
# melaya.projects.rename(old_name: "my-project", new_name: "renamed-project")
|
|
15
|
+
class ProjectsAPI
|
|
16
|
+
def initialize(http)
|
|
17
|
+
@http = http
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# GET /api/v1/private/projects
|
|
21
|
+
# List all projects the authenticated user can access
|
|
22
|
+
# (owned projects + projects they are a member of).
|
|
23
|
+
def list
|
|
24
|
+
@http.get("/api/v1/private/projects")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# POST /api/v1/private/projects
|
|
28
|
+
# Create a new agent project.
|
|
29
|
+
# @param name [String]
|
|
30
|
+
# @param description [String, nil]
|
|
31
|
+
def create(name:, description: nil)
|
|
32
|
+
body = compact("name" => name, "description" => description)
|
|
33
|
+
@http.post("/api/v1/private/projects", body)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# PATCH /api/v1/private/projects/rename
|
|
37
|
+
# Rename a project (body: oldName, newName).
|
|
38
|
+
# @param old_name [String]
|
|
39
|
+
# @param new_name [String]
|
|
40
|
+
def rename(old_name:, new_name:)
|
|
41
|
+
@http.patch("/api/v1/private/projects/rename",
|
|
42
|
+
"oldName" => old_name,
|
|
43
|
+
"newName" => new_name)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# GET /api/v1/private/projects/runner
|
|
47
|
+
# Get projects list (runner-facing, authed).
|
|
48
|
+
def runner_projects
|
|
49
|
+
@http.get("/api/v1/private/projects/runner")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def compact(hash)
|
|
55
|
+
hash.reject { |_, v| v.nil? }
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Melaya
|
|
4
|
+
# Runner API — mint, list, and revoke runner tokens.
|
|
5
|
+
#
|
|
6
|
+
# Runner tokens (mel_run_ prefix) authenticate the Melaya runner CLI process
|
|
7
|
+
# that executes agent pipelines on your infrastructure.
|
|
8
|
+
# The plaintext token is returned only once on creation — store it securely.
|
|
9
|
+
#
|
|
10
|
+
# Maps to /api/v1/private/runner/tokens/*.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# result = melaya.runner.create_token(label: "prod-server-1")
|
|
14
|
+
# token = result["token"] # store securely — shown only once
|
|
15
|
+
#
|
|
16
|
+
# tokens = melaya.runner.list_tokens
|
|
17
|
+
# melaya.runner.revoke_token(tokens.first["id"])
|
|
18
|
+
class RunnerAPI
|
|
19
|
+
def initialize(http)
|
|
20
|
+
@http = http
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# POST /api/v1/private/runner/tokens
|
|
24
|
+
# Mint a new mel_run_ runner token.
|
|
25
|
+
# @param label [String, nil] human-readable label for the token
|
|
26
|
+
def create_token(label: nil)
|
|
27
|
+
body = label ? { "label" => label } : nil
|
|
28
|
+
@http.post("/api/v1/private/runner/tokens", body)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# GET /api/v1/private/runner/tokens
|
|
32
|
+
# List all runner tokens for the caller (masked, with last_seen).
|
|
33
|
+
def list_tokens
|
|
34
|
+
@http.get("/api/v1/private/runner/tokens")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# DELETE /api/v1/private/runner/tokens/:tokenId
|
|
38
|
+
# Revoke a runner token by ID.
|
|
39
|
+
# @param token_id [String]
|
|
40
|
+
def revoke_token(token_id)
|
|
41
|
+
@http.delete("/api/v1/private/runner/tokens/#{URI.encode_www_form_component(token_id.to_s)}")
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/melaya/strategies.rb
CHANGED
|
@@ -151,5 +151,20 @@ module Melaya
|
|
|
151
151
|
def ai_opt_runs(strategy_id)
|
|
152
152
|
@http.get("/api/v1/strategies/#{strategy_id}/ai-opt/runs")
|
|
153
153
|
end
|
|
154
|
+
|
|
155
|
+
# ── restRoutes.ts strategies endpoints ────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
# GET /api/v1/private/strategies/team
|
|
158
|
+
# List strategies visible to the caller's team project.
|
|
159
|
+
def list_team
|
|
160
|
+
@http.get("/api/v1/private/strategies/team")
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# POST /api/v1/private/strategies/summaries/bulk
|
|
164
|
+
# Bulk-fetch lightweight summaries for a list of strategy IDs.
|
|
165
|
+
# @param strategy_ids [Array<String>]
|
|
166
|
+
def summaries_bulk(strategy_ids)
|
|
167
|
+
@http.post("/api/v1/private/strategies/summaries/bulk", "strategyIds" => strategy_ids)
|
|
168
|
+
end
|
|
154
169
|
end
|
|
155
170
|
end
|
data/lib/melaya/stream.rb
CHANGED
|
@@ -42,8 +42,9 @@ module Melaya
|
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def initialize(url, verify_ssl: true)
|
|
45
|
+
raise ArgumentError, "Melaya: TLS certificate verification cannot be disabled." unless verify_ssl
|
|
45
46
|
@uri = URI.parse(url)
|
|
46
|
-
@verify_ssl =
|
|
47
|
+
@verify_ssl = true
|
|
47
48
|
@socket = nil
|
|
48
49
|
@closed = false
|
|
49
50
|
@buf = String.new("", encoding: "BINARY")
|
|
@@ -54,7 +55,7 @@ module Melaya
|
|
|
54
55
|
|
|
55
56
|
@socket = if @uri.scheme == "wss"
|
|
56
57
|
ctx = OpenSSL::SSL::SSLContext.new
|
|
57
|
-
ctx.verify_mode =
|
|
58
|
+
ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
58
59
|
ssl = OpenSSL::SSL::SSLSocket.new(tcp, ctx)
|
|
59
60
|
ssl.hostname = @uri.host
|
|
60
61
|
ssl.connect
|
|
@@ -259,10 +260,11 @@ module Melaya
|
|
|
259
260
|
DEFAULT_WS_URL = "wss://wss.melaya.org"
|
|
260
261
|
|
|
261
262
|
def initialize(api_key, ws_url, http, verify_ssl: true)
|
|
263
|
+
raise ArgumentError, "Melaya: TLS certificate verification cannot be disabled." unless verify_ssl
|
|
262
264
|
@api_key = api_key
|
|
263
265
|
@ws_url = ws_url.to_s.chomp("/")
|
|
264
266
|
@http = http
|
|
265
|
-
@verify_ssl =
|
|
267
|
+
@verify_ssl = true
|
|
266
268
|
end
|
|
267
269
|
|
|
268
270
|
# Live ticker frames.
|
data/lib/melaya/team.rb
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Melaya
|
|
4
|
+
# Team API — manage project team membership, roles, and invitations.
|
|
5
|
+
#
|
|
6
|
+
# Maps to:
|
|
7
|
+
# /api/v1/private/projects/:project/members/*
|
|
8
|
+
# /api/v1/private/team/*
|
|
9
|
+
# /api/v1/private/projects/:project/pipelines/:pipeline/visibility
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# members = melaya.team.list_members("my-project")
|
|
13
|
+
# melaya.team.invite("my-project", username: "alice")
|
|
14
|
+
# link = melaya.team.create_invite_link("my-project")
|
|
15
|
+
class TeamAPI
|
|
16
|
+
def initialize(http)
|
|
17
|
+
@http = http
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# GET /api/v1/private/projects/:project/members
|
|
21
|
+
# List members of a project team.
|
|
22
|
+
# @param project [String] project name
|
|
23
|
+
def list_members(project)
|
|
24
|
+
@http.get("/api/v1/private/projects/#{enc(project)}/members")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# POST /api/v1/private/projects/:project/members/invite
|
|
28
|
+
# Invite a user to a project team by username.
|
|
29
|
+
# @param project [String]
|
|
30
|
+
# @param username [String]
|
|
31
|
+
def invite(project, username:)
|
|
32
|
+
@http.post("/api/v1/private/projects/#{enc(project)}/members/invite",
|
|
33
|
+
"username" => username)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# POST /api/v1/private/projects/:project/invite-link
|
|
37
|
+
# Create a shareable invite link for a project.
|
|
38
|
+
# Returns the URL to send to new team members.
|
|
39
|
+
# @param project [String]
|
|
40
|
+
def create_invite_link(project)
|
|
41
|
+
@http.post("/api/v1/private/projects/#{enc(project)}/invite-link")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# POST /api/v1/private/team/invite/accept
|
|
45
|
+
# Accept a project invite using the token from an invite link.
|
|
46
|
+
# @param token [String]
|
|
47
|
+
def accept_invite(token)
|
|
48
|
+
@http.post("/api/v1/private/team/invite/accept", "token" => token)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# PATCH /api/v1/private/projects/:project/members/:userId
|
|
52
|
+
# Update a team member's role in a project.
|
|
53
|
+
# @param project [String]
|
|
54
|
+
# @param user_id [String]
|
|
55
|
+
# @param role [String] one of "owner", "editor", "viewer"
|
|
56
|
+
def update_member_role(project, user_id, role:)
|
|
57
|
+
@http.patch(
|
|
58
|
+
"/api/v1/private/projects/#{enc(project)}/members/#{enc(user_id)}",
|
|
59
|
+
"role" => role
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# DELETE /api/v1/private/projects/:project/members/:userId
|
|
64
|
+
# Remove a member from a project team.
|
|
65
|
+
# @param project [String]
|
|
66
|
+
# @param user_id [String]
|
|
67
|
+
def remove_member(project, user_id)
|
|
68
|
+
@http.delete("/api/v1/private/projects/#{enc(project)}/members/#{enc(user_id)}")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# ── Pipeline visibility ────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
# GET /api/v1/private/projects/:project/pipelines/:pipeline/visibility
|
|
74
|
+
# Get visibility settings for a pipeline within a project.
|
|
75
|
+
# @param project [String]
|
|
76
|
+
# @param pipeline [String]
|
|
77
|
+
def get_pipeline_visibility(project, pipeline)
|
|
78
|
+
@http.get(
|
|
79
|
+
"/api/v1/private/projects/#{enc(project)}/pipelines/#{enc(pipeline)}/visibility"
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# PUT /api/v1/private/projects/:project/pipelines/:pipeline/visibility
|
|
84
|
+
# Set pipeline visibility within a project.
|
|
85
|
+
# @param project [String]
|
|
86
|
+
# @param pipeline [String]
|
|
87
|
+
# @param body [Hash]
|
|
88
|
+
def set_pipeline_visibility(project, pipeline, body = {})
|
|
89
|
+
@http.put(
|
|
90
|
+
"/api/v1/private/projects/#{enc(project)}/pipelines/#{enc(pipeline)}/visibility",
|
|
91
|
+
body
|
|
92
|
+
)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
def enc(s)
|
|
98
|
+
URI.encode_www_form_component(s.to_s)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|