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
data/exe/prompt_objects
ADDED
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Add lib to load path for development (harmless when gem is installed)
|
|
5
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
6
|
+
|
|
7
|
+
# Note: Do NOT require "bundler/setup" here - it would conflict with
|
|
8
|
+
# the user's project Gemfile if they run this from within a Rails app, etc.
|
|
9
|
+
require "prompt_objects"
|
|
10
|
+
require "prompt_objects/cli"
|
|
11
|
+
require "fileutils"
|
|
12
|
+
require "tmpdir"
|
|
13
|
+
|
|
14
|
+
# Sandbox manages an isolated environment for testing
|
|
15
|
+
class Sandbox
|
|
16
|
+
attr_reader :sandbox_dir, :objects_dir, :primitives_dir, :original_objects_dir
|
|
17
|
+
|
|
18
|
+
def initialize(original_objects_dir)
|
|
19
|
+
@original_objects_dir = File.expand_path(original_objects_dir)
|
|
20
|
+
@sandbox_dir = Dir.mktmpdir("prompt_objects_sandbox_")
|
|
21
|
+
@objects_dir = File.join(@sandbox_dir, "objects")
|
|
22
|
+
@primitives_dir = File.join(@sandbox_dir, "primitives")
|
|
23
|
+
|
|
24
|
+
setup
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def setup
|
|
28
|
+
FileUtils.mkdir_p(@objects_dir)
|
|
29
|
+
FileUtils.mkdir_p(@primitives_dir)
|
|
30
|
+
|
|
31
|
+
# Copy existing objects to sandbox
|
|
32
|
+
if Dir.exist?(@original_objects_dir)
|
|
33
|
+
Dir.glob(File.join(@original_objects_dir, "*.md")).each do |src|
|
|
34
|
+
FileUtils.cp(src, @objects_dir)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
puts "Sandbox initialized at: #{@sandbox_dir}"
|
|
39
|
+
puts " Objects copied: #{Dir.glob(File.join(@objects_dir, '*.md')).count}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def cleanup(keep_changes: false)
|
|
43
|
+
if keep_changes
|
|
44
|
+
copy_back_changes
|
|
45
|
+
end
|
|
46
|
+
FileUtils.rm_rf(@sandbox_dir)
|
|
47
|
+
puts "Sandbox cleaned up"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def copy_back_changes
|
|
51
|
+
# Find new/modified objects
|
|
52
|
+
new_objects = Dir.glob(File.join(@objects_dir, "*.md")).select do |sandbox_file|
|
|
53
|
+
original = File.join(@original_objects_dir, File.basename(sandbox_file))
|
|
54
|
+
!File.exist?(original) || File.read(sandbox_file) != File.read(original)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Find new primitives
|
|
58
|
+
new_primitives = Dir.glob(File.join(@primitives_dir, "*.rb"))
|
|
59
|
+
|
|
60
|
+
return if new_objects.empty? && new_primitives.empty?
|
|
61
|
+
|
|
62
|
+
puts "\nChanges to copy back:"
|
|
63
|
+
new_objects.each { |f| puts " + objects/#{File.basename(f)}" }
|
|
64
|
+
new_primitives.each { |f| puts " + primitives/#{File.basename(f)}" }
|
|
65
|
+
|
|
66
|
+
print "\nCopy these to main project? (y/n): "
|
|
67
|
+
answer = $stdin.gets&.chomp&.downcase
|
|
68
|
+
return unless answer == "y"
|
|
69
|
+
|
|
70
|
+
new_objects.each do |f|
|
|
71
|
+
FileUtils.cp(f, @original_objects_dir)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
if new_primitives.any?
|
|
75
|
+
target_primitives = File.join(File.dirname(@original_objects_dir), "primitives")
|
|
76
|
+
FileUtils.mkdir_p(target_primitives)
|
|
77
|
+
new_primitives.each do |f|
|
|
78
|
+
FileUtils.cp(f, target_primitives)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
puts "Changes copied to main project"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def show_new_files
|
|
86
|
+
new_objects = Dir.glob(File.join(@objects_dir, "*.md")).reject do |sandbox_file|
|
|
87
|
+
original = File.join(@original_objects_dir, File.basename(sandbox_file))
|
|
88
|
+
File.exist?(original) && File.read(sandbox_file) == File.read(original)
|
|
89
|
+
end
|
|
90
|
+
new_primitives = Dir.glob(File.join(@primitives_dir, "*.rb"))
|
|
91
|
+
|
|
92
|
+
{ objects: new_objects, primitives: new_primitives }
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Simple REPL for interacting with a Prompt Object
|
|
97
|
+
class REPL
|
|
98
|
+
def initialize(prompt_object, env, sandbox: nil)
|
|
99
|
+
@po = prompt_object
|
|
100
|
+
@env = env
|
|
101
|
+
@context = env.context
|
|
102
|
+
@show_log = true # Show message log by default
|
|
103
|
+
@sandbox = sandbox
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def run
|
|
107
|
+
puts header
|
|
108
|
+
if @sandbox
|
|
109
|
+
puts "SANDBOX MODE - changes isolated from main project"
|
|
110
|
+
puts
|
|
111
|
+
end
|
|
112
|
+
puts "Loaded: #{@po.name}"
|
|
113
|
+
puts @po.description
|
|
114
|
+
puts
|
|
115
|
+
puts "Capabilities: #{format_capabilities}"
|
|
116
|
+
puts "-" * 60
|
|
117
|
+
puts "Commands: 'exit', 'history', 'log', 'log on/off', 'sandbox' (if in sandbox mode)"
|
|
118
|
+
puts
|
|
119
|
+
|
|
120
|
+
loop do
|
|
121
|
+
print "You: "
|
|
122
|
+
input = $stdin.gets&.chomp
|
|
123
|
+
|
|
124
|
+
break if input.nil?
|
|
125
|
+
next if input.empty?
|
|
126
|
+
|
|
127
|
+
case input.downcase
|
|
128
|
+
when "exit", "quit"
|
|
129
|
+
handle_exit
|
|
130
|
+
break
|
|
131
|
+
when "history"
|
|
132
|
+
show_history
|
|
133
|
+
next
|
|
134
|
+
when "log"
|
|
135
|
+
show_message_log
|
|
136
|
+
next
|
|
137
|
+
when "log on"
|
|
138
|
+
@show_log = true
|
|
139
|
+
puts "Message log display: ON"
|
|
140
|
+
next
|
|
141
|
+
when "log off"
|
|
142
|
+
@show_log = false
|
|
143
|
+
puts "Message log display: OFF"
|
|
144
|
+
next
|
|
145
|
+
when "sandbox"
|
|
146
|
+
show_sandbox_status
|
|
147
|
+
next
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Clear log before each interaction to show only relevant messages
|
|
151
|
+
log_start = @env.bus.log.length
|
|
152
|
+
|
|
153
|
+
begin
|
|
154
|
+
# Log the human message
|
|
155
|
+
@env.bus.publish(from: "human", to: @po.name, message: input)
|
|
156
|
+
|
|
157
|
+
@context.current_capability = @po.name
|
|
158
|
+
response = @po.receive(input, context: @context)
|
|
159
|
+
|
|
160
|
+
# Log the response back to human
|
|
161
|
+
@env.bus.publish(from: @po.name, to: "human", message: response)
|
|
162
|
+
|
|
163
|
+
# Show the message log for this interaction
|
|
164
|
+
if @show_log
|
|
165
|
+
puts
|
|
166
|
+
show_interaction_log(log_start)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
puts
|
|
170
|
+
puts "#{@po.name}: #{response}"
|
|
171
|
+
puts
|
|
172
|
+
rescue StandardError => e
|
|
173
|
+
puts "\nError: #{e.message}"
|
|
174
|
+
puts e.backtrace.first(5).join("\n") if ENV["DEBUG"]
|
|
175
|
+
puts
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
private
|
|
181
|
+
|
|
182
|
+
def header
|
|
183
|
+
<<~HEADER
|
|
184
|
+
|
|
185
|
+
PromptObjects v0.1.0
|
|
186
|
+
|
|
187
|
+
HEADER
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def format_capabilities
|
|
191
|
+
caps = @po.config["capabilities"] || []
|
|
192
|
+
return "(none)" if caps.empty?
|
|
193
|
+
|
|
194
|
+
caps.map do |cap_name|
|
|
195
|
+
cap = @env.registry.get(cap_name)
|
|
196
|
+
if cap.is_a?(PromptObjects::PromptObject)
|
|
197
|
+
"#{cap_name} (PO)"
|
|
198
|
+
elsif cap.is_a?(PromptObjects::Primitive)
|
|
199
|
+
"#{cap_name}"
|
|
200
|
+
else
|
|
201
|
+
"#{cap_name} (?)"
|
|
202
|
+
end
|
|
203
|
+
end.join(", ")
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def handle_exit
|
|
207
|
+
puts "\nGoodbye!"
|
|
208
|
+
return unless @sandbox
|
|
209
|
+
|
|
210
|
+
new_files = @sandbox.show_new_files
|
|
211
|
+
if new_files[:objects].any? || new_files[:primitives].any?
|
|
212
|
+
puts "\nSession created files:"
|
|
213
|
+
new_files[:objects].each { |f| puts " - #{File.basename(f)}" }
|
|
214
|
+
new_files[:primitives].each { |f| puts " - #{File.basename(f)}" }
|
|
215
|
+
@sandbox.cleanup(keep_changes: true)
|
|
216
|
+
else
|
|
217
|
+
puts "No new files created."
|
|
218
|
+
@sandbox.cleanup(keep_changes: false)
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def show_sandbox_status
|
|
223
|
+
unless @sandbox
|
|
224
|
+
puts "Not in sandbox mode. Run with --sandbox to enable."
|
|
225
|
+
return
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
puts "\nSandbox Status"
|
|
229
|
+
puts " Location: #{@sandbox.sandbox_dir}"
|
|
230
|
+
new_files = @sandbox.show_new_files
|
|
231
|
+
if new_files[:objects].any? || new_files[:primitives].any?
|
|
232
|
+
puts " New/modified files this session:"
|
|
233
|
+
new_files[:objects].each { |f| puts " + #{File.basename(f)}" }
|
|
234
|
+
new_files[:primitives].each { |f| puts " + #{File.basename(f)}" }
|
|
235
|
+
else
|
|
236
|
+
puts " No new files created yet."
|
|
237
|
+
end
|
|
238
|
+
puts
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def show_history
|
|
242
|
+
puts "\n--- Conversation History ---"
|
|
243
|
+
@po.history.each_with_index do |msg, i|
|
|
244
|
+
role = msg[:role].to_s.capitalize
|
|
245
|
+
content = msg[:content]&.slice(0, 100)
|
|
246
|
+
content += "..." if msg[:content]&.length.to_i > 100
|
|
247
|
+
puts "#{i + 1}. [#{role}] #{content}"
|
|
248
|
+
end
|
|
249
|
+
puts "----------------------------\n\n"
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def show_message_log
|
|
253
|
+
puts "\n--- Full Message Log ---"
|
|
254
|
+
puts @env.bus.format_log(50)
|
|
255
|
+
puts "------------------------\n\n"
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def show_interaction_log(start_index)
|
|
259
|
+
entries = @env.bus.log[start_index..]
|
|
260
|
+
return if entries.nil? || entries.empty?
|
|
261
|
+
|
|
262
|
+
puts "--- Message Log ---"
|
|
263
|
+
entries.each do |entry|
|
|
264
|
+
time = entry[:timestamp].strftime("%H:%M:%S")
|
|
265
|
+
from = entry[:from]
|
|
266
|
+
to = entry[:to]
|
|
267
|
+
msg = entry[:message]
|
|
268
|
+
|
|
269
|
+
line = "#{time} #{from} -> #{to}: #{msg}"
|
|
270
|
+
line = line[0, 70] + "..." if line.length > 73
|
|
271
|
+
puts line
|
|
272
|
+
end
|
|
273
|
+
puts "-------------------"
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# === CLI Command Handling ===
|
|
278
|
+
|
|
279
|
+
def print_main_help
|
|
280
|
+
puts <<~HELP
|
|
281
|
+
Usage: prompt_objects <command> [options]
|
|
282
|
+
|
|
283
|
+
Commands:
|
|
284
|
+
env Manage environments (create, list, export, etc.)
|
|
285
|
+
serve <env> Run environment as a web server
|
|
286
|
+
repl [name] [objects_dir] Start interactive REPL with a prompt object
|
|
287
|
+
help Show this help message
|
|
288
|
+
|
|
289
|
+
Run 'prompt_objects <command> --help' for command-specific help.
|
|
290
|
+
|
|
291
|
+
Examples:
|
|
292
|
+
prompt_objects env list # List all environments
|
|
293
|
+
prompt_objects env create demo --template demo # Create from template
|
|
294
|
+
prompt_objects serve my-env # Start web UI
|
|
295
|
+
prompt_objects serve my-env --open # Start and open browser
|
|
296
|
+
HELP
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def print_repl_help
|
|
300
|
+
puts <<~HELP
|
|
301
|
+
Usage: prompt_objects repl [options] [name] [objects_dir]
|
|
302
|
+
|
|
303
|
+
Arguments:
|
|
304
|
+
name Prompt object to load (default: greeter)
|
|
305
|
+
objects_dir Directory containing .md files (default: objects)
|
|
306
|
+
|
|
307
|
+
Options:
|
|
308
|
+
--sandbox, -s Run in sandbox mode (isolates changes from main project)
|
|
309
|
+
--help, -h Show this help message
|
|
310
|
+
|
|
311
|
+
Sandbox Mode:
|
|
312
|
+
When running with --sandbox, all new capabilities and prompt objects
|
|
313
|
+
created during the session are stored in a temporary directory.
|
|
314
|
+
On exit, you'll be prompted to copy changes back to the main project.
|
|
315
|
+
HELP
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def print_serve_help
|
|
319
|
+
puts <<~HELP
|
|
320
|
+
Usage: prompt_objects serve [options] <environment>
|
|
321
|
+
|
|
322
|
+
Run an environment as a server, exposing it through different interfaces.
|
|
323
|
+
|
|
324
|
+
Arguments:
|
|
325
|
+
environment Environment name or path to environment directory
|
|
326
|
+
|
|
327
|
+
Options:
|
|
328
|
+
--web Run as web server with React UI (default)
|
|
329
|
+
--mcp Run as MCP server (stdio transport, for Claude Desktop/Cursor)
|
|
330
|
+
--port PORT Port for web server (default: 3000)
|
|
331
|
+
--host HOST Host for web server (default: localhost)
|
|
332
|
+
--open Open browser automatically
|
|
333
|
+
--help, -h Show this help message
|
|
334
|
+
|
|
335
|
+
Examples:
|
|
336
|
+
prompt_objects serve my-assistant # Web UI (default)
|
|
337
|
+
prompt_objects serve my-assistant --port 4000 # Custom port
|
|
338
|
+
prompt_objects serve --mcp my-assistant # MCP server mode
|
|
339
|
+
prompt_objects serve ./my-environment # Serve from path
|
|
340
|
+
|
|
341
|
+
Claude Desktop Configuration (MCP mode):
|
|
342
|
+
Add to claude_desktop_config.json:
|
|
343
|
+
{
|
|
344
|
+
"mcpServers": {
|
|
345
|
+
"my-assistant": {
|
|
346
|
+
"command": "prompt_objects",
|
|
347
|
+
"args": ["serve", "--mcp", "my-assistant"]
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
HELP
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def run_repl(args)
|
|
355
|
+
options = {
|
|
356
|
+
name: "greeter",
|
|
357
|
+
objects_dir: "objects",
|
|
358
|
+
sandbox: false
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
positional = []
|
|
362
|
+
args.each do |arg|
|
|
363
|
+
case arg
|
|
364
|
+
when "--sandbox", "-s"
|
|
365
|
+
options[:sandbox] = true
|
|
366
|
+
when "--help", "-h"
|
|
367
|
+
print_repl_help
|
|
368
|
+
exit 0
|
|
369
|
+
else
|
|
370
|
+
positional << arg
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
options[:name] = positional[0] if positional[0]
|
|
375
|
+
options[:objects_dir] = positional[1] if positional[1]
|
|
376
|
+
|
|
377
|
+
sandbox = nil
|
|
378
|
+
primitives_dir = nil
|
|
379
|
+
|
|
380
|
+
if options[:sandbox]
|
|
381
|
+
sandbox = Sandbox.new(options[:objects_dir])
|
|
382
|
+
objects_dir = sandbox.objects_dir
|
|
383
|
+
primitives_dir = sandbox.primitives_dir
|
|
384
|
+
else
|
|
385
|
+
objects_dir = options[:objects_dir]
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
env = PromptObjects::Runtime.new(objects_dir: objects_dir, primitives_dir: primitives_dir)
|
|
389
|
+
|
|
390
|
+
begin
|
|
391
|
+
po = env.load_by_name(options[:name])
|
|
392
|
+
env.load_dependencies(po)
|
|
393
|
+
rescue PromptObjects::Error => e
|
|
394
|
+
puts "Error loading '#{options[:name]}': #{e.message}"
|
|
395
|
+
puts "\nAvailable objects in '#{objects_dir}/':"
|
|
396
|
+
Dir.glob(File.join(objects_dir, "*.md")).each do |path|
|
|
397
|
+
puts " - #{File.basename(path, '.md')}"
|
|
398
|
+
end
|
|
399
|
+
sandbox&.cleanup(keep_changes: false)
|
|
400
|
+
exit 1
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
REPL.new(po, env, sandbox: sandbox).run
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
def run_serve(args)
|
|
407
|
+
options = {
|
|
408
|
+
web: false,
|
|
409
|
+
mcp: false,
|
|
410
|
+
port: 3000,
|
|
411
|
+
host: "localhost",
|
|
412
|
+
open: false,
|
|
413
|
+
environment: nil
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
skip_next = false
|
|
417
|
+
args.each_with_index do |arg, i|
|
|
418
|
+
if skip_next
|
|
419
|
+
skip_next = false
|
|
420
|
+
next
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
case arg
|
|
424
|
+
when "--web"
|
|
425
|
+
options[:web] = true
|
|
426
|
+
when "--mcp"
|
|
427
|
+
options[:mcp] = true
|
|
428
|
+
when "--port"
|
|
429
|
+
options[:port] = args[i + 1].to_i
|
|
430
|
+
skip_next = true
|
|
431
|
+
when "--host"
|
|
432
|
+
options[:host] = args[i + 1]
|
|
433
|
+
skip_next = true
|
|
434
|
+
when "--open", "-o"
|
|
435
|
+
options[:open] = true
|
|
436
|
+
when "--help", "-h"
|
|
437
|
+
print_serve_help
|
|
438
|
+
exit 0
|
|
439
|
+
else
|
|
440
|
+
# Assume it's the environment
|
|
441
|
+
options[:environment] = arg unless arg.start_with?("-")
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
unless options[:environment]
|
|
446
|
+
puts "Error: environment name or path required"
|
|
447
|
+
puts "Run 'prompt_objects serve --help' for usage"
|
|
448
|
+
exit 1
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
# Default to web if no interface specified
|
|
452
|
+
options[:web] = true unless options[:mcp]
|
|
453
|
+
|
|
454
|
+
# Redirect stderr to a log file to avoid polluting MCP stdio
|
|
455
|
+
if options[:mcp] && ENV["PROMPT_OBJECTS_LOG"]
|
|
456
|
+
$stderr.reopen(ENV["PROMPT_OBJECTS_LOG"], "a")
|
|
457
|
+
$stderr.sync = true
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
# Resolve environment
|
|
461
|
+
env_path = resolve_environment(options[:environment])
|
|
462
|
+
unless env_path
|
|
463
|
+
$stderr.puts "Error: environment '#{options[:environment]}' not found"
|
|
464
|
+
exit 1
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
# Load the runtime
|
|
468
|
+
runtime = PromptObjects::Runtime.new(env_path: env_path)
|
|
469
|
+
|
|
470
|
+
# Load all prompt objects
|
|
471
|
+
load_all_objects(runtime, env_path)
|
|
472
|
+
|
|
473
|
+
if options[:mcp]
|
|
474
|
+
connector = PromptObjects::Connectors::MCP.new(runtime: runtime)
|
|
475
|
+
connector.start
|
|
476
|
+
elsif options[:web]
|
|
477
|
+
require "prompt_objects/server"
|
|
478
|
+
|
|
479
|
+
# Open browser if requested
|
|
480
|
+
if options[:open]
|
|
481
|
+
url = "http://#{options[:host]}:#{options[:port]}"
|
|
482
|
+
open_browser(url)
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
PromptObjects::Server.start(
|
|
486
|
+
runtime: runtime,
|
|
487
|
+
host: options[:host],
|
|
488
|
+
port: options[:port],
|
|
489
|
+
env_path: env_path
|
|
490
|
+
)
|
|
491
|
+
end
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
def open_browser(url)
|
|
495
|
+
case RUBY_PLATFORM
|
|
496
|
+
when /darwin/
|
|
497
|
+
system("open", url)
|
|
498
|
+
when /linux/
|
|
499
|
+
system("xdg-open", url)
|
|
500
|
+
when /mswin|mingw/
|
|
501
|
+
system("start", url)
|
|
502
|
+
end
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
def resolve_environment(name_or_path)
|
|
506
|
+
# Check if it's already a path
|
|
507
|
+
if File.directory?(name_or_path)
|
|
508
|
+
manifest_path = File.join(name_or_path, "manifest.yml")
|
|
509
|
+
return name_or_path if File.exist?(manifest_path)
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
# Check in standard locations
|
|
513
|
+
locations = [
|
|
514
|
+
File.join(Dir.home, ".prompt_objects", "environments", name_or_path),
|
|
515
|
+
File.join(".", name_or_path)
|
|
516
|
+
]
|
|
517
|
+
|
|
518
|
+
locations.each do |path|
|
|
519
|
+
manifest_path = File.join(path, "manifest.yml")
|
|
520
|
+
return path if File.exist?(manifest_path)
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
nil
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
def load_all_objects(runtime, env_path)
|
|
527
|
+
objects_dir = File.join(env_path, "objects")
|
|
528
|
+
return unless Dir.exist?(objects_dir)
|
|
529
|
+
|
|
530
|
+
Dir.glob(File.join(objects_dir, "*.md")).each do |path|
|
|
531
|
+
runtime.load_prompt_object(path)
|
|
532
|
+
rescue StandardError => e
|
|
533
|
+
$stderr.puts "Warning: Failed to load #{path}: #{e.message}"
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
runtime.registry.prompt_objects.each do |po|
|
|
537
|
+
runtime.load_dependencies(po)
|
|
538
|
+
end
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
# === Main Entry Point ===
|
|
542
|
+
|
|
543
|
+
def run_env(args)
|
|
544
|
+
cmd = PromptObjects::CLI::EnvCommand.new
|
|
545
|
+
cmd.run(args)
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
def main
|
|
549
|
+
if ARGV.empty?
|
|
550
|
+
print_main_help
|
|
551
|
+
exit 0
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
command = ARGV[0]
|
|
555
|
+
args = ARGV[1..]
|
|
556
|
+
|
|
557
|
+
case command
|
|
558
|
+
when "env"
|
|
559
|
+
run_env(args)
|
|
560
|
+
when "repl"
|
|
561
|
+
run_repl(args)
|
|
562
|
+
when "serve"
|
|
563
|
+
run_serve(args)
|
|
564
|
+
when "help", "--help", "-h"
|
|
565
|
+
print_main_help
|
|
566
|
+
else
|
|
567
|
+
# Legacy behavior: treat as repl with name argument
|
|
568
|
+
run_repl(ARGV)
|
|
569
|
+
end
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
main
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "bundler/setup"
|
|
5
|
+
require_relative "../lib/prompt_objects"
|
|
6
|
+
require_relative "../lib/prompt_objects/mcp/server"
|
|
7
|
+
|
|
8
|
+
# Parse command line options
|
|
9
|
+
objects_dir = ENV["PROMPT_OBJECTS_DIR"] || "objects"
|
|
10
|
+
primitives_dir = ENV["PROMPT_OBJECTS_PRIMITIVES_DIR"]
|
|
11
|
+
|
|
12
|
+
# Allow overriding via command line
|
|
13
|
+
ARGV.each_with_index do |arg, i|
|
|
14
|
+
case arg
|
|
15
|
+
when "--objects-dir"
|
|
16
|
+
objects_dir = ARGV[i + 1]
|
|
17
|
+
when "--primitives-dir"
|
|
18
|
+
primitives_dir = ARGV[i + 1]
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Redirect stderr to a log file to avoid polluting MCP stdio
|
|
23
|
+
if ENV["PROMPT_OBJECTS_LOG"]
|
|
24
|
+
$stderr.reopen(ENV["PROMPT_OBJECTS_LOG"], "a")
|
|
25
|
+
$stderr.sync = true
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Start the MCP server
|
|
29
|
+
server = PromptObjects::MCP::Server.new(
|
|
30
|
+
objects_dir: objects_dir,
|
|
31
|
+
primitives_dir: primitives_dir
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
server.start
|
data/frontend/.gitignore
ADDED
data/frontend/index.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>PromptObjects</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body class="bg-po-bg text-gray-100">
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|