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.
Files changed (117) hide show
  1. checksums.yaml +7 -0
  2. data/CLAUDE.md +108 -0
  3. data/Gemfile +10 -0
  4. data/Gemfile.lock +231 -0
  5. data/IMPLEMENTATION_PLAN.md +1073 -0
  6. data/LICENSE +21 -0
  7. data/README.md +73 -0
  8. data/Rakefile +27 -0
  9. data/design-doc-v2.md +1232 -0
  10. data/exe/prompt_objects +572 -0
  11. data/exe/prompt_objects_mcp +34 -0
  12. data/frontend/.gitignore +3 -0
  13. data/frontend/index.html +13 -0
  14. data/frontend/package-lock.json +4417 -0
  15. data/frontend/package.json +32 -0
  16. data/frontend/postcss.config.js +6 -0
  17. data/frontend/src/App.tsx +95 -0
  18. data/frontend/src/components/CapabilitiesPanel.tsx +44 -0
  19. data/frontend/src/components/ChatPanel.tsx +251 -0
  20. data/frontend/src/components/Dashboard.tsx +83 -0
  21. data/frontend/src/components/Header.tsx +141 -0
  22. data/frontend/src/components/MarkdownMessage.tsx +153 -0
  23. data/frontend/src/components/MessageBus.tsx +55 -0
  24. data/frontend/src/components/ModelSelector.tsx +112 -0
  25. data/frontend/src/components/NotificationPanel.tsx +134 -0
  26. data/frontend/src/components/POCard.tsx +56 -0
  27. data/frontend/src/components/PODetail.tsx +117 -0
  28. data/frontend/src/components/PromptPanel.tsx +51 -0
  29. data/frontend/src/components/SessionsPanel.tsx +174 -0
  30. data/frontend/src/components/ThreadsSidebar.tsx +119 -0
  31. data/frontend/src/components/index.ts +11 -0
  32. data/frontend/src/hooks/useWebSocket.ts +363 -0
  33. data/frontend/src/index.css +37 -0
  34. data/frontend/src/main.tsx +10 -0
  35. data/frontend/src/store/index.ts +246 -0
  36. data/frontend/src/types/index.ts +146 -0
  37. data/frontend/tailwind.config.js +25 -0
  38. data/frontend/tsconfig.json +30 -0
  39. data/frontend/vite.config.ts +29 -0
  40. data/lib/prompt_objects/capability.rb +46 -0
  41. data/lib/prompt_objects/cli.rb +431 -0
  42. data/lib/prompt_objects/connectors/base.rb +73 -0
  43. data/lib/prompt_objects/connectors/mcp.rb +524 -0
  44. data/lib/prompt_objects/environment/exporter.rb +83 -0
  45. data/lib/prompt_objects/environment/git.rb +118 -0
  46. data/lib/prompt_objects/environment/importer.rb +159 -0
  47. data/lib/prompt_objects/environment/manager.rb +401 -0
  48. data/lib/prompt_objects/environment/manifest.rb +218 -0
  49. data/lib/prompt_objects/environment.rb +283 -0
  50. data/lib/prompt_objects/human_queue.rb +144 -0
  51. data/lib/prompt_objects/llm/anthropic_adapter.rb +137 -0
  52. data/lib/prompt_objects/llm/factory.rb +84 -0
  53. data/lib/prompt_objects/llm/gemini_adapter.rb +209 -0
  54. data/lib/prompt_objects/llm/openai_adapter.rb +104 -0
  55. data/lib/prompt_objects/llm/response.rb +61 -0
  56. data/lib/prompt_objects/loader.rb +32 -0
  57. data/lib/prompt_objects/mcp/server.rb +167 -0
  58. data/lib/prompt_objects/mcp/tools/get_conversation.rb +60 -0
  59. data/lib/prompt_objects/mcp/tools/get_pending_requests.rb +54 -0
  60. data/lib/prompt_objects/mcp/tools/inspect_po.rb +73 -0
  61. data/lib/prompt_objects/mcp/tools/list_prompt_objects.rb +37 -0
  62. data/lib/prompt_objects/mcp/tools/respond_to_request.rb +68 -0
  63. data/lib/prompt_objects/mcp/tools/send_message.rb +71 -0
  64. data/lib/prompt_objects/message_bus.rb +97 -0
  65. data/lib/prompt_objects/primitive.rb +13 -0
  66. data/lib/prompt_objects/primitives/http_get.rb +72 -0
  67. data/lib/prompt_objects/primitives/list_files.rb +95 -0
  68. data/lib/prompt_objects/primitives/read_file.rb +81 -0
  69. data/lib/prompt_objects/primitives/write_file.rb +73 -0
  70. data/lib/prompt_objects/prompt_object.rb +415 -0
  71. data/lib/prompt_objects/registry.rb +88 -0
  72. data/lib/prompt_objects/server/api/routes.rb +297 -0
  73. data/lib/prompt_objects/server/app.rb +174 -0
  74. data/lib/prompt_objects/server/file_watcher.rb +113 -0
  75. data/lib/prompt_objects/server/public/assets/index-2acS2FYZ.js +77 -0
  76. data/lib/prompt_objects/server/public/assets/index-DXU5uRXQ.css +1 -0
  77. data/lib/prompt_objects/server/public/index.html +14 -0
  78. data/lib/prompt_objects/server/websocket_handler.rb +619 -0
  79. data/lib/prompt_objects/server.rb +166 -0
  80. data/lib/prompt_objects/session/store.rb +826 -0
  81. data/lib/prompt_objects/universal/add_capability.rb +74 -0
  82. data/lib/prompt_objects/universal/add_primitive.rb +113 -0
  83. data/lib/prompt_objects/universal/ask_human.rb +109 -0
  84. data/lib/prompt_objects/universal/create_capability.rb +219 -0
  85. data/lib/prompt_objects/universal/create_primitive.rb +170 -0
  86. data/lib/prompt_objects/universal/list_capabilities.rb +55 -0
  87. data/lib/prompt_objects/universal/list_primitives.rb +145 -0
  88. data/lib/prompt_objects/universal/modify_primitive.rb +180 -0
  89. data/lib/prompt_objects/universal/request_primitive.rb +287 -0
  90. data/lib/prompt_objects/universal/think.rb +41 -0
  91. data/lib/prompt_objects/universal/verify_primitive.rb +173 -0
  92. data/lib/prompt_objects.rb +62 -0
  93. data/objects/coordinator.md +48 -0
  94. data/objects/greeter.md +30 -0
  95. data/objects/reader.md +33 -0
  96. data/prompt_objects.gemspec +50 -0
  97. data/templates/basic/.gitignore +2 -0
  98. data/templates/basic/manifest.yml +7 -0
  99. data/templates/basic/objects/basic.md +32 -0
  100. data/templates/developer/.gitignore +5 -0
  101. data/templates/developer/manifest.yml +17 -0
  102. data/templates/developer/objects/code_reviewer.md +33 -0
  103. data/templates/developer/objects/coordinator.md +39 -0
  104. data/templates/developer/objects/debugger.md +35 -0
  105. data/templates/empty/.gitignore +5 -0
  106. data/templates/empty/manifest.yml +14 -0
  107. data/templates/empty/objects/.gitkeep +0 -0
  108. data/templates/empty/objects/assistant.md +41 -0
  109. data/templates/minimal/.gitignore +5 -0
  110. data/templates/minimal/manifest.yml +7 -0
  111. data/templates/minimal/objects/assistant.md +41 -0
  112. data/templates/writer/.gitignore +5 -0
  113. data/templates/writer/manifest.yml +17 -0
  114. data/templates/writer/objects/coordinator.md +33 -0
  115. data/templates/writer/objects/editor.md +33 -0
  116. data/templates/writer/objects/researcher.md +34 -0
  117. metadata +343 -0
@@ -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
@@ -0,0 +1,3 @@
1
+ node_modules/
2
+ dist/
3
+ .DS_Store
@@ -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>