melaya 0.2.0 → 0.3.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/README.md +92 -25
- data/lib/melaya/accounts.rb +13 -0
- data/lib/melaya/billing.rb +23 -0
- data/lib/melaya/connector_tools.rb +177 -0
- data/lib/melaya/connectors.rb +73 -0
- data/lib/melaya/credentials.rb +118 -0
- data/lib/melaya/evals.rb +36 -6
- data/lib/melaya/http_client.rb +124 -34
- data/lib/melaya/namespaces.rb +5 -1
- data/lib/melaya/phone.rb +23 -0
- data/lib/melaya/pipelines.rb +246 -9
- data/lib/melaya/team.rb +10 -0
- data/lib/melaya/version.rb +1 -1
- data/lib/melaya.rb +13 -3
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 47fefbce18553e5a38d414eb96ad35da805b1bb4d685e9fd809206b00d2dbe68
|
|
4
|
+
data.tar.gz: 822418410f53fc0cb03caf2fd06a8a122db8082e58ded4c68f6032dea5898965
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7680af7ede9b7cfa4e4fa2c6860c3e40260c488f2a3b6906930382b9a44ab36862ff8117ac6ce533bcac5fe5d7b8f12c4961257a61c149ebf6c07953da84e1e7
|
|
7
|
+
data.tar.gz: 6e929c21c54d44b99c0cfffa1d717ca85ca95bf0c95ecbc1d2e260e9343e8cc4383d47e38d7b9ffe965aa15798e7ee202ae2230f2fc4de75a21b15bdd2aa500e
|
data/README.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
Official SDK for the **[Melaya](https://melaya.org)** Agent Builder and flagship Mobile Device Control APIs. Trading namespaces are included only as a preview of a later product.
|
|
6
6
|
|
|
7
|
+
**Melaya products:** [Melaya Agents](https://melaya.org/en/product/agentic-framework) · [Melaya Assistant](https://melaya.org/en/product/assistant) · [Device Control](https://melaya.org/en/product/agentic-device-control) · [Browser Control](https://melaya.org/en/product/agentic-browser-control) · [MCP Server](https://melaya.org/en/product/mcp) · [Melaya Marketing](https://melaya.org/en/product/marketing)
|
|
8
|
+
|
|
7
9
|
- Zero runtime gem dependencies (stdlib `net/http`, `openssl`, `json` only).
|
|
8
10
|
- Full Agent Builder lifecycle: projects, pipelines, templates, Connectors, HITL, evals, events, billing, team, and runner management.
|
|
9
11
|
- Catalogs of **1,500+ scoped tools**, **100+ specialized subagents**, and **20+ model providers** (runtime catalog endpoints are the source of truth).
|
|
@@ -48,26 +50,30 @@ A Melaya platform key is required. "No app API required" means Device Control op
|
|
|
48
50
|
|
|
49
51
|
Configure provider credentials through Melaya Connectors first. Never include a provider key in pipeline configuration or per-run overrides.
|
|
50
52
|
|
|
53
|
+
A pipeline's run is generated **only** from `config["steps"]` — a top-level `agents` list alone produces an **empty** pipeline. Every agent a step runs must be embedded inline on that step's `"agent"` key. There is no `prompt` field: the two prompt fields are `instruction` (the task) and, optionally, `system_prompt_override`.
|
|
54
|
+
|
|
51
55
|
```ruby
|
|
52
56
|
melaya.agents.pipelines.create(
|
|
53
57
|
name: "mobile-review",
|
|
54
58
|
project: "Operations",
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
steps: [{
|
|
60
|
+
"kind" => "agent",
|
|
61
|
+
"agent" => {
|
|
62
|
+
"name" => "mobile-operator",
|
|
63
|
+
"role" => "Careful mobile operator",
|
|
64
|
+
"instruction" => "Read before acting. Never send, publish, or delete.",
|
|
65
|
+
"model" => { "provider" => "anthropic", "name" => "claude-sonnet-4-6" },
|
|
66
|
+
"agent_tools" => [
|
|
67
|
+
"phone_get_screen_tree",
|
|
68
|
+
"phone_current_app",
|
|
69
|
+
"phone_open_app",
|
|
70
|
+
"phone_click_text",
|
|
71
|
+
"phone_back",
|
|
72
|
+
"phone_wait"
|
|
73
|
+
],
|
|
74
|
+
"human_approval_tools" => []
|
|
75
|
+
}
|
|
69
76
|
}],
|
|
70
|
-
steps: [{ "kind" => "agent", "agent" => { "name" => "mobile-operator" } }],
|
|
71
77
|
maxCostUsd: 1.00
|
|
72
78
|
)
|
|
73
79
|
|
|
@@ -82,6 +88,64 @@ status = melaya.agents.pipelines.run_status("mobile-review", run_id)
|
|
|
82
88
|
melaya.events.on_run_update(run_id) { |e| puts e["event_type"] }
|
|
83
89
|
```
|
|
84
90
|
|
|
91
|
+
Other config fields worth knowing: `"hitl_mode"` (`"safe"` default | `"autonomous"` | `"payments_only"` — only `"safe"` honours each agent's `human_approval_tools`), `"connector_source"` (`"personal"` | `"project"`), `"force_local_runner"`, and `"inputs"` (declared run-input fields, see `run_inputs:` below).
|
|
92
|
+
|
|
93
|
+
`get` returns an **envelope**, not a bare config — `{ "name", "client", "config", "code", "docs" }`. To edit and save, mutate `envelope["config"]` and pass that to `update`:
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
envelope = melaya.pipelines.get("mobile-review", project: "Operations")
|
|
97
|
+
config = envelope["config"]
|
|
98
|
+
config["steps"][0]["agent"]["model"] = { "provider" => "anthropic", "name" => "claude-opus-4-8" }
|
|
99
|
+
melaya.pipelines.update("mobile-review", config: config, project: "Operations")
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Quick start: run inputs and file attachments
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
# Upload a file ahead of a run, then reference it by file_id
|
|
106
|
+
upload = melaya.pipelines.upload_run_file("mobile-review", "screenshot", File.open("shot.png", "rb"))
|
|
107
|
+
|
|
108
|
+
run = melaya.pipelines.run("mobile-review",
|
|
109
|
+
project: "Operations",
|
|
110
|
+
run_inputs: {
|
|
111
|
+
"brief" => "Review the attached screenshot for policy violations.",
|
|
112
|
+
"values" => { "screenshot" => { "file_id" => upload["file_id"] } }
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Download a run's own input file back (raw bytes — do not JSON-parse)
|
|
117
|
+
bytes = melaya.pipelines.run_input_file("mobile-review", run["run_id"], 0)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Quick start: call a connector tool directly
|
|
121
|
+
|
|
122
|
+
Call any of your already-connected service tools (Gmail, Slack, Stripe, ...) —
|
|
123
|
+
the same surface the MCP server and the Melaya Assistant use. This is
|
|
124
|
+
different from `melaya.connectors`, which only stores project credentials.
|
|
125
|
+
|
|
126
|
+
Reads run immediately. Writes default to `approval: "required"`, which stages
|
|
127
|
+
the same approval card the Assistant raises in the Melaya app and returns
|
|
128
|
+
HTTP 202 (a success, not an error) with a `requestId` to poll; pass
|
|
129
|
+
`approval: "none"` to run a write immediately instead (still audit-logged).
|
|
130
|
+
Money-moving/trading tools are always refused, under both approval modes. No
|
|
131
|
+
method here ever accepts or returns a credential value.
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
melaya.agents.connector_tools.services # { "services" => [...], "toolCounts" => {...} }
|
|
135
|
+
melaya.agents.connector_tools.search("unread email") # discover tools by keyword
|
|
136
|
+
melaya.agents.connector_tools.describe("gmail_list_messages")
|
|
137
|
+
|
|
138
|
+
# Read — runs immediately
|
|
139
|
+
result = melaya.agents.connector_tools.call("gmail_list_messages", args: { max_results: 5 })
|
|
140
|
+
puts result["result"]
|
|
141
|
+
|
|
142
|
+
# Write — staged for approval by default; block until decided (or it times out)
|
|
143
|
+
outcome = melaya.agents.connector_tools.call_and_wait("gmail_send", args: { to: "a@b.com" })
|
|
144
|
+
|
|
145
|
+
# Also reachable via the flat alias:
|
|
146
|
+
melaya.connector_tools.services
|
|
147
|
+
```
|
|
148
|
+
|
|
85
149
|
## Trading quick start (preview)
|
|
86
150
|
|
|
87
151
|
```ruby
|
|
@@ -201,25 +265,28 @@ Public market-data and account/strategy reads work with the `mk_` key alone. **L
|
|
|
201
265
|
| Auth | `auth.login`, `verify_mfa`, `register`, `verify_signup`, `resend_verification`, `me`, `check`, `change_password`, `forgot_password`, `reset_password`, `mobile_handoff`, `permissions`, `refresh` |
|
|
202
266
|
| MFA | `auth.mfa_status`, `mfa_setup`, `mfa_confirm` (also via `melaya.platform.mfa`) |
|
|
203
267
|
| Projects | `projects.list`, `create`, `rename`, `runner_projects` |
|
|
204
|
-
| Connectors | `connectors.connected_services`, `set`, `delete`, `env_handle`, `google_oauth_start` |
|
|
205
|
-
|
|
|
206
|
-
|
|
|
268
|
+
| Connectors | `connectors.connected_services`, `set`, `delete`, `env_handle`, `google_oauth_start`, `apply_personal`, `shared_by`, `google_status`, `google_set_default`, `google_disconnect`, `db_test_start`, `db_test_status` |
|
|
269
|
+
| Connector Tools | `connector_tools.services`, `search`, `describe`, `test`, `connect`, `call`, `call_status`, `call_and_wait` |
|
|
270
|
+
| Credentials | `credentials.list`, `connected_services`, `get`, `set`, `delete`, `test`, `list_models`, `google_status`, `google_set_default`, `google_disconnect`, `db_test_start`, `db_test_status`, `telegram_qr_start`, `telegram_qr_poll`, `whatsapp_signup_config`, `whatsapp_signup_exchange`, `tiktok_creator_info`, `substack_email_link_send`, `substack_email_link_redeem`, plus operator-profile, OAuth, and RAG helpers |
|
|
271
|
+
| Pipelines | `pipelines.create`, `get`, `update`, `delete_pipeline`, `list_pipelines`, `run`, `upload_run_file`, `run_inputs`, `run_input_file`, `run_active`, `run_ids`, `run_status`, `cancel_run`, `outputs`, `output`, `preview_code`, `tools`, `subagents`, `instantiate_template`, `build_with_ai` |
|
|
272
|
+
| Pipeline docs & RAG | `pipelines.list_docs`, `upload_doc`, `delete_doc`, `upload_retrieval_doc`, `ingest_retrieval`, `delete_retrieval_doc` |
|
|
207
273
|
| Runs & traces | `pipelines.list`, `recent`, `count`, `traces`, `trace`, `trace_stats`, `delete_traces` |
|
|
274
|
+
| Tool-call audit | `pipelines.project_tool_calls`, `project_tool_call_facets`, `tool_call_detail` |
|
|
208
275
|
| Schedules | `pipelines.list_schedules`, `get_schedule`, `upsert_schedule`, `pause_schedule`, `resume_schedule` |
|
|
209
276
|
| Overview | `pipelines.overview`, `model_prices`, `chart_data`, `cost_breakdown`, `server_version` |
|
|
210
277
|
| Templates | `templates.list`, `list_global`, `list_validated`, `save`, `update`, `duplicate`, `delete`, `share`, `share_targets`, `list_assignments`, `assign(id, user_id:` \| `project_id:)`, `unassign(id, user_id:` \| `project_id:)` |
|
|
211
|
-
| Phone | `phone.pair`, `list_devices`, `revoke_device`, `screen_tree`, `list_apps`, `set_allowed_apps`, `register_active_run` |
|
|
278
|
+
| Phone | `phone.pair`, `list_devices`, `revoke_device`, `screen_tree`, `list_apps`, `set_allowed_apps`, `register_active_run`, `grant_app`, `request_cast` |
|
|
212
279
|
| HITL | `hitl.pending`, `history`, `approve`, `reject`, `bulk_decide`, `run_tool_stats`, `run_tool_stats_by_agent`, `run_messages`, `run_tool_calls` |
|
|
213
|
-
| Evals | `evals.list_runs`, `summary`, `run_detail`, `compare`, `memory_graph`, `run_memory`, `crew_memory`, `benchmarks` |
|
|
280
|
+
| Evals | `evals.list_runs`, `summary`, `run_detail`, `compare`, `memory_graph`, `run_memory`, `crew_memory`, `edit_crew_memory_entry`, `delete_crew_memory_entry`, `benchmarks` |
|
|
214
281
|
| Events | `events.on_run_update`, `on_init_phase`, `on_project_event`, `on_hitl_approval`, `on_pipeline_created`, `on_pipeline_updated`, `on_pipeline_deleted`, `leave_run`, `leave_project`, `close` |
|
|
215
|
-
| Billing | `billing.subscription`, `create_checkout`, `create_portal`, `plans`, `credits`, `ai_credits`, `portfolio_ideas_credits`, `risk_monitoring_credits` |
|
|
216
|
-
| Accounts | `accounts.export_data`, `remove_key`, `update_profile` |
|
|
282
|
+
| Billing | `billing.subscription`, `create_checkout`, `create_portal`, `plans`, `credits`, `ai_credits`, `portfolio_ideas_credits`, `risk_monitoring_credits`, `ambassador_perk`, `redeem_code`, `reserved_promo` |
|
|
283
|
+
| Accounts | `accounts.export_data`, `remove_key`, `update_profile`, `resend_email_verification`, `verify_email` |
|
|
217
284
|
| Runner | `runner.create_token`, `list_tokens`, `revoke_token` |
|
|
218
|
-
| Team | `team.list_members`, `invite`, `create_invite_link`, `accept_invite`, `update_member_role`, `remove_member`, `get_pipeline_visibility`, `set_pipeline_visibility` |
|
|
285
|
+
| Team | `team.list_members`, `invite`, `create_invite_link`, `accept_invite`, `update_member_role`, `remove_member`, `transfer_ownership`, `get_pipeline_visibility`, `set_pipeline_visibility` |
|
|
219
286
|
| Assistant | `assistant.get_profile`, `set_profile` |
|
|
220
287
|
| Bugs | `bugs.create`, `list_mine`, `get`, `add_comment`, `list_notifications`, `mark_notifications_read` |
|
|
221
288
|
|
|
222
|
-
Every area is also reachable through the domain namespaces: `melaya.agents.*` (pipelines/runs, hitl, assistant, phone, evals, models) and `melaya.platform.*` (projects, credentials, connectors, billing, team, templates, overview, runner, auth, mfa, accounts, bugs, events).
|
|
289
|
+
Every area is also reachable through the domain namespaces: `melaya.agents.*` (pipelines/runs, hitl, assistant, phone, evals, models, connector_tools) and `melaya.platform.*` (projects, credentials, connectors, billing, team, templates, overview, runner, auth, mfa, accounts, bugs, events).
|
|
223
290
|
|
|
224
291
|
### Trading (preview — not generally available)
|
|
225
292
|
|
|
@@ -237,7 +304,7 @@ Every area is also reachable through the domain namespaces: `melaya.agents.*` (p
|
|
|
237
304
|
| Public streaming | `stream.ticker`, `orderbook`, `ohlcv`, `trades`, `liquidations` |
|
|
238
305
|
| Private streaming | `stream.strategies`, `stream.private` |
|
|
239
306
|
|
|
240
|
-
Full docs: **[melaya.org/
|
|
307
|
+
Full docs: **[melaya.org/documentation](https://melaya.org/documentation)**.
|
|
241
308
|
|
|
242
309
|
## License
|
|
243
310
|
|
data/lib/melaya/accounts.rb
CHANGED
|
@@ -19,6 +19,19 @@ module Melaya
|
|
|
19
19
|
@http.post("/api/v1/private/accounts/export")
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
# POST /api/v1/private/accounts/resend-email-verification
|
|
23
|
+
# Send a verification email to the signed-in account's saved address.
|
|
24
|
+
def resend_email_verification
|
|
25
|
+
@http.post("/api/v1/private/accounts/resend-email-verification")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# POST /api/v1/private/accounts/verify-email
|
|
29
|
+
# Confirm saved-email ownership without creating a login session.
|
|
30
|
+
# @param token [String] 64 hex-char verification token
|
|
31
|
+
def verify_email(token)
|
|
32
|
+
@http.post("/api/v1/private/accounts/verify-email", "token" => token)
|
|
33
|
+
end
|
|
34
|
+
|
|
22
35
|
# DELETE /api/v1/private/keys/:keyId
|
|
23
36
|
# Remove a stored CEX API key.
|
|
24
37
|
# @param key_id [String]
|
data/lib/melaya/billing.rb
CHANGED
|
@@ -40,6 +40,29 @@ module Melaya
|
|
|
40
40
|
@http.get("/api/v1/billing/plans")
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
+
# ── Promos ────────────────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
# GET /api/v1/private/billing/ambassador-perk
|
|
46
|
+
# The ambassador discount the caller is entitled to, or nil.
|
|
47
|
+
def ambassador_perk
|
|
48
|
+
@http.get("/api/v1/private/billing/ambassador-perk")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# POST /api/v1/private/billing/redeem-code
|
|
52
|
+
# Redeem a single-use promo code to the caller's account; the discount
|
|
53
|
+
# applies on the next checkout.
|
|
54
|
+
# @param code [String]
|
|
55
|
+
def redeem_code(code)
|
|
56
|
+
@http.post("/api/v1/private/billing/redeem-code", "code" => code)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# GET /api/v1/private/billing/reserved-promo
|
|
60
|
+
# The caller's active reserved promo (for the subscription modal to
|
|
61
|
+
# reflect), or nil.
|
|
62
|
+
def reserved_promo
|
|
63
|
+
@http.get("/api/v1/private/billing/reserved-promo")
|
|
64
|
+
end
|
|
65
|
+
|
|
43
66
|
# ── Credits ───────────────────────────────────────────────────────────────
|
|
44
67
|
|
|
45
68
|
# GET /api/v1/private/accounts/credits
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Melaya
|
|
4
|
+
# Connector Tools API — call any of the user's connected-service tools
|
|
5
|
+
# (Gmail, Slack, Stripe, ...) directly. Same surface the MCP server and the
|
|
6
|
+
# Melaya Assistant use: list connected services, discover tools by keyword,
|
|
7
|
+
# describe one, test a stored connector, start connecting a service, and
|
|
8
|
+
# call a tool.
|
|
9
|
+
#
|
|
10
|
+
# Do not confuse this with +melaya.connectors+ (project-scoped credential
|
|
11
|
+
# storage) — this module only calls already-connected tools.
|
|
12
|
+
#
|
|
13
|
+
# Maps to /api/v1/private/connector-tools/*.
|
|
14
|
+
#
|
|
15
|
+
# Read tools run immediately. A write tool defaults to +approval: "required"+
|
|
16
|
+
# and is staged as the same approval card the Assistant raises in the
|
|
17
|
+
# Melaya app: +call+ returns HTTP 202 (a success, not an error) with a
|
|
18
|
+
# +requestId+; poll +call_status+ — or use +call_and_wait+ — until the user
|
|
19
|
+
# has decided, and the write runs exactly once, on the first poll after
|
|
20
|
+
# approval. Pass +approval: "none"+ to run a write immediately instead; it
|
|
21
|
+
# is still audit-logged. Tools that move money or trade are always
|
|
22
|
+
# refused, under BOTH approval modes. No method here ever accepts or
|
|
23
|
+
# returns a credential value.
|
|
24
|
+
#
|
|
25
|
+
# @example
|
|
26
|
+
# services = melaya.agents.connector_tools.services
|
|
27
|
+
# found = melaya.agents.connector_tools.search("unread email")
|
|
28
|
+
# info = melaya.agents.connector_tools.describe("gmail_list_messages")
|
|
29
|
+
#
|
|
30
|
+
# # Read — runs immediately
|
|
31
|
+
# read = melaya.agents.connector_tools.call("gmail_list_messages", args: { max_results: 5 })
|
|
32
|
+
# puts read["result"]
|
|
33
|
+
#
|
|
34
|
+
# # Write — staged for approval by default
|
|
35
|
+
# staged = melaya.agents.connector_tools.call("gmail_send", args: { to: "a@b.com" })
|
|
36
|
+
# outcome = melaya.agents.connector_tools.call_status(staged["requestId"])
|
|
37
|
+
#
|
|
38
|
+
# # Or block until the approval is decided (or times out)
|
|
39
|
+
# outcome = melaya.agents.connector_tools.call_and_wait("gmail_send", args: { to: "a@b.com" })
|
|
40
|
+
#
|
|
41
|
+
# # Also reachable via the flat alias:
|
|
42
|
+
# melaya.connector_tools.services
|
|
43
|
+
class ConnectorToolsAPI
|
|
44
|
+
BASE = "/api/v1/private/connector-tools"
|
|
45
|
+
|
|
46
|
+
# Statuses that end a +call_and_wait+ poll loop.
|
|
47
|
+
TERMINAL_STATUSES = %w[done rejected expired].freeze
|
|
48
|
+
|
|
49
|
+
# Defaults mirrored from the spec: poll every 3s, give up after 10 minutes.
|
|
50
|
+
DEFAULT_POLL_INTERVAL_S = 3
|
|
51
|
+
DEFAULT_WAIT_TIMEOUT_S = 600
|
|
52
|
+
|
|
53
|
+
def initialize(http)
|
|
54
|
+
@http = http
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# GET /connector-tools/services
|
|
58
|
+
# Connected services and built-in tool counts. Names only.
|
|
59
|
+
# @return [Hash] { "services" => [String], "builtIn" => "melaya_core",
|
|
60
|
+
# "toolCounts" => { service => { "readTools" => Integer, "writeTools" => Integer } } }
|
|
61
|
+
def services
|
|
62
|
+
@http.get("#{BASE}/services")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# GET /connector-tools/search?q=&limit=
|
|
66
|
+
# Discover tools by plain business keywords (e.g. "unread email").
|
|
67
|
+
# @param q [String] required — plain business keywords
|
|
68
|
+
# @param limit [Integer, nil] 1-50, default 15
|
|
69
|
+
# @return [Hash] { "query" => String, "services" => [String], "tools" => [Hash] }
|
|
70
|
+
def search(q, limit: nil)
|
|
71
|
+
@http.get("#{BASE}/search", compact("q" => q, "limit" => limit))
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# GET /connector-tools/tools/:tool
|
|
75
|
+
# Full description and parameters for one tool.
|
|
76
|
+
# @param tool [String]
|
|
77
|
+
# @return [Hash] ToolInfo — raises MelayaError (404) when unknown, or not
|
|
78
|
+
# unlocked by any of your connected services.
|
|
79
|
+
def describe(tool)
|
|
80
|
+
@http.get("#{BASE}/tools/#{enc(tool)}")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# POST /connector-tools/test { service }
|
|
84
|
+
# Test the STORED credential for a connected service.
|
|
85
|
+
# Raises MelayaError (504, code "timeout") if the service does not
|
|
86
|
+
# answer within 30 seconds server-side.
|
|
87
|
+
# @param service [String]
|
|
88
|
+
# @return [Hash] { "service" => String, "success" => Boolean, "message" => String }
|
|
89
|
+
def test(service)
|
|
90
|
+
@http.post("#{BASE}/test", "service" => service)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# POST /connector-tools/connect { service }
|
|
94
|
+
# Start connecting a service. Never accepts a secret — OAuth services
|
|
95
|
+
# return an authorization URL to open; other kinds return where to store
|
|
96
|
+
# the credential in the Melaya app.
|
|
97
|
+
# @param service [String]
|
|
98
|
+
# @return [Hash] { "service", "kind" => "oauth"|"oauth_unavailable"|"interactive_login"|"api_key",
|
|
99
|
+
# "authorizationUrl" => String (oauth only), "connectUrl" => String, "message" => String }
|
|
100
|
+
def connect(service)
|
|
101
|
+
@http.post("#{BASE}/connect", "service" => service)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# POST /connector-tools/call { tool, args, approval }
|
|
105
|
+
#
|
|
106
|
+
# A read tool (or a write with approval: "none") runs immediately and
|
|
107
|
+
# returns 200 with the result. A write with approval: "required"
|
|
108
|
+
# (default) is staged and returns 202 — a success, not an error — with a
|
|
109
|
+
# +requestId+ to poll via +call_status+ (or +call_and_wait+).
|
|
110
|
+
#
|
|
111
|
+
# Money-moving/trading tools are refused under both approval modes, and
|
|
112
|
+
# any other error response (400/403/404/502/503) is raised as a
|
|
113
|
+
# MelayaError by the underlying HTTP client, same as every other call in
|
|
114
|
+
# this SDK.
|
|
115
|
+
#
|
|
116
|
+
# @param tool [String]
|
|
117
|
+
# @param args [Hash] tool arguments
|
|
118
|
+
# @param approval [String] "required" (default, stage for approval) or "none" (run immediately)
|
|
119
|
+
# @return [Hash] { "status" => "done", "tool", "readOnly", "result" => String } or
|
|
120
|
+
# { "status" => "pending_approval", "tool", "requestId", "message" }
|
|
121
|
+
def call(tool, args: {}, approval: "required")
|
|
122
|
+
@http.post("#{BASE}/call", "tool" => tool, "args" => args || {}, "approval" => approval)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# GET /connector-tools/calls/:requestId
|
|
126
|
+
# Outcome of a staged write. Raises MelayaError (404) when the request is
|
|
127
|
+
# unknown or has expired.
|
|
128
|
+
# @param request_id [String]
|
|
129
|
+
# @return [Hash] one of:
|
|
130
|
+
# { "requestId", "tool", "status" => "pending" | "running" | "expired" }
|
|
131
|
+
# { "requestId", "tool", "status" => "done", "ok" => Boolean, "result"/"error" => String }
|
|
132
|
+
# { "requestId", "tool", "status" => "rejected", "reason" => String }
|
|
133
|
+
def call_status(request_id)
|
|
134
|
+
@http.get("#{BASE}/calls/#{enc(request_id)}")
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Helper: call a tool and, if it is staged for approval, block polling
|
|
138
|
+
# +call_status+ until the user has decided (done/rejected) or the
|
|
139
|
+
# request expires, then return that outcome. If the tool ran immediately
|
|
140
|
+
# (a read, or approval: "none"), returns that response as-is with no
|
|
141
|
+
# polling. Synchronous/blocking, like the rest of this SDK.
|
|
142
|
+
#
|
|
143
|
+
# @param tool [String]
|
|
144
|
+
# @param args [Hash]
|
|
145
|
+
# @param approval [String] "required" (default) or "none"
|
|
146
|
+
# @param poll_interval_s [Numeric] seconds between polls, default 3
|
|
147
|
+
# @param timeout_s [Numeric] give up polling after this many seconds and
|
|
148
|
+
# return the last-seen (non-terminal) status, default 600 (10 minutes)
|
|
149
|
+
# @return [Hash] the immediate +call+ response, or the final +call_status+ outcome
|
|
150
|
+
def call_and_wait(tool, args: {}, approval: "required",
|
|
151
|
+
poll_interval_s: DEFAULT_POLL_INTERVAL_S,
|
|
152
|
+
timeout_s: DEFAULT_WAIT_TIMEOUT_S)
|
|
153
|
+
resp = call(tool, args: args, approval: approval)
|
|
154
|
+
return resp unless resp.is_a?(Hash) && resp["status"] == "pending_approval"
|
|
155
|
+
|
|
156
|
+
request_id = resp["requestId"]
|
|
157
|
+
deadline = Time.now + timeout_s
|
|
158
|
+
loop do
|
|
159
|
+
outcome = call_status(request_id)
|
|
160
|
+
return outcome if outcome.is_a?(Hash) && TERMINAL_STATUSES.include?(outcome["status"])
|
|
161
|
+
return outcome if Time.now >= deadline
|
|
162
|
+
|
|
163
|
+
sleep(poll_interval_s)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
private
|
|
168
|
+
|
|
169
|
+
def enc(s)
|
|
170
|
+
URI.encode_www_form_component(s.to_s)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def compact(hash)
|
|
174
|
+
hash.reject { |_, v| v.nil? }
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
data/lib/melaya/connectors.rb
CHANGED
|
@@ -58,6 +58,79 @@ module Melaya
|
|
|
58
58
|
@http.post("/api/v1/private/projects/#{enc(project)}/connectors/google/oauth", body)
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
+
# POST /api/v1/private/projects/:project/connectors/:service/apply-personal
|
|
62
|
+
# Share the caller's OWN personal connector credential into the project
|
|
63
|
+
# pool (editor or owner only). Values stay server-side.
|
|
64
|
+
# @param project [String]
|
|
65
|
+
# @param service [String]
|
|
66
|
+
# @param google_capabilities [Array<String>, nil] restrict a Google
|
|
67
|
+
# connector to these capabilities only, e.g. "gmail", "calendar",
|
|
68
|
+
# "drive", "sheets", "docs", "search_console", "youtube", "google_ads",
|
|
69
|
+
# "analytics", "meet", "slides"
|
|
70
|
+
def apply_personal(project, service, google_capabilities: nil)
|
|
71
|
+
body = compact("googleCapabilities" => google_capabilities)
|
|
72
|
+
@http.post("/api/v1/private/projects/#{enc(project)}/connectors/#{enc(service)}/apply-personal", body)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# GET /api/v1/private/projects/:project/connectors/shared-by
|
|
76
|
+
# Which member shared each connected project connector (usernames only —
|
|
77
|
+
# values are never returned).
|
|
78
|
+
# @param project [String]
|
|
79
|
+
def shared_by(project)
|
|
80
|
+
@http.get("/api/v1/private/projects/#{enc(project)}/connectors/shared-by")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# ── Google OAuth (status / defaults / disconnect) ─────────────────────────
|
|
84
|
+
|
|
85
|
+
# GET /api/v1/private/projects/:project/connectors/google/status
|
|
86
|
+
# List the Google OAuth capabilities actually granted to a project.
|
|
87
|
+
# @param project [String]
|
|
88
|
+
def google_status(project)
|
|
89
|
+
@http.get("/api/v1/private/projects/#{enc(project)}/connectors/google/status")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# PUT /api/v1/private/projects/:project/connectors/google/default
|
|
93
|
+
# Select the project's connected Google account used by one capability.
|
|
94
|
+
# @param project [String]
|
|
95
|
+
# @param capability [String] e.g. "gmail", "calendar", "drive", "sheets", ...
|
|
96
|
+
# @param account_id [String] 24-hex-char connected-account id
|
|
97
|
+
def google_set_default(project, capability, account_id)
|
|
98
|
+
@http.put("/api/v1/private/projects/#{enc(project)}/connectors/google/default",
|
|
99
|
+
"capability" => capability, "accountId" => account_id)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# DELETE /api/v1/private/projects/:project/connectors/google/access
|
|
103
|
+
# Disconnect one Google product, or an entire Google account, from a project.
|
|
104
|
+
# @param project [String]
|
|
105
|
+
# @param account_id [String] 24-hex-char connected-account id
|
|
106
|
+
# @param capability [String, nil] omit to disconnect the whole account
|
|
107
|
+
def google_disconnect(project, account_id, capability: nil)
|
|
108
|
+
body = compact("accountId" => account_id, "capability" => capability)
|
|
109
|
+
@http.delete("/api/v1/private/projects/#{enc(project)}/connectors/google/access", {}, body)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# ── Database connector test (runner-probed) ───────────────────────────────
|
|
113
|
+
|
|
114
|
+
# POST /api/v1/private/projects/:project/connectors/db-test
|
|
115
|
+
# Test a project database connector from the user's own runner (reaches
|
|
116
|
+
# IP-allow-listed / VPC hosts a cloud probe never could).
|
|
117
|
+
# @param project [String]
|
|
118
|
+
# @param service [String] one of "postgres", "mysql", "snowflake", "databricks", "sqlite"
|
|
119
|
+
# @param credentials [Hash, nil] freshly-typed credentials to test instead of the stored ones
|
|
120
|
+
# @return [Hash] { "sessionId" => String, ... }
|
|
121
|
+
def db_test_start(project, service, credentials: nil)
|
|
122
|
+
body = compact("service" => service, "credentials" => credentials)
|
|
123
|
+
@http.post("/api/v1/private/projects/#{enc(project)}/connectors/db-test", body)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# GET /api/v1/private/projects/:project/connectors/db-test/:sessionId
|
|
127
|
+
# Poll a project DB connector runner-test result.
|
|
128
|
+
# @param project [String]
|
|
129
|
+
# @param session_id [String]
|
|
130
|
+
def db_test_status(project, session_id)
|
|
131
|
+
@http.get("/api/v1/private/projects/#{enc(project)}/connectors/db-test/#{enc(session_id)}")
|
|
132
|
+
end
|
|
133
|
+
|
|
61
134
|
private
|
|
62
135
|
|
|
63
136
|
def enc(s)
|
data/lib/melaya/credentials.rb
CHANGED
|
@@ -242,6 +242,124 @@ module Melaya
|
|
|
242
242
|
@http.post("/api/v1/private/credentials/telegram/auth/2fa", body)
|
|
243
243
|
end
|
|
244
244
|
|
|
245
|
+
# ── Telegram user QR login (alternative to the phone-number flow above) ────
|
|
246
|
+
|
|
247
|
+
# POST /api/v1/private/credentials/telegram/auth/qr/start
|
|
248
|
+
# Start Telegram user QR login.
|
|
249
|
+
# @param api_id [Integer]
|
|
250
|
+
# @param api_hash [String]
|
|
251
|
+
# @return [Hash] { "handle" => String, ... } — handle starts with "tgauth_"
|
|
252
|
+
def telegram_qr_start(api_id, api_hash)
|
|
253
|
+
@http.post("/api/v1/private/credentials/telegram/auth/qr/start",
|
|
254
|
+
"api_id" => api_id, "api_hash" => api_hash)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# POST /api/v1/private/credentials/telegram/auth/qr/poll
|
|
258
|
+
# Poll Telegram user QR login.
|
|
259
|
+
# @param handle [String] starts with "tgauth_"
|
|
260
|
+
def telegram_qr_poll(handle)
|
|
261
|
+
@http.post("/api/v1/private/credentials/telegram/auth/qr/poll", "handle" => handle)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# ── Google OAuth (status / defaults / disconnect) ──────────────────────────
|
|
265
|
+
|
|
266
|
+
# GET /api/v1/private/credentials/google/status
|
|
267
|
+
# List the Google OAuth capabilities actually granted to the caller.
|
|
268
|
+
def google_status
|
|
269
|
+
@http.get("/api/v1/private/credentials/google/status")
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# PUT /api/v1/private/credentials/google/default
|
|
273
|
+
# Select the connected Google account used by one capability.
|
|
274
|
+
# @param capability [String] e.g. "gmail", "calendar", "drive", "sheets",
|
|
275
|
+
# "docs", "search_console", "youtube", "google_ads", "analytics", "meet", "slides"
|
|
276
|
+
# @param account_id [String] 24-hex-char connected-account id
|
|
277
|
+
def google_set_default(capability, account_id)
|
|
278
|
+
@http.put("/api/v1/private/credentials/google/default",
|
|
279
|
+
"capability" => capability, "accountId" => account_id)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# DELETE /api/v1/private/credentials/google/access
|
|
283
|
+
# Disconnect one Google product, or an entire Google account.
|
|
284
|
+
# @param account_id [String] 24-hex-char connected-account id
|
|
285
|
+
# @param capability [String, nil] omit to disconnect the whole account
|
|
286
|
+
def google_disconnect(account_id, capability: nil)
|
|
287
|
+
body = compact("accountId" => account_id, "capability" => capability)
|
|
288
|
+
@http.delete("/api/v1/private/credentials/google/access", {}, body)
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# ── Database connector test (runner-probed) ────────────────────────────────
|
|
292
|
+
|
|
293
|
+
# POST /api/v1/private/credentials/db-test
|
|
294
|
+
# Test a database connector from the user's own runner (reaches
|
|
295
|
+
# IP-allow-listed / VPC hosts a cloud probe never could).
|
|
296
|
+
# @param service [String] one of "postgres", "mysql", "snowflake", "databricks", "sqlite"
|
|
297
|
+
# @param credentials [Hash, nil] freshly-typed credentials to test instead of the stored ones
|
|
298
|
+
# @return [Hash] { "sessionId" => String, ... }
|
|
299
|
+
def db_test_start(service, credentials: nil)
|
|
300
|
+
body = compact("service" => service, "credentials" => credentials)
|
|
301
|
+
@http.post("/api/v1/private/credentials/db-test", body)
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# GET /api/v1/private/credentials/db-test/:sessionId
|
|
305
|
+
# Poll a DB connector runner-test result.
|
|
306
|
+
# @param session_id [String]
|
|
307
|
+
def db_test_status(session_id)
|
|
308
|
+
@http.get("/api/v1/private/credentials/db-test/#{enc(session_id)}")
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# ── WhatsApp Embedded Signup ────────────────────────────────────────────────
|
|
312
|
+
|
|
313
|
+
# GET /api/v1/private/credentials/whatsapp/embedded-signup/config
|
|
314
|
+
# WhatsApp Embedded Signup config (appId/configId) for the client SDK.
|
|
315
|
+
def whatsapp_signup_config
|
|
316
|
+
@http.get("/api/v1/private/credentials/whatsapp/embedded-signup/config")
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# POST /api/v1/private/credentials/whatsapp/embedded-signup/exchange
|
|
320
|
+
# Exchange a WhatsApp Embedded Signup code for a connected number.
|
|
321
|
+
# @param code [String]
|
|
322
|
+
# @param phone_number_id [String]
|
|
323
|
+
# @param waba_id [String]
|
|
324
|
+
# @param project [String, nil]
|
|
325
|
+
def whatsapp_signup_exchange(code:, phone_number_id:, waba_id:, project: nil)
|
|
326
|
+
body = compact(
|
|
327
|
+
"code" => code,
|
|
328
|
+
"phoneNumberId" => phone_number_id,
|
|
329
|
+
"wabaId" => waba_id,
|
|
330
|
+
"project" => project
|
|
331
|
+
)
|
|
332
|
+
@http.post("/api/v1/private/credentials/whatsapp/embedded-signup/exchange", body)
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# ── TikTok ──────────────────────────────────────────────────────────────────
|
|
336
|
+
|
|
337
|
+
# GET /api/v1/private/credentials/tiktok/creator-info
|
|
338
|
+
# The connected TikTok account's creator info (nickname, allowed privacy
|
|
339
|
+
# levels, interaction availability) for the compliant Post-to-TikTok
|
|
340
|
+
# approval UI.
|
|
341
|
+
def tiktok_creator_info
|
|
342
|
+
@http.get("/api/v1/private/credentials/tiktok/creator-info")
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# ── Substack email-link sign-in ─────────────────────────────────────────────
|
|
346
|
+
|
|
347
|
+
# POST /api/v1/private/credentials/substack/email-link
|
|
348
|
+
# Ask Substack to email a sign-in link.
|
|
349
|
+
# @param email [String]
|
|
350
|
+
def substack_email_link_send(email)
|
|
351
|
+
@http.post("/api/v1/private/credentials/substack/email-link", "email" => email)
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# POST /api/v1/private/credentials/substack/email-link/redeem
|
|
355
|
+
# Finish Substack sign-in with the emailed link.
|
|
356
|
+
# @param link [String]
|
|
357
|
+
# @param email [String, nil]
|
|
358
|
+
def substack_email_link_redeem(link, email: nil)
|
|
359
|
+
body = compact("link" => link, "email" => email)
|
|
360
|
+
@http.post("/api/v1/private/credentials/substack/email-link/redeem", body)
|
|
361
|
+
end
|
|
362
|
+
|
|
245
363
|
private
|
|
246
364
|
|
|
247
365
|
def enc(s)
|