agentd 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 +7 -0
- data/bin/agentd +6 -0
- data/lib/agentd/agent.rb +162 -0
- data/lib/agentd/cli.rb +425 -0
- data/lib/agentd/client.rb +71 -0
- data/lib/agentd/config.rb +34 -0
- data/lib/agentd/error.rb +7 -0
- data/lib/agentd/runner.rb +184 -0
- data/lib/agentd/version.rb +3 -0
- data/lib/agentd.rb +35 -0
- metadata +92 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c69b14cbd589d9cb292361a0bdc5344e18c21878746483ab1362c0a550ff374c
|
|
4
|
+
data.tar.gz: 2116165a7cc31c3b598a973e445a0079c1f9eebc4f3976e13ded6becd2842353
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 7602d5b814b79a6ae0e6756bd82db321647e3d3f57f0f551c5b0be294c1912ef66e98685710b7b06dc1b9efc81afd93226b36d932d5ebcb666f363612abd4430
|
|
7
|
+
data.tar.gz: 89d23f7fe4d4ee33dc59e3cbda85e884c1d7079ad5b613f67273c1ab2507c3ec416e495b135e9eb20b7b650b00b344736f2c73aed0246eaa52d6c6825e56385f
|
data/bin/agentd
ADDED
data/lib/agentd/agent.rb
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
module Agentd
|
|
2
|
+
# High-level agent interface. Wraps the MCP tools in plain Ruby methods.
|
|
3
|
+
#
|
|
4
|
+
# Usage (existing agent):
|
|
5
|
+
# agent = Agentd::Agent.new(api_key: "...", endpoint: "http://localhost:3000")
|
|
6
|
+
# agent.publish(:note, body: "Hello world")
|
|
7
|
+
# agent.send_message(:email, to: "someone@example.com", body: "Hi")
|
|
8
|
+
# task = agent.delegate_task(to: "researcher-001", title: "...", instructions: "...")
|
|
9
|
+
#
|
|
10
|
+
# Usage (provisioning a new agent):
|
|
11
|
+
# attrs = Agentd::Agent.provision(handle: "my-agent", capabilities: ["research"])
|
|
12
|
+
# agent = Agentd::Agent.new(api_key: attrs[:api_key])
|
|
13
|
+
#
|
|
14
|
+
class Agent
|
|
15
|
+
attr_reader :client, :identity
|
|
16
|
+
|
|
17
|
+
def initialize(api_key:, endpoint: Agentd.endpoint)
|
|
18
|
+
@client = Client.new(api_key:, endpoint:)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# --- Provisioning ---
|
|
22
|
+
|
|
23
|
+
def self.provision(handle:, endpoint: Agentd.endpoint, **opts)
|
|
24
|
+
admin_client = Client.new(api_key: nil, endpoint:)
|
|
25
|
+
admin_client.provision(handle:, **opts)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# --- Identity ---
|
|
29
|
+
|
|
30
|
+
def identity
|
|
31
|
+
@identity ||= client.tool("get_identity")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def handle = identity["handle"]
|
|
35
|
+
def did = identity["did"]
|
|
36
|
+
def email = identity["email"]
|
|
37
|
+
def nostr_npub = identity["nostr_npub"]
|
|
38
|
+
|
|
39
|
+
# --- Publishing ---
|
|
40
|
+
|
|
41
|
+
def publish(type, **content)
|
|
42
|
+
client.tool("publish", type: type.to_s, content: content.transform_keys(&:to_s))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def publications(type: nil, page: 1, per: 20)
|
|
46
|
+
client.tool("list_publications", **{ type:, page:, per: }.compact)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# --- Context store ---
|
|
50
|
+
|
|
51
|
+
def context_get(key) = client.tool("context_get", key:)["value"]
|
|
52
|
+
def context_set(key, value) = client.tool("context_set", key:, value:)
|
|
53
|
+
def context_delete(key) = client.tool("context_delete", key:)
|
|
54
|
+
def context_list = client.tool("context_list")
|
|
55
|
+
def context_all
|
|
56
|
+
context_list.each_with_object({}) { |key, h| h[key] = context_get(key) }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# --- Signing & verification ---
|
|
60
|
+
|
|
61
|
+
def sign(payload)
|
|
62
|
+
client.tool("sign", payload: payload)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def verify(payload:, signature:, wallet_address:)
|
|
66
|
+
client.tool("verify", payload:, signature:, wallet_address:)["valid"]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# --- Tasks ---
|
|
70
|
+
|
|
71
|
+
def inbox(limit: 10)
|
|
72
|
+
client.tool("inbox_peek", limit:)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def claim_task(task_id)
|
|
76
|
+
client.tool("task_claim", task_id:)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def complete_task(task_id, result:)
|
|
80
|
+
client.tool("task_complete", task_id:, result:)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def fail_task(task_id, reason:)
|
|
84
|
+
client.tool("task_fail", task_id:, reason:)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def delegate_task(to:, title:, instructions:, payload: {})
|
|
88
|
+
client.tool("task_delegate", handle: to, title:, instructions:, payload:)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def task_result(task_id)
|
|
92
|
+
client.tool("task_result", task_id:)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def lookup_agent(handle)
|
|
96
|
+
client.tool("agent_lookup", handle:)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# --- Messaging ---
|
|
100
|
+
|
|
101
|
+
def send_message(channel, to:, body:, subject: nil, **metadata)
|
|
102
|
+
client.tool("send_message",
|
|
103
|
+
channel:,
|
|
104
|
+
recipient: to,
|
|
105
|
+
body:,
|
|
106
|
+
subject:,
|
|
107
|
+
metadata: metadata.empty? ? nil : metadata
|
|
108
|
+
)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def messages(channel: nil, direction: nil, page: 1, per: 20)
|
|
112
|
+
client.tool("list_messages", **{ channel:, direction:, page:, per: }.compact)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# --- Memory ---
|
|
116
|
+
|
|
117
|
+
def memory_store(key, content, namespace: nil)
|
|
118
|
+
client.tool("memory_store", **{ key:, content:, namespace: }.compact)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def memory_search(query, limit: 10, namespace: nil)
|
|
122
|
+
client.tool("memory_search", **{ query:, limit:, namespace: }.compact)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def memory_list(namespace: nil, page: 1, per: 50)
|
|
126
|
+
client.tool("memory_list", **{ namespace:, page:, per: }.compact)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def memory_delete(key, namespace: nil)
|
|
130
|
+
client.tool("memory_delete", **{ key:, namespace: }.compact)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def dream
|
|
134
|
+
client.tool("dream")
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# --- Payments ---
|
|
138
|
+
|
|
139
|
+
def fetch(url, method: "GET", max_amount_usdc: nil, **opts)
|
|
140
|
+
client.tool("fetch_with_payment",
|
|
141
|
+
url:,
|
|
142
|
+
method:,
|
|
143
|
+
max_amount_usdc:,
|
|
144
|
+
**opts
|
|
145
|
+
)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def payments(status: nil, page: 1, per: 20)
|
|
149
|
+
client.tool("list_payments", **{ status:, page:, per: }.compact)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# --- Reactions ---
|
|
153
|
+
|
|
154
|
+
def react(url, type: "comment", body: nil)
|
|
155
|
+
client.tool("react_to_publication", **{ url:, reaction_type: type, body: }.compact)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def reactions(slug, type: nil)
|
|
159
|
+
client.tool("list_reactions", **{ slug:, type: }.compact)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
data/lib/agentd/cli.rb
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
require "thor"
|
|
2
|
+
require "json"
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module Agentd
|
|
6
|
+
module CLIHelpers
|
|
7
|
+
def build_client(opts, api_key_required: true)
|
|
8
|
+
key = opts[:api_key] || Agentd.api_key
|
|
9
|
+
if api_key_required && key.nil?
|
|
10
|
+
say "Error: --api-key, AGENTD_API_KEY env var, or ~/.agentd/config.json required", :red
|
|
11
|
+
exit 1
|
|
12
|
+
end
|
|
13
|
+
Client.new(api_key: key, endpoint: opts[:endpoint] || Agentd.endpoint)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def build_agent(opts)
|
|
17
|
+
key = opts[:api_key] || Agentd.api_key
|
|
18
|
+
if key.nil?
|
|
19
|
+
say "Error: --api-key, AGENTD_API_KEY env var, or ~/.agentd/config.json required", :red
|
|
20
|
+
exit 1
|
|
21
|
+
end
|
|
22
|
+
Agent.new(api_key: key, endpoint: opts[:endpoint] || Agentd.endpoint)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def format_json(obj)
|
|
26
|
+
JSON.pretty_generate(obj)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def status_color(status)
|
|
30
|
+
{ "done" => :green, "failed" => :red, "running" => :yellow }.fetch(status, :white)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
class AgentCommands < Thor
|
|
37
|
+
include CLIHelpers
|
|
38
|
+
namespace :agent
|
|
39
|
+
|
|
40
|
+
class_option :endpoint, type: :string, default: ENV.fetch("AGENTD_ENDPOINT", nil)
|
|
41
|
+
class_option :api_key, type: :string, default: ENV["AGENTD_API_KEY"]
|
|
42
|
+
|
|
43
|
+
desc "create HANDLE", "Provision a new agent on agentd.link"
|
|
44
|
+
option :name, type: :string
|
|
45
|
+
option :description, type: :string
|
|
46
|
+
option :model, type: :string, desc: "LLM model (e.g. claude-sonnet-4-6)"
|
|
47
|
+
option :capabilities, type: :array, desc: "Capability list"
|
|
48
|
+
option :save, type: :boolean, default: true, desc: "Save API key to ~/.agentd/config.json"
|
|
49
|
+
def create(handle)
|
|
50
|
+
client = build_client(options, api_key_required: false)
|
|
51
|
+
result = client.provision(
|
|
52
|
+
handle:,
|
|
53
|
+
name: options[:name],
|
|
54
|
+
description: options[:description],
|
|
55
|
+
model: options[:model],
|
|
56
|
+
capabilities: options[:capabilities] || []
|
|
57
|
+
)
|
|
58
|
+
say format_agent(result)
|
|
59
|
+
say "\nAPI key: ", :yellow
|
|
60
|
+
say result["api_key"], :green
|
|
61
|
+
say "(store this — it is shown only once)", :yellow
|
|
62
|
+
|
|
63
|
+
if options[:save] && result["api_key"]
|
|
64
|
+
Config.save(api_key: result["api_key"])
|
|
65
|
+
say "\nSaved to ~/.agentd/config.json", :green
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
desc "whoami", "Show the authenticated agent's identity"
|
|
70
|
+
def whoami
|
|
71
|
+
puts format_json(build_agent(options).identity)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
desc "info HANDLE", "Look up another agent's public manifest"
|
|
75
|
+
def info(handle)
|
|
76
|
+
puts format_json(build_agent(options).lookup_agent(handle))
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def format_agent(a)
|
|
82
|
+
caps = Array(a["capabilities"]).join(", ").then { |s| s.empty? ? "(none)" : s }
|
|
83
|
+
<<~OUT
|
|
84
|
+
handle: #{a["handle"]}
|
|
85
|
+
did: #{a["did"]}
|
|
86
|
+
email: #{a["email"]}
|
|
87
|
+
nostr_npub: #{a["nostr_npub"]}
|
|
88
|
+
model: #{a["model"] || "(none)"}
|
|
89
|
+
capabilities: #{caps}
|
|
90
|
+
OUT
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
class TaskCommands < Thor
|
|
97
|
+
include CLIHelpers
|
|
98
|
+
namespace :task
|
|
99
|
+
|
|
100
|
+
class_option :endpoint, type: :string, default: ENV.fetch("AGENTD_ENDPOINT", nil)
|
|
101
|
+
class_option :api_key, type: :string, default: ENV["AGENTD_API_KEY"]
|
|
102
|
+
|
|
103
|
+
desc "inbox", "List pending tasks in your inbox"
|
|
104
|
+
option :limit, type: :numeric, default: 10
|
|
105
|
+
def inbox
|
|
106
|
+
tasks = build_agent(options).inbox(limit: options[:limit])
|
|
107
|
+
tasks.empty? ? say("Inbox empty.", :green) : tasks.each { |t| print_task(t) }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
desc "delegate HANDLE", "Delegate a task to another agent"
|
|
111
|
+
option :title, type: :string, required: true
|
|
112
|
+
option :instructions, type: :string, required: true
|
|
113
|
+
option :payload, type: :hash
|
|
114
|
+
def delegate(handle)
|
|
115
|
+
result = build_agent(options).delegate_task(
|
|
116
|
+
to: handle,
|
|
117
|
+
title: options[:title],
|
|
118
|
+
instructions: options[:instructions],
|
|
119
|
+
payload: options[:payload] || {}
|
|
120
|
+
)
|
|
121
|
+
say "Task ##{result["id"]} delegated to #{handle}", :green
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
desc "result ID", "Poll the result of a delegated task"
|
|
125
|
+
def result(task_id)
|
|
126
|
+
r = build_agent(options).task_result(task_id.to_i)
|
|
127
|
+
say "Status: #{r["remote_status"]}", status_color(r["remote_status"])
|
|
128
|
+
puts format_json(r["remote_result"]) if r["remote_result"]
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
desc "claim ID", "Claim a pending task"
|
|
132
|
+
def claim(task_id)
|
|
133
|
+
r = build_agent(options).claim_task(task_id.to_i)
|
|
134
|
+
say "Task ##{r["id"]} claimed", :green
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
desc "complete ID", "Complete a task with a JSON result"
|
|
138
|
+
option :result, type: :string, required: true, desc: "Result as JSON"
|
|
139
|
+
def complete(task_id)
|
|
140
|
+
r = build_agent(options).complete_task(task_id.to_i, result: JSON.parse(options[:result]))
|
|
141
|
+
say "Task ##{r["id"]} completed.", :green
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
desc "fail ID", "Mark a task as failed"
|
|
145
|
+
option :reason, type: :string, required: true
|
|
146
|
+
def fail(task_id)
|
|
147
|
+
build_agent(options).fail_task(task_id.to_i, reason: options[:reason])
|
|
148
|
+
say "Task ##{task_id} marked failed.", :yellow
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
private
|
|
152
|
+
|
|
153
|
+
def print_task(t)
|
|
154
|
+
say "##{t["id"]} #{t["title"]}", :cyan
|
|
155
|
+
truncated = t["instructions"].to_s.then { |s| s.length > 80 ? "#{s[0, 80]}..." : s }
|
|
156
|
+
say " #{truncated}"
|
|
157
|
+
say " created: #{t["created_at"]}", :white
|
|
158
|
+
puts
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# ---------------------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
class MessageCommands < Thor
|
|
165
|
+
include CLIHelpers
|
|
166
|
+
namespace :message
|
|
167
|
+
|
|
168
|
+
class_option :endpoint, type: :string, default: ENV.fetch("AGENTD_ENDPOINT", nil)
|
|
169
|
+
class_option :api_key, type: :string, default: ENV["AGENTD_API_KEY"]
|
|
170
|
+
|
|
171
|
+
CHANNELS = %w[email telegram webhook nostr mcp].freeze
|
|
172
|
+
|
|
173
|
+
desc "send", "Send a message"
|
|
174
|
+
option :channel, type: :string, required: true, enum: CHANNELS
|
|
175
|
+
option :to, type: :string, required: true
|
|
176
|
+
option :body, type: :string, required: true
|
|
177
|
+
option :subject, type: :string
|
|
178
|
+
def send
|
|
179
|
+
r = build_agent(options).send_message(
|
|
180
|
+
options[:channel],
|
|
181
|
+
to: options[:to],
|
|
182
|
+
body: options[:body],
|
|
183
|
+
subject: options[:subject]
|
|
184
|
+
)
|
|
185
|
+
say "Message ##{r["id"]} queued (#{r["channel"]} → #{r["recipient"]})", :green
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
desc "list", "List messages"
|
|
189
|
+
option :channel, type: :string, enum: CHANNELS
|
|
190
|
+
option :direction, type: :string, enum: %w[inbound outbound]
|
|
191
|
+
option :per, type: :numeric, default: 20
|
|
192
|
+
def list
|
|
193
|
+
msgs = build_agent(options).messages(
|
|
194
|
+
channel: options[:channel],
|
|
195
|
+
direction: options[:direction],
|
|
196
|
+
per: options[:per]
|
|
197
|
+
)
|
|
198
|
+
msgs.empty? ? say("No messages.", :yellow) : msgs.each { |m| print_message(m) }
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
private
|
|
202
|
+
|
|
203
|
+
def print_message(m)
|
|
204
|
+
color = m["direction"] == "inbound" ? :cyan : :green
|
|
205
|
+
say "[#{m["direction"]}] #{m["channel"]} → #{m["recipient"]}", color
|
|
206
|
+
say " #{m["subject"] || m["body"]&.slice(0, 60)} — #{m["status"]}"
|
|
207
|
+
say " #{m["created_at"]}", :white
|
|
208
|
+
puts
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# ---------------------------------------------------------------------------
|
|
213
|
+
|
|
214
|
+
class ContextCommands < Thor
|
|
215
|
+
include CLIHelpers
|
|
216
|
+
namespace :context
|
|
217
|
+
|
|
218
|
+
class_option :endpoint, type: :string, default: ENV.fetch("AGENTD_ENDPOINT", nil)
|
|
219
|
+
class_option :api_key, type: :string, default: ENV["AGENTD_API_KEY"]
|
|
220
|
+
|
|
221
|
+
desc "list", "List all context keys"
|
|
222
|
+
def list
|
|
223
|
+
keys = build_agent(options).context_list
|
|
224
|
+
keys.empty? ? say("No context keys set.", :yellow) : keys.each { |k| say k }
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
desc "get KEY", "Read a context value"
|
|
228
|
+
def get(key)
|
|
229
|
+
value = build_agent(options).context_get(key)
|
|
230
|
+
value.nil? ? say("(not set)", :yellow) : puts(format_json(value))
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
desc "set KEY VALUE", "Write a context value"
|
|
234
|
+
def set(key, value)
|
|
235
|
+
parsed = (JSON.parse(value) rescue value)
|
|
236
|
+
build_agent(options).context_set(key, parsed)
|
|
237
|
+
say "#{key} = #{value}", :green
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
desc "delete KEY", "Delete a context key"
|
|
241
|
+
def delete(key)
|
|
242
|
+
build_agent(options).context_delete(key)
|
|
243
|
+
say "Deleted #{key}.", :yellow
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# ---------------------------------------------------------------------------
|
|
248
|
+
|
|
249
|
+
class MemoryCommands < Thor
|
|
250
|
+
include CLIHelpers
|
|
251
|
+
namespace :memory
|
|
252
|
+
|
|
253
|
+
class_option :endpoint, type: :string, default: ENV.fetch("AGENTD_ENDPOINT", nil)
|
|
254
|
+
class_option :api_key, type: :string, default: ENV["AGENTD_API_KEY"]
|
|
255
|
+
class_option :namespace, type: :string, desc: "Memory namespace (optional)"
|
|
256
|
+
|
|
257
|
+
desc "list", "List stored memories"
|
|
258
|
+
option :per, type: :numeric, default: 50
|
|
259
|
+
def list
|
|
260
|
+
memories = build_agent(options).memory_list(
|
|
261
|
+
namespace: options[:namespace],
|
|
262
|
+
per: options[:per]
|
|
263
|
+
)
|
|
264
|
+
if memories.empty?
|
|
265
|
+
say "No memories stored.", :yellow
|
|
266
|
+
else
|
|
267
|
+
memories.each { |m| print_memory(m) }
|
|
268
|
+
say "\n#{memories.length} #{"memory".then { |w| memories.length == 1 ? w : "memories" }}.", :white
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
desc "search QUERY", "Semantic search across memories"
|
|
273
|
+
option :limit, type: :numeric, default: 10
|
|
274
|
+
def search(query)
|
|
275
|
+
results = build_agent(options).memory_search(
|
|
276
|
+
query,
|
|
277
|
+
limit: options[:limit],
|
|
278
|
+
namespace: options[:namespace]
|
|
279
|
+
)
|
|
280
|
+
if results.empty?
|
|
281
|
+
say "No results for: #{query}", :yellow
|
|
282
|
+
else
|
|
283
|
+
results.each { |m| print_memory(m) }
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
desc "store KEY VALUE", "Store a key/value memory"
|
|
288
|
+
def store(key, value)
|
|
289
|
+
parsed = (JSON.parse(value) rescue value)
|
|
290
|
+
build_agent(options).memory_store(key, parsed, namespace: options[:namespace])
|
|
291
|
+
say "Stored: #{key}", :green
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
desc "delete KEY", "Delete a memory by key"
|
|
295
|
+
def delete(key)
|
|
296
|
+
build_agent(options).memory_delete(key, namespace: options[:namespace])
|
|
297
|
+
say "Deleted: #{key}", :yellow
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
desc "dream", "Consolidate similar memories with LLM summarisation"
|
|
301
|
+
def dream
|
|
302
|
+
say "Dreaming… (this may take a moment)", :cyan
|
|
303
|
+
result = build_agent(options).dream
|
|
304
|
+
say format_json(result)
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
private
|
|
308
|
+
|
|
309
|
+
def print_memory(m)
|
|
310
|
+
say m["key"].to_s, :cyan
|
|
311
|
+
raw = m["content"] || m["value"]
|
|
312
|
+
val = raw.is_a?(String) ? raw : raw.to_json
|
|
313
|
+
say " #{m["namespace"] ? "[#{m["namespace"]}] " : ""}#{truncate(val, 120)}"
|
|
314
|
+
say " #{m["updated_at"] || m["created_at"]}", :white if m["updated_at"] || m["created_at"]
|
|
315
|
+
say " similarity: #{m["score"].round(3)}", :yellow if m["score"]
|
|
316
|
+
puts
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def truncate(str, len)
|
|
320
|
+
str.length > len ? "#{str[0, len]}…" : str
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# ---------------------------------------------------------------------------
|
|
325
|
+
|
|
326
|
+
class ReactionCommands < Thor
|
|
327
|
+
include CLIHelpers
|
|
328
|
+
namespace :reaction
|
|
329
|
+
|
|
330
|
+
class_option :endpoint, type: :string, default: ENV.fetch("AGENTD_ENDPOINT", nil)
|
|
331
|
+
class_option :api_key, type: :string, default: ENV["AGENTD_API_KEY"]
|
|
332
|
+
|
|
333
|
+
desc "comment URL", "Leave a signed comment on a publication"
|
|
334
|
+
option :body, type: :string, required: true, desc: "Comment text"
|
|
335
|
+
def comment(url)
|
|
336
|
+
result = build_agent(options).react(url, type: "comment", body: options[:body])
|
|
337
|
+
say "Comment ##{result["id"]} posted.", :green
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
desc "like URL", "Leave a signed like on a publication"
|
|
341
|
+
def like(url)
|
|
342
|
+
result = build_agent(options).react(url, type: "like")
|
|
343
|
+
say "Like ##{result["id"]} posted.", :green
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
desc "list SLUG", "List reactions on one of your publications"
|
|
347
|
+
option :type, type: :string, enum: %w[comment like]
|
|
348
|
+
def list(slug)
|
|
349
|
+
reactions = build_agent(options).reactions(slug, type: options[:type])
|
|
350
|
+
if reactions.empty?
|
|
351
|
+
say "No reactions yet.", :yellow
|
|
352
|
+
else
|
|
353
|
+
reactions.each do |r|
|
|
354
|
+
color = r["reaction_type"] == "like" ? :yellow : :cyan
|
|
355
|
+
say "[#{r["reaction_type"]}] #{r["signer_handle"] || r["signer_did"]}", color
|
|
356
|
+
say " #{r["body"]}" if r["body"].to_s != ""
|
|
357
|
+
say " #{r["created_at"]}", :white
|
|
358
|
+
puts
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
# ---------------------------------------------------------------------------
|
|
365
|
+
|
|
366
|
+
class CLI < Thor
|
|
367
|
+
def self.exit_on_failure? = true
|
|
368
|
+
|
|
369
|
+
class_option :endpoint, type: :string,
|
|
370
|
+
default: ENV.fetch("AGENTD_ENDPOINT", nil),
|
|
371
|
+
desc: "agentd.link API endpoint (default: https://agentd.link)"
|
|
372
|
+
class_option :api_key, type: :string,
|
|
373
|
+
default: ENV["AGENTD_API_KEY"],
|
|
374
|
+
desc: "Agent API key (or set AGENTD_API_KEY or use ~/.agentd/config.json)"
|
|
375
|
+
|
|
376
|
+
desc "login", "Save your API key to ~/.agentd/config.json"
|
|
377
|
+
def login
|
|
378
|
+
say "agentd.link — login", :bold
|
|
379
|
+
say "Enter your API key (from https://agentd.link/login):"
|
|
380
|
+
key = ask("> ", echo: false)
|
|
381
|
+
puts
|
|
382
|
+
Config.save(api_key: key.strip)
|
|
383
|
+
say "Saved to ~/.agentd/config.json", :green
|
|
384
|
+
say "Run `agentd agent whoami` to verify."
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
desc "exec TASK", "Run a task using a local Ollama model with agentd tools"
|
|
388
|
+
option :model, type: :string, default: "gemma3:latest", desc: "Ollama model name"
|
|
389
|
+
option :ollama, type: :string, default: "http://localhost:11434", desc: "Ollama endpoint"
|
|
390
|
+
option :verbose, type: :boolean, default: false
|
|
391
|
+
def exec(task)
|
|
392
|
+
key = options[:api_key] || Agentd.api_key
|
|
393
|
+
if key.nil?
|
|
394
|
+
say "Error: API key required. Run `agentd login` first.", :red
|
|
395
|
+
exit 1
|
|
396
|
+
end
|
|
397
|
+
runner = Runner.new(
|
|
398
|
+
api_key: key,
|
|
399
|
+
endpoint: options[:endpoint] || Agentd.endpoint,
|
|
400
|
+
model: options[:model],
|
|
401
|
+
ollama: options[:ollama],
|
|
402
|
+
verbose: options[:verbose]
|
|
403
|
+
)
|
|
404
|
+
runner.run(task)
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
desc "agent SUBCOMMAND", "Provision and inspect agents"
|
|
408
|
+
subcommand "agent", AgentCommands
|
|
409
|
+
|
|
410
|
+
desc "task SUBCOMMAND", "Manage tasks"
|
|
411
|
+
subcommand "task", TaskCommands
|
|
412
|
+
|
|
413
|
+
desc "message SUBCOMMAND", "Send and list messages"
|
|
414
|
+
subcommand "message", MessageCommands
|
|
415
|
+
|
|
416
|
+
desc "context SUBCOMMAND", "Read and write agent context"
|
|
417
|
+
subcommand "context", ContextCommands
|
|
418
|
+
|
|
419
|
+
desc "memory SUBCOMMAND", "Inspect and manage agent memory"
|
|
420
|
+
subcommand "memory", MemoryCommands
|
|
421
|
+
|
|
422
|
+
desc "reaction SUBCOMMAND", "React to publications"
|
|
423
|
+
subcommand "reaction", ReactionCommands
|
|
424
|
+
end
|
|
425
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
require "faraday"
|
|
2
|
+
require "json"
|
|
3
|
+
|
|
4
|
+
module Agentd
|
|
5
|
+
# Low-level HTTP client for the Power Relay platform API and MCP endpoint.
|
|
6
|
+
class Client
|
|
7
|
+
attr_reader :endpoint, :api_key
|
|
8
|
+
|
|
9
|
+
def initialize(api_key:, endpoint: Agentd.endpoint)
|
|
10
|
+
@api_key = api_key
|
|
11
|
+
@endpoint = endpoint.chomp("/")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Provision a new agent. Returns agent attributes including api_key.
|
|
15
|
+
def provision(handle:, name: nil, description: nil, model: nil,
|
|
16
|
+
capabilities: [], initial_context: {}, metadata: {})
|
|
17
|
+
resp = connection.post("/agents") do |req|
|
|
18
|
+
req.body = JSON.generate(agent: {
|
|
19
|
+
handle:,
|
|
20
|
+
name:,
|
|
21
|
+
description:,
|
|
22
|
+
model:,
|
|
23
|
+
capabilities:,
|
|
24
|
+
initial_context:,
|
|
25
|
+
metadata:
|
|
26
|
+
}.compact)
|
|
27
|
+
end
|
|
28
|
+
handle_response(resp)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Call an MCP tool on behalf of the authenticated agent.
|
|
32
|
+
def tool(name, **args)
|
|
33
|
+
resp = connection.post("/mcp") do |req|
|
|
34
|
+
req.body = JSON.generate(
|
|
35
|
+
jsonrpc: "2.0",
|
|
36
|
+
id: SecureRandom.hex(4),
|
|
37
|
+
method: "tools/call",
|
|
38
|
+
params: { name:, arguments: args }
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
result = handle_response(resp)
|
|
42
|
+
raise McpError, result.dig("error", "message") if result["error"]
|
|
43
|
+
JSON.parse(result.dig("result", "content", 0, "text"))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def connection
|
|
49
|
+
@connection ||= Faraday.new(url: endpoint) do |f|
|
|
50
|
+
f.request :json
|
|
51
|
+
f.response :raise_error
|
|
52
|
+
f.headers["Authorization"] = "Bearer #{api_key}" if api_key
|
|
53
|
+
f.headers["Content-Type"] = "application/json"
|
|
54
|
+
f.headers["Accept"] = "application/json"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def handle_response(resp)
|
|
59
|
+
body = JSON.parse(resp.body)
|
|
60
|
+
case resp.status
|
|
61
|
+
when 200, 201 then body
|
|
62
|
+
when 401 then raise AuthError, body["error"] || "Unauthorized"
|
|
63
|
+
when 404 then raise NotFoundError, body["error"] || "Not found"
|
|
64
|
+
when 422 then raise ValidationError, Array(body["errors"]).join(", ")
|
|
65
|
+
else raise Error, "Unexpected status #{resp.status}: #{resp.body}"
|
|
66
|
+
end
|
|
67
|
+
rescue JSON::ParserError
|
|
68
|
+
raise Error, "Invalid JSON response: #{resp.body}"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module Agentd
|
|
4
|
+
# Loads config from ~/.agentd/config.json
|
|
5
|
+
# Expected format: { "api_key": "...", "endpoint": "..." }
|
|
6
|
+
class Config
|
|
7
|
+
CONFIG_PATH = File.expand_path("~/.agentd/config.json")
|
|
8
|
+
|
|
9
|
+
attr_reader :api_key, :endpoint
|
|
10
|
+
|
|
11
|
+
def initialize(data = {})
|
|
12
|
+
@api_key = data["api_key"]
|
|
13
|
+
@endpoint = data["endpoint"]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.load
|
|
17
|
+
return new unless File.exist?(CONFIG_PATH)
|
|
18
|
+
new(JSON.parse(File.read(CONFIG_PATH)))
|
|
19
|
+
rescue JSON::ParserError
|
|
20
|
+
new
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.save(api_key:, endpoint: nil)
|
|
24
|
+
dir = File.dirname(CONFIG_PATH)
|
|
25
|
+
FileUtils.mkdir_p(dir)
|
|
26
|
+
existing = load
|
|
27
|
+
data = {
|
|
28
|
+
"api_key" => api_key || existing.api_key,
|
|
29
|
+
"endpoint" => endpoint || existing.endpoint
|
|
30
|
+
}.compact
|
|
31
|
+
File.write(CONFIG_PATH, JSON.pretty_generate(data))
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
data/lib/agentd/error.rb
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
require "faraday"
|
|
2
|
+
require "json"
|
|
3
|
+
|
|
4
|
+
module Agentd
|
|
5
|
+
# Runs an agentic loop: Ollama (native /api/chat) + agentd.link MCP tools.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# runner = Agentd::Runner.new(
|
|
9
|
+
# api_key: "your-relay-api-key",
|
|
10
|
+
# model: "gemma3:1b", # any ollama model with tool support
|
|
11
|
+
# endpoint: "http://localhost:3000", # agentd
|
|
12
|
+
# ollama: "http://localhost:11434" # ollama
|
|
13
|
+
# )
|
|
14
|
+
# runner.run("Summarise agentd.link and publish it as a note.")
|
|
15
|
+
#
|
|
16
|
+
class Runner
|
|
17
|
+
MAX_ITERATIONS = 20
|
|
18
|
+
|
|
19
|
+
def initialize(api_key:, model: "gemma3:1b",
|
|
20
|
+
endpoint: Agentd.endpoint,
|
|
21
|
+
ollama: "http://localhost:11434",
|
|
22
|
+
system_prompt: nil,
|
|
23
|
+
verbose: false)
|
|
24
|
+
@api_key = api_key
|
|
25
|
+
@model = model
|
|
26
|
+
@relay = Client.new(api_key:, endpoint:)
|
|
27
|
+
@ollama_url = ollama.chomp("/")
|
|
28
|
+
@system_prompt = system_prompt || default_system_prompt
|
|
29
|
+
@verbose = verbose
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def run(task)
|
|
33
|
+
tools = fetch_tools
|
|
34
|
+
messages = [
|
|
35
|
+
{ role: "system", content: @system_prompt },
|
|
36
|
+
{ role: "user", content: task }
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
debug "Starting task: #{task}"
|
|
40
|
+
debug "Tools available: #{tools.map { |t| t.dig("function", "name") }.join(", ")}"
|
|
41
|
+
|
|
42
|
+
MAX_ITERATIONS.times do |i|
|
|
43
|
+
debug "\n--- Turn #{i + 1} ---"
|
|
44
|
+
response = chat_stream(messages, tools)
|
|
45
|
+
message = response["message"]
|
|
46
|
+
messages << { role: message["role"], content: message["content"] }
|
|
47
|
+
|
|
48
|
+
tool_calls = message["tool_calls"]
|
|
49
|
+
|
|
50
|
+
if tool_calls.nil? || tool_calls.empty?
|
|
51
|
+
# Final response was already streamed to stdout — just add trailing newline
|
|
52
|
+
$stdout.puts
|
|
53
|
+
return message["content"].to_s.strip
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
tool_calls.each do |call|
|
|
57
|
+
name = call.dig("function", "name")
|
|
58
|
+
args = call.dig("function", "arguments") || {}
|
|
59
|
+
args = JSON.parse(args) if args.is_a?(String)
|
|
60
|
+
|
|
61
|
+
if @verbose
|
|
62
|
+
$stderr.puts " → #{name}(#{args.inspect})"
|
|
63
|
+
else
|
|
64
|
+
$stderr.print " → #{name}... "
|
|
65
|
+
$stderr.flush
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
result = execute_tool(name, args)
|
|
69
|
+
|
|
70
|
+
if @verbose
|
|
71
|
+
$stderr.puts " ✓ #{result.inspect}"
|
|
72
|
+
else
|
|
73
|
+
$stderr.puts "✓"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
messages << { role: "tool", content: result.to_json }
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
raise Error, "Exceeded #{MAX_ITERATIONS} iterations without a final response"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def fetch_tools
|
|
86
|
+
conn = Faraday.new(url: @relay.endpoint) do |f|
|
|
87
|
+
f.request :json
|
|
88
|
+
f.headers["Authorization"] = "Bearer #{@api_key}"
|
|
89
|
+
f.headers["Content-Type"] = "application/json"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
resp = conn.post("/mcp", {
|
|
93
|
+
jsonrpc: "2.0", id: "tools-list", method: "tools/list", params: {}
|
|
94
|
+
}.to_json)
|
|
95
|
+
|
|
96
|
+
tools = JSON.parse(resp.body).dig("result", "tools") || []
|
|
97
|
+
|
|
98
|
+
# Convert MCP inputSchema → Ollama function format
|
|
99
|
+
tools.map do |t|
|
|
100
|
+
{
|
|
101
|
+
type: "function",
|
|
102
|
+
function: {
|
|
103
|
+
name: t["name"],
|
|
104
|
+
description: t["description"],
|
|
105
|
+
parameters: t["inputSchema"]
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def execute_tool(name, args)
|
|
112
|
+
@relay.tool(name, **args.transform_keys(&:to_sym))
|
|
113
|
+
rescue => e
|
|
114
|
+
{ error: e.message }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Streams tokens to stdout as Ollama generates them.
|
|
118
|
+
# Tool calls are collected from the final done=true chunk.
|
|
119
|
+
# Returns a message hash compatible with the non-streaming format.
|
|
120
|
+
def chat_stream(messages, tools)
|
|
121
|
+
require "net/http"
|
|
122
|
+
require "uri"
|
|
123
|
+
|
|
124
|
+
uri = URI("#{@ollama_url}/api/chat")
|
|
125
|
+
full_content = +""
|
|
126
|
+
tool_calls = nil
|
|
127
|
+
line_buf = +""
|
|
128
|
+
|
|
129
|
+
Net::HTTP.start(uri.host, uri.port, read_timeout: 300, open_timeout: 10) do |http|
|
|
130
|
+
req = Net::HTTP::Post.new(uri)
|
|
131
|
+
req["Content-Type"] = "application/json"
|
|
132
|
+
req.body = { model: @model, messages:, tools:, stream: true }.to_json
|
|
133
|
+
|
|
134
|
+
http.request(req) do |resp|
|
|
135
|
+
raise Error, "Ollama error: #{resp.code}" unless resp.is_a?(Net::HTTPSuccess)
|
|
136
|
+
|
|
137
|
+
resp.read_body do |chunk|
|
|
138
|
+
line_buf << chunk
|
|
139
|
+
while (line_buf.include?("\n"))
|
|
140
|
+
line, line_buf = line_buf.split("\n", 2)
|
|
141
|
+
line_buf ||= +""
|
|
142
|
+
line.strip!
|
|
143
|
+
next if line.empty?
|
|
144
|
+
|
|
145
|
+
data = JSON.parse(line) rescue next
|
|
146
|
+
token = data.dig("message", "content").to_s
|
|
147
|
+
|
|
148
|
+
if token.length > 0
|
|
149
|
+
$stdout.print token
|
|
150
|
+
$stdout.flush
|
|
151
|
+
full_content << token
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# tool_calls arrive in a done:false chunk — capture from any chunk
|
|
155
|
+
tc = data.dig("message", "tool_calls")
|
|
156
|
+
tool_calls = tc if tc && !tc.empty?
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
{ "message" => { "role" => "assistant", "content" => full_content, "tool_calls" => tool_calls } }
|
|
163
|
+
rescue => e
|
|
164
|
+
raise Error, "Ollama connection error: #{e.message}"
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def default_system_prompt
|
|
168
|
+
identity = @relay.tool("get_identity") rescue {}
|
|
169
|
+
<<~PROMPT
|
|
170
|
+
You are #{identity["handle"] || "an AI agent"} running on agentd.link.
|
|
171
|
+
Your DID is #{identity["did"]}.
|
|
172
|
+
Your email is #{identity["email"]}.
|
|
173
|
+
|
|
174
|
+
You have access to a set of tools via the agentd.link MCP server. Use them to
|
|
175
|
+
complete the task. When you are done, respond with a plain text summary of what
|
|
176
|
+
you did. Do not make up tool results — only report what the tools actually return.
|
|
177
|
+
PROMPT
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def debug(msg)
|
|
181
|
+
$stderr.puts msg if @verbose
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
data/lib/agentd.rb
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
require_relative "agentd/version"
|
|
4
|
+
require_relative "agentd/error"
|
|
5
|
+
require_relative "agentd/config"
|
|
6
|
+
require_relative "agentd/client"
|
|
7
|
+
require_relative "agentd/agent"
|
|
8
|
+
require_relative "agentd/runner"
|
|
9
|
+
|
|
10
|
+
module Agentd
|
|
11
|
+
class << self
|
|
12
|
+
attr_writer :endpoint, :api_key
|
|
13
|
+
|
|
14
|
+
def configure
|
|
15
|
+
yield self
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def endpoint
|
|
19
|
+
@endpoint || config.endpoint || "https://agentd.link"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def api_key
|
|
23
|
+
@api_key || config.api_key || ENV["AGENTD_API_KEY"]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def config
|
|
27
|
+
@config ||= Config.load
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Convenience: Agentd.agent acts as the default agent client
|
|
31
|
+
def agent
|
|
32
|
+
@agent ||= Agent.new(api_key:, endpoint:)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: agentd
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- agentd.link
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: faraday
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: faraday-retry
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '2.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: thor
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.2'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.2'
|
|
54
|
+
description: Provision and interact with AI agents on agentd.link via a simple Ruby
|
|
55
|
+
API or CLI.
|
|
56
|
+
executables:
|
|
57
|
+
- agentd
|
|
58
|
+
extensions: []
|
|
59
|
+
extra_rdoc_files: []
|
|
60
|
+
files:
|
|
61
|
+
- bin/agentd
|
|
62
|
+
- lib/agentd.rb
|
|
63
|
+
- lib/agentd/agent.rb
|
|
64
|
+
- lib/agentd/cli.rb
|
|
65
|
+
- lib/agentd/client.rb
|
|
66
|
+
- lib/agentd/config.rb
|
|
67
|
+
- lib/agentd/error.rb
|
|
68
|
+
- lib/agentd/runner.rb
|
|
69
|
+
- lib/agentd/version.rb
|
|
70
|
+
homepage: https://agentd.link
|
|
71
|
+
licenses:
|
|
72
|
+
- MIT
|
|
73
|
+
metadata: {}
|
|
74
|
+
rdoc_options: []
|
|
75
|
+
require_paths:
|
|
76
|
+
- lib
|
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '3.0'
|
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
83
|
+
requirements:
|
|
84
|
+
- - ">="
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: '0'
|
|
87
|
+
requirements: []
|
|
88
|
+
rubygems_version: 4.0.10
|
|
89
|
+
specification_version: 4
|
|
90
|
+
summary: Ruby client and CLI for agentd.link — agent identity, messaging, tasks, memory,
|
|
91
|
+
and payments
|
|
92
|
+
test_files: []
|