prompt_objects 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CLAUDE.md +108 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +231 -0
- data/IMPLEMENTATION_PLAN.md +1073 -0
- data/LICENSE +21 -0
- data/README.md +73 -0
- data/Rakefile +27 -0
- data/design-doc-v2.md +1232 -0
- data/exe/prompt_objects +572 -0
- data/exe/prompt_objects_mcp +34 -0
- data/frontend/.gitignore +3 -0
- data/frontend/index.html +13 -0
- data/frontend/package-lock.json +4417 -0
- data/frontend/package.json +32 -0
- data/frontend/postcss.config.js +6 -0
- data/frontend/src/App.tsx +95 -0
- data/frontend/src/components/CapabilitiesPanel.tsx +44 -0
- data/frontend/src/components/ChatPanel.tsx +251 -0
- data/frontend/src/components/Dashboard.tsx +83 -0
- data/frontend/src/components/Header.tsx +141 -0
- data/frontend/src/components/MarkdownMessage.tsx +153 -0
- data/frontend/src/components/MessageBus.tsx +55 -0
- data/frontend/src/components/ModelSelector.tsx +112 -0
- data/frontend/src/components/NotificationPanel.tsx +134 -0
- data/frontend/src/components/POCard.tsx +56 -0
- data/frontend/src/components/PODetail.tsx +117 -0
- data/frontend/src/components/PromptPanel.tsx +51 -0
- data/frontend/src/components/SessionsPanel.tsx +174 -0
- data/frontend/src/components/ThreadsSidebar.tsx +119 -0
- data/frontend/src/components/index.ts +11 -0
- data/frontend/src/hooks/useWebSocket.ts +363 -0
- data/frontend/src/index.css +37 -0
- data/frontend/src/main.tsx +10 -0
- data/frontend/src/store/index.ts +246 -0
- data/frontend/src/types/index.ts +146 -0
- data/frontend/tailwind.config.js +25 -0
- data/frontend/tsconfig.json +30 -0
- data/frontend/vite.config.ts +29 -0
- data/lib/prompt_objects/capability.rb +46 -0
- data/lib/prompt_objects/cli.rb +431 -0
- data/lib/prompt_objects/connectors/base.rb +73 -0
- data/lib/prompt_objects/connectors/mcp.rb +524 -0
- data/lib/prompt_objects/environment/exporter.rb +83 -0
- data/lib/prompt_objects/environment/git.rb +118 -0
- data/lib/prompt_objects/environment/importer.rb +159 -0
- data/lib/prompt_objects/environment/manager.rb +401 -0
- data/lib/prompt_objects/environment/manifest.rb +218 -0
- data/lib/prompt_objects/environment.rb +283 -0
- data/lib/prompt_objects/human_queue.rb +144 -0
- data/lib/prompt_objects/llm/anthropic_adapter.rb +137 -0
- data/lib/prompt_objects/llm/factory.rb +84 -0
- data/lib/prompt_objects/llm/gemini_adapter.rb +209 -0
- data/lib/prompt_objects/llm/openai_adapter.rb +104 -0
- data/lib/prompt_objects/llm/response.rb +61 -0
- data/lib/prompt_objects/loader.rb +32 -0
- data/lib/prompt_objects/mcp/server.rb +167 -0
- data/lib/prompt_objects/mcp/tools/get_conversation.rb +60 -0
- data/lib/prompt_objects/mcp/tools/get_pending_requests.rb +54 -0
- data/lib/prompt_objects/mcp/tools/inspect_po.rb +73 -0
- data/lib/prompt_objects/mcp/tools/list_prompt_objects.rb +37 -0
- data/lib/prompt_objects/mcp/tools/respond_to_request.rb +68 -0
- data/lib/prompt_objects/mcp/tools/send_message.rb +71 -0
- data/lib/prompt_objects/message_bus.rb +97 -0
- data/lib/prompt_objects/primitive.rb +13 -0
- data/lib/prompt_objects/primitives/http_get.rb +72 -0
- data/lib/prompt_objects/primitives/list_files.rb +95 -0
- data/lib/prompt_objects/primitives/read_file.rb +81 -0
- data/lib/prompt_objects/primitives/write_file.rb +73 -0
- data/lib/prompt_objects/prompt_object.rb +415 -0
- data/lib/prompt_objects/registry.rb +88 -0
- data/lib/prompt_objects/server/api/routes.rb +297 -0
- data/lib/prompt_objects/server/app.rb +174 -0
- data/lib/prompt_objects/server/file_watcher.rb +113 -0
- data/lib/prompt_objects/server/public/assets/index-2acS2FYZ.js +77 -0
- data/lib/prompt_objects/server/public/assets/index-DXU5uRXQ.css +1 -0
- data/lib/prompt_objects/server/public/index.html +14 -0
- data/lib/prompt_objects/server/websocket_handler.rb +619 -0
- data/lib/prompt_objects/server.rb +166 -0
- data/lib/prompt_objects/session/store.rb +826 -0
- data/lib/prompt_objects/universal/add_capability.rb +74 -0
- data/lib/prompt_objects/universal/add_primitive.rb +113 -0
- data/lib/prompt_objects/universal/ask_human.rb +109 -0
- data/lib/prompt_objects/universal/create_capability.rb +219 -0
- data/lib/prompt_objects/universal/create_primitive.rb +170 -0
- data/lib/prompt_objects/universal/list_capabilities.rb +55 -0
- data/lib/prompt_objects/universal/list_primitives.rb +145 -0
- data/lib/prompt_objects/universal/modify_primitive.rb +180 -0
- data/lib/prompt_objects/universal/request_primitive.rb +287 -0
- data/lib/prompt_objects/universal/think.rb +41 -0
- data/lib/prompt_objects/universal/verify_primitive.rb +173 -0
- data/lib/prompt_objects.rb +62 -0
- data/objects/coordinator.md +48 -0
- data/objects/greeter.md +30 -0
- data/objects/reader.md +33 -0
- data/prompt_objects.gemspec +50 -0
- data/templates/basic/.gitignore +2 -0
- data/templates/basic/manifest.yml +7 -0
- data/templates/basic/objects/basic.md +32 -0
- data/templates/developer/.gitignore +5 -0
- data/templates/developer/manifest.yml +17 -0
- data/templates/developer/objects/code_reviewer.md +33 -0
- data/templates/developer/objects/coordinator.md +39 -0
- data/templates/developer/objects/debugger.md +35 -0
- data/templates/empty/.gitignore +5 -0
- data/templates/empty/manifest.yml +14 -0
- data/templates/empty/objects/.gitkeep +0 -0
- data/templates/empty/objects/assistant.md +41 -0
- data/templates/minimal/.gitignore +5 -0
- data/templates/minimal/manifest.yml +7 -0
- data/templates/minimal/objects/assistant.md +41 -0
- data/templates/writer/.gitignore +5 -0
- data/templates/writer/manifest.yml +17 -0
- data/templates/writer/objects/coordinator.md +33 -0
- data/templates/writer/objects/editor.md +33 -0
- data/templates/writer/objects/researcher.md +34 -0
- metadata +343 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PromptObjects
|
|
4
|
+
# Command-line interface for environment management.
|
|
5
|
+
module CLI
|
|
6
|
+
# Handle env subcommands: list, create, info, archive, restore, clone, etc.
|
|
7
|
+
class EnvCommand
|
|
8
|
+
def initialize(manager: nil)
|
|
9
|
+
@manager = manager || Env::Manager.new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Run the env subcommand.
|
|
13
|
+
# @param args [Array<String>] Arguments after 'env'
|
|
14
|
+
def run(args)
|
|
15
|
+
subcommand = args.shift || "list"
|
|
16
|
+
|
|
17
|
+
case subcommand
|
|
18
|
+
when "list", "ls"
|
|
19
|
+
list
|
|
20
|
+
when "create", "new"
|
|
21
|
+
create(args)
|
|
22
|
+
when "info"
|
|
23
|
+
info(args)
|
|
24
|
+
when "export"
|
|
25
|
+
export(args)
|
|
26
|
+
when "import"
|
|
27
|
+
import(args)
|
|
28
|
+
when "archive"
|
|
29
|
+
archive(args)
|
|
30
|
+
when "restore"
|
|
31
|
+
restore(args)
|
|
32
|
+
when "clone", "cp"
|
|
33
|
+
clone(args)
|
|
34
|
+
when "delete"
|
|
35
|
+
delete(args)
|
|
36
|
+
when "default"
|
|
37
|
+
set_default(args)
|
|
38
|
+
else
|
|
39
|
+
puts "Unknown env command: #{subcommand}"
|
|
40
|
+
puts
|
|
41
|
+
help
|
|
42
|
+
exit 1
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def list
|
|
47
|
+
@manager.setup!
|
|
48
|
+
envs = @manager.list_with_manifests
|
|
49
|
+
|
|
50
|
+
if envs.empty?
|
|
51
|
+
puts "No environments found."
|
|
52
|
+
puts "Create one with: prompt_objects env create <name>"
|
|
53
|
+
return
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
default = @manager.default_environment
|
|
57
|
+
|
|
58
|
+
puts "Environments:"
|
|
59
|
+
puts
|
|
60
|
+
envs.each do |manifest|
|
|
61
|
+
default_marker = manifest.name == default ? " (default)" : ""
|
|
62
|
+
last_opened = manifest.last_opened&.strftime("%Y-%m-%d") || "never"
|
|
63
|
+
puts " #{manifest.icon} #{manifest.name}#{default_marker}"
|
|
64
|
+
puts " #{manifest.description}" if manifest.description
|
|
65
|
+
puts " Last opened: #{last_opened}, Objects: #{manifest.stats['po_count']}"
|
|
66
|
+
puts
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def create(args)
|
|
71
|
+
name = args.shift
|
|
72
|
+
unless name
|
|
73
|
+
puts "Usage: prompt_objects env create <name> [--template <template>]"
|
|
74
|
+
exit 1
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
template = nil
|
|
78
|
+
args.each_with_index do |arg, i|
|
|
79
|
+
if arg == "--template" || arg == "-t"
|
|
80
|
+
template = args[i + 1]
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
@manager.setup!
|
|
85
|
+
|
|
86
|
+
path = @manager.create(name: name, template: template)
|
|
87
|
+
puts "Created environment: #{name}"
|
|
88
|
+
puts "Location: #{path}"
|
|
89
|
+
|
|
90
|
+
if @manager.list.size == 1
|
|
91
|
+
@manager.set_default_environment(name)
|
|
92
|
+
puts "Set as default environment."
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def info(args)
|
|
97
|
+
name = args.shift || @manager.default_environment
|
|
98
|
+
unless name
|
|
99
|
+
puts "Usage: prompt_objects env info <name>"
|
|
100
|
+
exit 1
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
manifest = @manager.manifest_for(name)
|
|
104
|
+
unless manifest
|
|
105
|
+
puts "Environment '#{name}' not found."
|
|
106
|
+
exit 1
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
path = @manager.environment_path(name)
|
|
110
|
+
objects = Dir.glob(File.join(path, "objects", "*.md")).map { |f| File.basename(f, ".md") }
|
|
111
|
+
primitives = Dir.glob(File.join(path, "primitives", "*.rb")).map { |f| File.basename(f, ".rb") }
|
|
112
|
+
|
|
113
|
+
puts manifest.info
|
|
114
|
+
puts
|
|
115
|
+
puts " Location: #{path}"
|
|
116
|
+
puts " Objects: #{objects.join(', ')}" if objects.any?
|
|
117
|
+
puts " Custom primitives: #{primitives.join(', ')}" if primitives.any?
|
|
118
|
+
puts
|
|
119
|
+
|
|
120
|
+
if Env::Git.repo?(path)
|
|
121
|
+
commits = Env::Git.commit_count(path)
|
|
122
|
+
dirty = Env::Git.dirty?(path)
|
|
123
|
+
puts " Git: #{commits} commits#{dirty ? ' (uncommitted changes)' : ''}"
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def export(args)
|
|
128
|
+
@manager.setup!
|
|
129
|
+
|
|
130
|
+
# Parse arguments
|
|
131
|
+
name = nil
|
|
132
|
+
output = nil
|
|
133
|
+
|
|
134
|
+
i = 0
|
|
135
|
+
while i < args.length
|
|
136
|
+
case args[i]
|
|
137
|
+
when "-o", "--output"
|
|
138
|
+
output = args[i + 1]
|
|
139
|
+
i += 1
|
|
140
|
+
else
|
|
141
|
+
name ||= args[i]
|
|
142
|
+
end
|
|
143
|
+
i += 1
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Default to current directory name if no output specified
|
|
147
|
+
name ||= @manager.default_environment
|
|
148
|
+
unless name
|
|
149
|
+
puts "Usage: prompt_objects env export <name> [-o output.poenv]"
|
|
150
|
+
exit 1
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
unless @manager.environment_exists?(name)
|
|
154
|
+
puts "Environment '#{name}' not found."
|
|
155
|
+
exit 1
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
output ||= "#{name}.poenv"
|
|
159
|
+
env_path = @manager.environment_path(name)
|
|
160
|
+
|
|
161
|
+
exporter = Env::Exporter.new(env_path)
|
|
162
|
+
result = exporter.export(output)
|
|
163
|
+
|
|
164
|
+
if result[:success]
|
|
165
|
+
puts "Exported '#{name}' to: #{result[:path]}"
|
|
166
|
+
puts
|
|
167
|
+
puts "Stats:"
|
|
168
|
+
puts " Commits: #{result[:stats][:commits]}"
|
|
169
|
+
puts " Objects: #{result[:stats][:objects]}"
|
|
170
|
+
puts " Primitives: #{result[:stats][:primitives]}"
|
|
171
|
+
else
|
|
172
|
+
puts "Export failed: #{result[:error]}"
|
|
173
|
+
exit 1
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def import(args)
|
|
178
|
+
@manager.setup!
|
|
179
|
+
|
|
180
|
+
# Parse arguments
|
|
181
|
+
bundle_path = nil
|
|
182
|
+
import_as = nil
|
|
183
|
+
trust = false
|
|
184
|
+
|
|
185
|
+
i = 0
|
|
186
|
+
while i < args.length
|
|
187
|
+
case args[i]
|
|
188
|
+
when "--as"
|
|
189
|
+
import_as = args[i + 1]
|
|
190
|
+
i += 1
|
|
191
|
+
when "--trust"
|
|
192
|
+
trust = true
|
|
193
|
+
else
|
|
194
|
+
bundle_path ||= args[i]
|
|
195
|
+
end
|
|
196
|
+
i += 1
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
unless bundle_path
|
|
200
|
+
puts "Usage: prompt_objects env import <bundle.poenv> [--as <name>] [--trust]"
|
|
201
|
+
puts
|
|
202
|
+
puts "Options:"
|
|
203
|
+
puts " --as <name> Import with a different name"
|
|
204
|
+
puts " --trust Trust custom primitives (skip sandbox warnings)"
|
|
205
|
+
exit 1
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
importer = Env::Importer.new(bundle_path)
|
|
209
|
+
|
|
210
|
+
# First, inspect and show what's in the bundle
|
|
211
|
+
info = importer.inspect_bundle
|
|
212
|
+
unless info.valid
|
|
213
|
+
puts "Invalid bundle: #{info.error}"
|
|
214
|
+
exit 1
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
puts "Bundle contents:"
|
|
218
|
+
puts " Name: #{info.name}"
|
|
219
|
+
puts " Description: #{info.description}" if info.description
|
|
220
|
+
puts " Objects: #{info.objects.join(', ')}" if info.objects.any?
|
|
221
|
+
puts " Primitives: #{info.primitives.join(', ')}" if info.primitives.any?
|
|
222
|
+
puts " Commits: #{info.commits}"
|
|
223
|
+
puts
|
|
224
|
+
|
|
225
|
+
# Warn about primitives
|
|
226
|
+
if info.primitives.any? && !trust
|
|
227
|
+
puts "⚠️ WARNING: This bundle contains custom primitives."
|
|
228
|
+
puts "Custom primitives can execute arbitrary Ruby code."
|
|
229
|
+
puts "Review the code before running, or use --trust to skip this warning."
|
|
230
|
+
puts
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Import
|
|
234
|
+
import_name = import_as || info.name
|
|
235
|
+
result = importer.import(manager: @manager, name: import_name, trust_primitives: trust)
|
|
236
|
+
|
|
237
|
+
if result[:success]
|
|
238
|
+
puts "Imported as '#{result[:name]}'"
|
|
239
|
+
puts "Location: #{result[:path]}"
|
|
240
|
+
|
|
241
|
+
if result[:warnings].any?
|
|
242
|
+
puts
|
|
243
|
+
result[:warnings].each { |w| puts w }
|
|
244
|
+
end
|
|
245
|
+
else
|
|
246
|
+
puts "Import failed: #{result[:error]}"
|
|
247
|
+
exit 1
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def archive(args)
|
|
252
|
+
name = args.shift
|
|
253
|
+
unless name
|
|
254
|
+
puts "Usage: prompt_objects env archive <name>"
|
|
255
|
+
exit 1
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
path = @manager.archive(name)
|
|
259
|
+
puts "Archived environment '#{name}' to:"
|
|
260
|
+
puts " #{path}"
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def restore(args)
|
|
264
|
+
archived_name = args.shift
|
|
265
|
+
unless archived_name
|
|
266
|
+
# Show available archived environments
|
|
267
|
+
archived = @manager.list_archived
|
|
268
|
+
if archived.empty?
|
|
269
|
+
puts "No archived environments."
|
|
270
|
+
else
|
|
271
|
+
puts "Archived environments:"
|
|
272
|
+
archived.each { |name| puts " - #{name}" }
|
|
273
|
+
puts
|
|
274
|
+
puts "Usage: prompt_objects env restore <archived_name> [--as <new_name>]"
|
|
275
|
+
end
|
|
276
|
+
return
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
restore_as = nil
|
|
280
|
+
args.each_with_index do |arg, i|
|
|
281
|
+
restore_as = args[i + 1] if arg == "--as"
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
path = @manager.restore(archived_name, restore_as: restore_as)
|
|
285
|
+
puts "Restored to: #{path}"
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def clone(args)
|
|
289
|
+
source = args.shift
|
|
290
|
+
target = args.shift
|
|
291
|
+
|
|
292
|
+
unless source && target
|
|
293
|
+
puts "Usage: prompt_objects env clone <source> <target>"
|
|
294
|
+
exit 1
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
path = @manager.clone(source, target)
|
|
298
|
+
puts "Cloned '#{source}' to '#{target}'"
|
|
299
|
+
puts "Location: #{path}"
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def delete(args)
|
|
303
|
+
name = args.shift
|
|
304
|
+
permanent = args.include?("--permanent")
|
|
305
|
+
|
|
306
|
+
unless name
|
|
307
|
+
puts "Usage: prompt_objects env delete <archived_name> --permanent"
|
|
308
|
+
puts
|
|
309
|
+
puts "Note: Only archived environments can be permanently deleted."
|
|
310
|
+
puts "First archive an environment, then delete it."
|
|
311
|
+
return
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
unless permanent
|
|
315
|
+
puts "Use --permanent to confirm deletion."
|
|
316
|
+
puts "This cannot be undone."
|
|
317
|
+
return
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
@manager.delete_archived(name)
|
|
321
|
+
puts "Permanently deleted: #{name}"
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def set_default(args)
|
|
325
|
+
name = args.shift
|
|
326
|
+
unless name
|
|
327
|
+
current = @manager.default_environment
|
|
328
|
+
if current
|
|
329
|
+
puts "Current default: #{current}"
|
|
330
|
+
else
|
|
331
|
+
puts "No default environment set."
|
|
332
|
+
end
|
|
333
|
+
puts
|
|
334
|
+
puts "Usage: prompt_objects env default <name>"
|
|
335
|
+
return
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
@manager.set_default_environment(name)
|
|
339
|
+
puts "Default environment set to: #{name}"
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def help
|
|
343
|
+
puts <<~HELP
|
|
344
|
+
Environment management commands:
|
|
345
|
+
|
|
346
|
+
prompt_objects env list List all environments
|
|
347
|
+
prompt_objects env create <name> Create new environment
|
|
348
|
+
--template, -t <template> Use template (minimal, developer, writer, empty)
|
|
349
|
+
prompt_objects env info <name> Show environment details
|
|
350
|
+
prompt_objects env export <name> Export environment as .poenv bundle
|
|
351
|
+
-o, --output <file> Output file path
|
|
352
|
+
prompt_objects env import <file> Import environment from .poenv bundle
|
|
353
|
+
--as <name> Import with different name
|
|
354
|
+
--trust Trust custom primitives
|
|
355
|
+
prompt_objects env archive <name> Archive (soft delete) environment
|
|
356
|
+
prompt_objects env restore <name> Restore archived environment
|
|
357
|
+
--as <new_name> Restore with different name
|
|
358
|
+
prompt_objects env clone <src> <dest> Clone environment
|
|
359
|
+
prompt_objects env default <name> Set default environment
|
|
360
|
+
prompt_objects env delete <name> --permanent Delete archived env
|
|
361
|
+
|
|
362
|
+
Available templates:
|
|
363
|
+
basic - No capabilities, learns as needed (great for demos!)
|
|
364
|
+
minimal - Basic assistant with file reading
|
|
365
|
+
developer - Code review, debugging, testing specialists
|
|
366
|
+
writer - Editor, researcher for content creation
|
|
367
|
+
empty - Bootstrap assistant only
|
|
368
|
+
HELP
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# Parse arguments and run appropriate command.
|
|
373
|
+
# @param args [Array<String>] Command line arguments
|
|
374
|
+
# @return [Hash] Parsed options for main command
|
|
375
|
+
def self.parse(args)
|
|
376
|
+
options = {
|
|
377
|
+
env_name: nil,
|
|
378
|
+
dev_mode: false,
|
|
379
|
+
sandbox: false,
|
|
380
|
+
command: :run
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
i = 0
|
|
384
|
+
while i < args.length
|
|
385
|
+
arg = args[i]
|
|
386
|
+
case arg
|
|
387
|
+
when "env"
|
|
388
|
+
options[:command] = :env
|
|
389
|
+
options[:env_args] = args[(i + 1)..]
|
|
390
|
+
return options
|
|
391
|
+
when "--env", "-e"
|
|
392
|
+
options[:env_name] = args[i + 1]
|
|
393
|
+
i += 1
|
|
394
|
+
when "--dev"
|
|
395
|
+
options[:dev_mode] = true
|
|
396
|
+
when "--sandbox", "-s"
|
|
397
|
+
options[:sandbox] = true
|
|
398
|
+
when "--help", "-h"
|
|
399
|
+
options[:command] = :help
|
|
400
|
+
return options
|
|
401
|
+
else
|
|
402
|
+
# Assume it's a PO name or objects_dir (legacy)
|
|
403
|
+
options[:legacy_args] ||= []
|
|
404
|
+
options[:legacy_args] << arg
|
|
405
|
+
end
|
|
406
|
+
i += 1
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
options
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
# List available templates.
|
|
413
|
+
# @return [Array<Hash>] Template info
|
|
414
|
+
def self.list_templates
|
|
415
|
+
templates_dir = File.expand_path("../../templates", __dir__)
|
|
416
|
+
return [] unless Dir.exist?(templates_dir)
|
|
417
|
+
|
|
418
|
+
Dir.children(templates_dir).filter_map do |name|
|
|
419
|
+
manifest_path = File.join(templates_dir, name, "manifest.yml")
|
|
420
|
+
next unless File.exist?(manifest_path)
|
|
421
|
+
|
|
422
|
+
manifest = YAML.safe_load(File.read(manifest_path))
|
|
423
|
+
{
|
|
424
|
+
name: name,
|
|
425
|
+
description: manifest["description"],
|
|
426
|
+
icon: manifest["icon"]
|
|
427
|
+
}
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PromptObjects
|
|
4
|
+
module Connectors
|
|
5
|
+
# Base class for connectors that provide different interfaces to environments.
|
|
6
|
+
# Each connector (MCP, API, Web, etc.) inherits from this class.
|
|
7
|
+
class Base
|
|
8
|
+
attr_reader :runtime, :config
|
|
9
|
+
|
|
10
|
+
# @param runtime [Runtime] The environment runtime
|
|
11
|
+
# @param config [Hash] Connector-specific configuration
|
|
12
|
+
def initialize(runtime:, config: {})
|
|
13
|
+
@runtime = runtime
|
|
14
|
+
@config = config
|
|
15
|
+
@running = false
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Start the connector (may be blocking or non-blocking depending on implementation)
|
|
19
|
+
def start
|
|
20
|
+
raise NotImplementedError, "#{self.class} must implement #start"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Stop the connector gracefully
|
|
24
|
+
def stop
|
|
25
|
+
@running = false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Whether the connector is currently running
|
|
29
|
+
def running?
|
|
30
|
+
@running
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Connector identifier for session source tracking
|
|
34
|
+
# @return [String] e.g., "mcp", "api", "web"
|
|
35
|
+
def source_name
|
|
36
|
+
raise NotImplementedError, "#{self.class} must implement #source_name"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
protected
|
|
40
|
+
|
|
41
|
+
# Helper to get or create a session for a PO with source tracking
|
|
42
|
+
# @param po [PromptObject] The prompt object
|
|
43
|
+
# @param session_name [String, nil] Optional session name
|
|
44
|
+
# @return [String] Session ID
|
|
45
|
+
def get_or_create_session(po, session_name: nil)
|
|
46
|
+
return nil unless runtime.session_store
|
|
47
|
+
|
|
48
|
+
# Check if PO already has a session
|
|
49
|
+
session_id = po.instance_variable_get(:@session_id)
|
|
50
|
+
return session_id if session_id
|
|
51
|
+
|
|
52
|
+
# Create new session with source tracking
|
|
53
|
+
session_id = runtime.session_store.create_session(
|
|
54
|
+
po_name: po.name,
|
|
55
|
+
name: session_name || "#{source_name.upcase} Session #{Time.now.strftime('%H:%M')}",
|
|
56
|
+
source: source_name
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Attach to PO
|
|
60
|
+
po.instance_variable_set(:@session_id, session_id)
|
|
61
|
+
session_id
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Update last_message_source when a message is sent
|
|
65
|
+
# @param session_id [String] Session ID
|
|
66
|
+
def update_session_source(session_id)
|
|
67
|
+
return unless runtime.session_store && session_id
|
|
68
|
+
|
|
69
|
+
runtime.session_store.update_session(session_id, last_message_source: source_name)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|