prompt_objects 0.6.0 → 0.7.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 (291) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +126 -0
  3. data/README.md +93 -22
  4. data/exe/poop +5 -0
  5. data/exe/prompt_objects +1 -1108
  6. data/exe/prompt_objects_mcp +5 -3
  7. data/lib/prompt_objects/capability.rb +33 -0
  8. data/lib/prompt_objects/cli/diagnostics_command.rb +69 -0
  9. data/lib/prompt_objects/cli/env_command.rb +435 -0
  10. data/lib/prompt_objects/cli/events_command.rb +145 -0
  11. data/lib/prompt_objects/cli/explore_command.rb +155 -0
  12. data/lib/prompt_objects/cli/helpers.rb +91 -0
  13. data/lib/prompt_objects/cli/message_command.rb +216 -0
  14. data/lib/prompt_objects/cli/repl_command.rb +366 -0
  15. data/lib/prompt_objects/cli/serve_command.rb +162 -0
  16. data/lib/prompt_objects/cli.rb +64 -422
  17. data/lib/prompt_objects/connectors/base.rb +16 -29
  18. data/lib/prompt_objects/connectors/mcp.rb +82 -98
  19. data/lib/prompt_objects/domain/records.rb +45 -0
  20. data/lib/prompt_objects/environment/exporter.rb +5 -4
  21. data/lib/prompt_objects/environment/git.rb +21 -1
  22. data/lib/prompt_objects/environment/importer.rb +5 -4
  23. data/lib/prompt_objects/environment/manager.rb +44 -6
  24. data/lib/prompt_objects/environment/manifest.rb +26 -4
  25. data/lib/prompt_objects/environment.rb +1337 -20
  26. data/lib/prompt_objects/execution/blocking_executor.rb +88 -0
  27. data/lib/prompt_objects/execution/context.rb +137 -0
  28. data/lib/prompt_objects/execution/contracts.rb +59 -0
  29. data/lib/prompt_objects/execution/permit_pool.rb +49 -0
  30. data/lib/prompt_objects/execution/policy.rb +47 -0
  31. data/lib/prompt_objects/execution/resource_lock_manager.rb +63 -0
  32. data/lib/prompt_objects/execution/runner.rb +498 -0
  33. data/lib/prompt_objects/execution/scheduler.rb +325 -0
  34. data/lib/prompt_objects/llm/factory.rb +4 -4
  35. data/lib/prompt_objects/llm/gemini_adapter.rb +25 -8
  36. data/lib/prompt_objects/llm/openai_adapter.rb +9 -1
  37. data/lib/prompt_objects/llm/pricing.rb +9 -2
  38. data/lib/prompt_objects/llm/response.rb +9 -4
  39. data/lib/prompt_objects/loader.rb +28 -1
  40. data/lib/prompt_objects/mcp/server.rb +6 -3
  41. data/lib/prompt_objects/mcp/tools/get_conversation.rb +15 -11
  42. data/lib/prompt_objects/mcp/tools/get_pending_requests.rb +2 -2
  43. data/lib/prompt_objects/mcp/tools/respond_to_request.rb +3 -3
  44. data/lib/prompt_objects/mcp/tools/send_message.rb +42 -32
  45. data/lib/prompt_objects/operations/diagnostics.rb +115 -0
  46. data/lib/prompt_objects/operations/retention.rb +75 -0
  47. data/lib/prompt_objects/operations/run_trace_builder.rb +152 -0
  48. data/lib/prompt_objects/primitives/http_get.rb +2 -0
  49. data/lib/prompt_objects/primitives/list_files.rb +2 -0
  50. data/lib/prompt_objects/primitives/read_file.rb +2 -0
  51. data/lib/prompt_objects/primitives/think.rb +36 -0
  52. data/lib/prompt_objects/primitives/write_file.rb +9 -0
  53. data/lib/prompt_objects/prompt_object.rb +171 -519
  54. data/lib/prompt_objects/repositories/artifact_repository.rb +109 -0
  55. data/lib/prompt_objects/repositories/base_repository.rb +37 -0
  56. data/lib/prompt_objects/repositories/definition_repository.rb +36 -0
  57. data/lib/prompt_objects/repositories/env_data_repository.rb +103 -0
  58. data/lib/prompt_objects/repositories/event_repository.rb +105 -0
  59. data/lib/prompt_objects/repositories/message_repository.rb +73 -0
  60. data/lib/prompt_objects/repositories/run_repository.rb +254 -0
  61. data/lib/prompt_objects/repositories/thread_repository.rb +87 -0
  62. data/lib/prompt_objects/repositories/tool_execution_repository.rb +88 -0
  63. data/lib/prompt_objects/repositories/workspace_session_repository.rb +61 -0
  64. data/lib/prompt_objects/server/api/routes.rb +549 -53
  65. data/lib/prompt_objects/server/app.rb +133 -14
  66. data/lib/prompt_objects/server/file_watcher.rb +9 -0
  67. data/lib/prompt_objects/server/public/assets/index-BjKlODDD.js +90 -0
  68. data/lib/prompt_objects/server/public/assets/index-w3fpIb5c.css +1 -0
  69. data/lib/prompt_objects/server/public/index.html +2 -2
  70. data/lib/prompt_objects/server/websocket_handler.rb +202 -227
  71. data/lib/prompt_objects/server.rb +170 -1
  72. data/lib/prompt_objects/service.rb +129 -0
  73. data/lib/prompt_objects/session/store.rb +600 -13
  74. data/lib/prompt_objects/universal/add_capability.rb +1 -1
  75. data/lib/prompt_objects/universal/create_capability.rb +4 -2
  76. data/lib/prompt_objects/universal/delete_env_data.rb +23 -15
  77. data/lib/prompt_objects/universal/delete_primitive.rb +1 -1
  78. data/lib/prompt_objects/universal/dispatch_po.rb +54 -0
  79. data/lib/prompt_objects/universal/get_env_data.rb +8 -14
  80. data/lib/prompt_objects/universal/get_run_status.rb +30 -0
  81. data/lib/prompt_objects/universal/join_runs.rb +35 -0
  82. data/lib/prompt_objects/universal/list_env_data.rb +10 -14
  83. data/lib/prompt_objects/universal/modify_primitive.rb +1 -1
  84. data/lib/prompt_objects/universal/render_artifact.rb +91 -0
  85. data/lib/prompt_objects/universal/request_primitive.rb +1 -1
  86. data/lib/prompt_objects/universal/{store_env_data.rb → set_env_data.rb} +34 -20
  87. data/lib/prompt_objects/version.rb +5 -0
  88. data/lib/prompt_objects.rb +33 -8
  89. data/objects/coordinator.md +8 -4
  90. data/templates/arc-agi-1/objects/data_manager.md +2 -2
  91. data/templates/arc-agi-1/objects/observer.md +2 -2
  92. data/templates/arc-agi-1/objects/solver.md +6 -5
  93. data/templates/arc-agi-1/objects/verifier.md +2 -2
  94. data/templates/arc-agi-3/manifest.yml +19 -0
  95. data/templates/arc-agi-3/objects/analyst.md +79 -0
  96. data/templates/arc-agi-3/objects/player.md +76 -0
  97. data/templates/arc-agi-3/objects/strategist.md +97 -0
  98. data/templates/arc-agi-3/primitives/action_log.rb +133 -0
  99. data/templates/arc-agi-3/primitives/arc3_api.rb +223 -0
  100. data/templates/arc-agi-3/primitives/arc3_scorecard.rb +131 -0
  101. data/templates/arc-agi-3/primitives/frame_diff.rb +143 -0
  102. data/templates/arc-agi-3/primitives/frame_info.rb +96 -0
  103. data/templates/arc-agi-3/primitives/render_frame.rb +140 -0
  104. data/templates/basic/objects/basic.md +11 -3
  105. data/templates/chiptune/extras/sonic_pi_boot.rb +220 -0
  106. data/templates/chiptune/manifest.yml +20 -0
  107. data/templates/chiptune/objects/conductor.md +184 -0
  108. data/templates/chiptune/objects/sonic_pi.md +65 -0
  109. data/templates/chiptune/objects/voice_bass.md +44 -0
  110. data/templates/chiptune/objects/voice_lead.md +44 -0
  111. data/templates/chiptune/objects/voice_pad.md +43 -0
  112. data/templates/chiptune/objects/voice_perc.md +45 -0
  113. data/templates/chiptune/primitives/play_note.rb +59 -0
  114. data/templates/chiptune/primitives/play_sequence.rb +55 -0
  115. data/templates/chiptune/primitives/query_sonic_pi.rb +141 -0
  116. data/templates/chiptune/primitives/set_synth.rb +57 -0
  117. data/templates/chiptune/primitives/set_tempo.rb +45 -0
  118. data/templates/chiptune/primitives/stop_voice.rb +46 -0
  119. data/templates/chiptune/primitives/support/osc_bridge.rb +33 -0
  120. data/templates/chiptune/vendor/osc-ruby-1.1.5/LICENSE +19 -0
  121. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
  122. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
  123. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
  124. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
  125. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
  126. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
  127. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
  128. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
  129. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
  130. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
  131. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
  132. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
  133. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
  134. data/templates/developer/objects/code_reviewer.md +1 -1
  135. data/templates/developer/objects/coordinator.md +16 -3
  136. data/templates/developer/objects/debugger.md +3 -3
  137. data/templates/empty/objects/assistant.md +10 -5
  138. data/templates/minimal/objects/assistant.md +10 -5
  139. data/templates/music/extras/sonic_pi_boot.rb +220 -0
  140. data/templates/music/manifest.yml +16 -0
  141. data/templates/music/objects/conductor.md +154 -0
  142. data/templates/music/objects/sonic_pi.md +65 -0
  143. data/templates/music/primitives/play_note.rb +55 -0
  144. data/templates/music/primitives/play_sequence.rb +51 -0
  145. data/templates/music/primitives/query_sonic_pi.rb +139 -0
  146. data/templates/music/primitives/set_synth.rb +53 -0
  147. data/templates/music/primitives/set_tempo.rb +43 -0
  148. data/templates/music/primitives/stop_voice.rb +44 -0
  149. data/templates/music/primitives/support/osc_bridge.rb +33 -0
  150. data/templates/music/vendor/osc-ruby-1.1.5/LICENSE +19 -0
  151. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
  152. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
  153. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
  154. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
  155. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
  156. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
  157. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
  158. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
  159. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
  160. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
  161. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
  162. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
  163. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
  164. data/templates/music-beginner/README.md +100 -0
  165. data/templates/music-beginner/extras/README.md +27 -0
  166. data/templates/music-beginner/extras/sonic_pi_boot.rb +297 -0
  167. data/templates/music-beginner/manifest.yml +25 -0
  168. data/templates/music-beginner/objects/conductor.md +222 -0
  169. data/templates/music-beginner/objects/engine_setup.md +54 -0
  170. data/templates/music-beginner/objects/voice_bass.md +43 -0
  171. data/templates/music-beginner/objects/voice_keys.md +48 -0
  172. data/templates/music-beginner/objects/voice_pad.md +46 -0
  173. data/templates/music-beginner/objects/voice_percussion.md +45 -0
  174. data/templates/music-beginner/objects/voice_texture.md +37 -0
  175. data/templates/music-beginner/primitives/play_pattern.rb +118 -0
  176. data/templates/music-beginner/primitives/query_music_engine.rb +94 -0
  177. data/templates/music-beginner/primitives/set_instrument.rb +47 -0
  178. data/templates/music-beginner/primitives/set_tempo.rb +43 -0
  179. data/templates/music-beginner/primitives/set_voice_level.rb +46 -0
  180. data/templates/music-beginner/primitives/stop_voice.rb +37 -0
  181. data/templates/music-beginner/primitives/support/osc_bridge.rb +25 -0
  182. data/templates/music-beginner/vendor/osc-ruby-1.1.5/LICENSE +19 -0
  183. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
  184. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
  185. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
  186. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
  187. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
  188. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
  189. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
  190. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
  191. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
  192. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
  193. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
  194. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
  195. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
  196. data/templates/music-pro/extras/AUDITION.md +100 -0
  197. data/templates/music-pro/extras/sonic_pi_boot.rb +546 -0
  198. data/templates/music-pro/manifest.yml +19 -0
  199. data/templates/music-pro/objects/conductor.md +126 -0
  200. data/templates/music-pro/objects/sonic_pi.md +35 -0
  201. data/templates/music-pro/objects/voice_harmony.md +48 -0
  202. data/templates/music-pro/objects/voice_rhythm.md +43 -0
  203. data/templates/music-pro/objects/voice_texture.md +42 -0
  204. data/templates/music-pro/primitives/define_patch.rb +46 -0
  205. data/templates/music-pro/primitives/query_music_engine.rb +69 -0
  206. data/templates/music-pro/primitives/set_live_pattern.rb +70 -0
  207. data/templates/music-pro/primitives/set_tempo.rb +31 -0
  208. data/templates/music-pro/primitives/stop_voice.rb +31 -0
  209. data/templates/music-pro/primitives/support/music_protocol.rb +136 -0
  210. data/templates/music-pro/primitives/support/osc_bridge.rb +33 -0
  211. data/templates/music-pro/primitives/trigger_event.rb +44 -0
  212. data/templates/music-pro/vendor/osc-ruby-1.1.5/LICENSE +19 -0
  213. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
  214. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
  215. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
  216. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
  217. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
  218. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
  219. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
  220. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
  221. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
  222. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
  223. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
  224. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
  225. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
  226. data/templates/writer/objects/coordinator.md +14 -3
  227. data/templates/writer/objects/editor.md +1 -1
  228. data/templates/writer/objects/researcher.md +2 -2
  229. data/tools/thread-explorer.html +1 -1
  230. metadata +172 -65
  231. data/CLAUDE.md +0 -177
  232. data/Gemfile +0 -10
  233. data/Rakefile +0 -27
  234. data/frontend/.gitignore +0 -3
  235. data/frontend/index.html +0 -17
  236. data/frontend/package-lock.json +0 -4540
  237. data/frontend/package.json +0 -36
  238. data/frontend/postcss.config.js +0 -6
  239. data/frontend/src/App.tsx +0 -104
  240. data/frontend/src/canvas/CanvasView.tsx +0 -113
  241. data/frontend/src/canvas/ForceLayout.ts +0 -115
  242. data/frontend/src/canvas/SceneManager.ts +0 -587
  243. data/frontend/src/canvas/canvasStore.ts +0 -47
  244. data/frontend/src/canvas/constants.ts +0 -95
  245. data/frontend/src/canvas/controls/CameraControls.ts +0 -98
  246. data/frontend/src/canvas/edges/MessageArc.ts +0 -149
  247. data/frontend/src/canvas/inspector/InspectorPanel.tsx +0 -31
  248. data/frontend/src/canvas/inspector/POInspector.tsx +0 -262
  249. data/frontend/src/canvas/inspector/ToolCallInspector.tsx +0 -67
  250. data/frontend/src/canvas/nodes/PONode.ts +0 -249
  251. data/frontend/src/canvas/nodes/ToolCallNode.ts +0 -116
  252. data/frontend/src/canvas/types.ts +0 -24
  253. data/frontend/src/components/ContextMenu.tsx +0 -68
  254. data/frontend/src/components/EnvDataPane.tsx +0 -69
  255. data/frontend/src/components/Inspector.tsx +0 -263
  256. data/frontend/src/components/MarkdownMessage.tsx +0 -155
  257. data/frontend/src/components/MethodList.tsx +0 -90
  258. data/frontend/src/components/ModelSelector.tsx +0 -115
  259. data/frontend/src/components/NotificationPanel.tsx +0 -130
  260. data/frontend/src/components/ObjectList.tsx +0 -78
  261. data/frontend/src/components/PaneSlot.tsx +0 -30
  262. data/frontend/src/components/SourcePane.tsx +0 -202
  263. data/frontend/src/components/SystemBar.tsx +0 -74
  264. data/frontend/src/components/Transcript.tsx +0 -76
  265. data/frontend/src/components/UsagePanel.tsx +0 -105
  266. data/frontend/src/components/Workspace.tsx +0 -260
  267. data/frontend/src/components/index.ts +0 -12
  268. data/frontend/src/hooks/useResize.ts +0 -55
  269. data/frontend/src/hooks/useWebSocket.ts +0 -559
  270. data/frontend/src/index.css +0 -102
  271. data/frontend/src/main.tsx +0 -10
  272. data/frontend/src/store/index.ts +0 -311
  273. data/frontend/src/types/index.ts +0 -176
  274. data/frontend/tailwind.config.js +0 -44
  275. data/frontend/tsconfig.json +0 -30
  276. data/frontend/vite.config.ts +0 -29
  277. data/lib/prompt_objects/server/public/assets/index-DEPawnfZ.js +0 -4345
  278. data/lib/prompt_objects/server/public/assets/index-oMrRce1m.css +0 -1
  279. data/lib/prompt_objects/universal/add_primitive.rb +0 -118
  280. data/lib/prompt_objects/universal/ask_human.rb +0 -109
  281. data/lib/prompt_objects/universal/create_primitive.rb +0 -174
  282. data/lib/prompt_objects/universal/list_primitives.rb +0 -145
  283. data/lib/prompt_objects/universal/think.rb +0 -41
  284. data/lib/prompt_objects/universal/update_env_data.rb +0 -88
  285. data/prompt_objects.gemspec +0 -50
  286. data/templates/basic/.gitignore +0 -2
  287. data/templates/developer/.gitignore +0 -5
  288. data/templates/empty/.gitignore +0 -5
  289. data/templates/empty/objects/.gitkeep +0 -0
  290. data/templates/minimal/.gitignore +0 -5
  291. data/templates/writer/.gitignore +0 -5
@@ -0,0 +1,366 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "tmpdir"
5
+
6
+ module PromptObjects
7
+ module CLI
8
+ # Sandbox manages an isolated environment for testing
9
+ class Sandbox
10
+ attr_reader :sandbox_dir, :objects_dir, :primitives_dir, :original_objects_dir
11
+
12
+ def initialize(original_objects_dir)
13
+ @original_objects_dir = File.expand_path(original_objects_dir)
14
+ @sandbox_dir = Dir.mktmpdir("prompt_objects_sandbox_")
15
+ @objects_dir = File.join(@sandbox_dir, "objects")
16
+ @primitives_dir = File.join(@sandbox_dir, "primitives")
17
+
18
+ setup
19
+ end
20
+
21
+ def setup
22
+ FileUtils.mkdir_p(@objects_dir)
23
+ FileUtils.mkdir_p(@primitives_dir)
24
+
25
+ # Copy existing objects to sandbox
26
+ if Dir.exist?(@original_objects_dir)
27
+ Dir.glob(File.join(@original_objects_dir, "*.md")).each do |src|
28
+ FileUtils.cp(src, @objects_dir)
29
+ end
30
+ end
31
+
32
+ puts "Sandbox initialized at: #{@sandbox_dir}"
33
+ puts " Objects copied: #{Dir.glob(File.join(@objects_dir, '*.md')).count}"
34
+ end
35
+
36
+ def cleanup(keep_changes: false)
37
+ if keep_changes
38
+ copy_back_changes
39
+ end
40
+ FileUtils.rm_rf(@sandbox_dir)
41
+ puts "Sandbox cleaned up"
42
+ end
43
+
44
+ def copy_back_changes
45
+ # Find new/modified objects
46
+ new_objects = Dir.glob(File.join(@objects_dir, "*.md")).select do |sandbox_file|
47
+ original = File.join(@original_objects_dir, File.basename(sandbox_file))
48
+ !File.exist?(original) || File.read(sandbox_file) != File.read(original)
49
+ end
50
+
51
+ # Find new primitives
52
+ new_primitives = Dir.glob(File.join(@primitives_dir, "*.rb"))
53
+
54
+ return if new_objects.empty? && new_primitives.empty?
55
+
56
+ puts "\nChanges to copy back:"
57
+ new_objects.each { |f| puts " + objects/#{File.basename(f)}" }
58
+ new_primitives.each { |f| puts " + primitives/#{File.basename(f)}" }
59
+
60
+ print "\nCopy these to main project? (y/n): "
61
+ answer = $stdin.gets&.chomp&.downcase
62
+ return unless answer == "y"
63
+
64
+ new_objects.each do |f|
65
+ FileUtils.cp(f, @original_objects_dir)
66
+ end
67
+
68
+ if new_primitives.any?
69
+ target_primitives = File.join(File.dirname(@original_objects_dir), "primitives")
70
+ FileUtils.mkdir_p(target_primitives)
71
+ new_primitives.each do |f|
72
+ FileUtils.cp(f, target_primitives)
73
+ end
74
+ end
75
+
76
+ puts "Changes copied to main project"
77
+ end
78
+
79
+ def show_new_files
80
+ new_objects = Dir.glob(File.join(@objects_dir, "*.md")).reject do |sandbox_file|
81
+ original = File.join(@original_objects_dir, File.basename(sandbox_file))
82
+ File.exist?(original) && File.read(sandbox_file) == File.read(original)
83
+ end
84
+ new_primitives = Dir.glob(File.join(@primitives_dir, "*.rb"))
85
+
86
+ { objects: new_objects, primitives: new_primitives }
87
+ end
88
+ end
89
+
90
+ # Simple REPL for interacting with a Prompt Object
91
+ class REPL
92
+ def initialize(prompt_object, env, sandbox: nil)
93
+ @po = prompt_object
94
+ @env = env
95
+ @context = env.context
96
+ @show_log = true # Show message log by default
97
+ @sandbox = sandbox
98
+ @runtime_thread_id = nil
99
+ @last_run_id = nil
100
+ end
101
+
102
+ def run
103
+ puts header
104
+ if @sandbox
105
+ puts "SANDBOX MODE - changes isolated from main project"
106
+ puts
107
+ end
108
+ puts "Loaded: #{@po.name}"
109
+ puts @po.description
110
+ puts
111
+ puts "Capabilities: #{format_capabilities}"
112
+ puts "-" * 60
113
+ puts "Commands: 'exit', 'history', 'log', 'log on/off', 'sandbox' (if in sandbox mode)"
114
+ puts
115
+
116
+ loop do
117
+ print "You: "
118
+ input = $stdin.gets&.chomp
119
+
120
+ break if input.nil?
121
+ next if input.empty?
122
+
123
+ case input.downcase
124
+ when "exit", "quit"
125
+ handle_exit
126
+ break
127
+ when "history"
128
+ show_history
129
+ next
130
+ when "log"
131
+ show_message_log
132
+ next
133
+ when "log on"
134
+ @show_log = true
135
+ puts "Message log display: ON"
136
+ next
137
+ when "log off"
138
+ @show_log = false
139
+ puts "Message log display: OFF"
140
+ next
141
+ when "sandbox"
142
+ show_sandbox_status
143
+ next
144
+ end
145
+
146
+ # Clear log before each interaction to show only relevant messages
147
+ log_start = @env.bus.log.length
148
+
149
+ begin
150
+ run, response = @env.execute_message_run(
151
+ target_po: @po.name,
152
+ content: input,
153
+ from_source: "api:repl",
154
+ thread_id: @runtime_thread_id,
155
+ client_request_id: SecureRandom.uuid,
156
+ tui_mode: true
157
+ )
158
+ @runtime_thread_id = run.thread_id
159
+ @last_run_id = run.id
160
+
161
+ # Show the message log for this interaction
162
+ if @show_log
163
+ puts
164
+ show_interaction_log(log_start)
165
+ end
166
+
167
+ puts
168
+ puts "#{@po.name}: #{response}"
169
+ puts
170
+ rescue StandardError => e
171
+ puts "\nError: #{e.message}"
172
+ puts e.backtrace.first(5).join("\n") if ENV["DEBUG"]
173
+ puts
174
+ end
175
+ end
176
+ end
177
+
178
+ private
179
+
180
+ def header
181
+ <<~HEADER
182
+
183
+ PromptObjects v#{PromptObjects::VERSION}
184
+
185
+ HEADER
186
+ end
187
+
188
+ def format_capabilities
189
+ caps = @po.config["capabilities"] || []
190
+ return "(none)" if caps.empty?
191
+
192
+ caps.map do |cap_name|
193
+ cap = @env.registry.get(cap_name)
194
+ if cap.is_a?(PromptObjects::PromptObject)
195
+ "#{cap_name} (PO)"
196
+ elsif cap.is_a?(PromptObjects::Primitive)
197
+ "#{cap_name}"
198
+ else
199
+ "#{cap_name} (?)"
200
+ end
201
+ end.join(", ")
202
+ end
203
+
204
+ def handle_exit
205
+ puts "\nGoodbye!"
206
+ return unless @sandbox
207
+
208
+ new_files = @sandbox.show_new_files
209
+ if new_files[:objects].any? || new_files[:primitives].any?
210
+ puts "\nSession created files:"
211
+ new_files[:objects].each { |f| puts " - #{File.basename(f)}" }
212
+ new_files[:primitives].each { |f| puts " - #{File.basename(f)}" }
213
+ @sandbox.cleanup(keep_changes: true)
214
+ else
215
+ puts "No new files created."
216
+ @sandbox.cleanup(keep_changes: false)
217
+ end
218
+ end
219
+
220
+ def show_sandbox_status
221
+ unless @sandbox
222
+ puts "Not in sandbox mode. Run with --sandbox to enable."
223
+ return
224
+ end
225
+
226
+ puts "\nSandbox Status"
227
+ puts " Location: #{@sandbox.sandbox_dir}"
228
+ new_files = @sandbox.show_new_files
229
+ if new_files[:objects].any? || new_files[:primitives].any?
230
+ puts " New/modified files this session:"
231
+ new_files[:objects].each { |f| puts " + #{File.basename(f)}" }
232
+ new_files[:primitives].each { |f| puts " + #{File.basename(f)}" }
233
+ else
234
+ puts " No new files created yet."
235
+ end
236
+ puts
237
+ end
238
+
239
+ def show_history
240
+ puts "\n--- Conversation History ---"
241
+ history = if @runtime_thread_id
242
+ Repositories::MessageRepository.new(@env.session_store)
243
+ .list(thread_id: @runtime_thread_id, limit: 10_000)
244
+ else
245
+ []
246
+ end
247
+ history.each_with_index do |msg, i|
248
+ role = msg[:role].to_s.capitalize
249
+ content = msg[:content]&.slice(0, 100)
250
+ content += "..." if msg[:content]&.length.to_i > 100
251
+ puts "#{i + 1}. [#{role}] #{content}"
252
+ end
253
+ puts "----------------------------\n\n"
254
+ end
255
+
256
+ def show_message_log
257
+ puts "\n--- Full Message Log ---"
258
+ puts @env.bus.format_log(50)
259
+ puts "------------------------\n\n"
260
+ end
261
+
262
+ def show_interaction_log(start_index)
263
+ if @last_run_id
264
+ events = Repositories::EventRepository.new(@env.session_store).run_events(run_id: @last_run_id)
265
+ return if events.empty?
266
+
267
+ puts "--- Run Events ---"
268
+ events.each { |event| puts "#{event.timestamp.strftime('%H:%M:%S')} #{event.kind}" }
269
+ puts "------------------"
270
+ return
271
+ end
272
+
273
+ entries = @env.bus.log[start_index..]
274
+ return if entries.nil? || entries.empty?
275
+
276
+ puts "--- Message Log ---"
277
+ entries.each do |entry|
278
+ time = entry[:timestamp].strftime("%H:%M:%S")
279
+ from = entry[:from]
280
+ to = entry[:to]
281
+ msg = entry[:summary]
282
+
283
+ line = "#{time} #{from} -> #{to}: #{msg}"
284
+ line = line[0, 70] + "..." if line.length > 73
285
+ puts line
286
+ end
287
+ puts "-------------------"
288
+ end
289
+ end
290
+
291
+ # `repl` command: interactive session with a prompt object.
292
+ class ReplCommand
293
+ def run(args)
294
+ options = {
295
+ name: "greeter",
296
+ objects_dir: "objects",
297
+ sandbox: false
298
+ }
299
+
300
+ positional = []
301
+ args.each do |arg|
302
+ case arg
303
+ when "--sandbox", "-s"
304
+ options[:sandbox] = true
305
+ when "--help", "-h"
306
+ help
307
+ exit 0
308
+ else
309
+ positional << arg
310
+ end
311
+ end
312
+
313
+ options[:name] = positional[0] if positional[0]
314
+ options[:objects_dir] = positional[1] if positional[1]
315
+
316
+ sandbox = nil
317
+ primitives_dir = nil
318
+
319
+ if options[:sandbox]
320
+ sandbox = Sandbox.new(options[:objects_dir])
321
+ objects_dir = sandbox.objects_dir
322
+ primitives_dir = sandbox.primitives_dir
323
+ else
324
+ objects_dir = options[:objects_dir]
325
+ end
326
+
327
+ env = PromptObjects::Runtime.new(objects_dir: objects_dir, primitives_dir: primitives_dir)
328
+
329
+ begin
330
+ po = env.load_by_name(options[:name])
331
+ env.load_dependencies(po)
332
+ rescue PromptObjects::Error => e
333
+ puts "Error loading '#{options[:name]}': #{e.message}"
334
+ puts "\nAvailable objects in '#{objects_dir}/':"
335
+ Dir.glob(File.join(objects_dir, "*.md")).each do |path|
336
+ puts " - #{File.basename(path, '.md')}"
337
+ end
338
+ sandbox&.cleanup(keep_changes: false)
339
+ exit 1
340
+ end
341
+
342
+ REPL.new(po, env, sandbox: sandbox).run
343
+ end
344
+
345
+ def help
346
+ prog = Helpers.program_name
347
+ puts <<~HELP
348
+ Usage: #{prog} repl [options] [name] [objects_dir]
349
+
350
+ Arguments:
351
+ name Prompt object to load (default: greeter)
352
+ objects_dir Directory containing .md files (default: objects)
353
+
354
+ Options:
355
+ --sandbox, -s Run in sandbox mode (isolates changes from main project)
356
+ --help, -h Show this help message
357
+
358
+ Sandbox Mode:
359
+ When running with --sandbox, all new capabilities and prompt objects
360
+ created during the session are stored in a temporary directory.
361
+ On exit, you'll be prompted to copy changes back to the main project.
362
+ HELP
363
+ end
364
+ end
365
+ end
366
+ end
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptObjects
4
+ module CLI
5
+ # `serve` command: run an environment as a web or MCP server.
6
+ class ServeCommand
7
+ def run(args)
8
+ options = {
9
+ web: false,
10
+ mcp: false,
11
+ port: 3000,
12
+ host: "localhost",
13
+ open: false,
14
+ environment: nil
15
+ }
16
+
17
+ skip_next = false
18
+ args.each_with_index do |arg, i|
19
+ if skip_next
20
+ skip_next = false
21
+ next
22
+ end
23
+
24
+ case arg
25
+ when "--web"
26
+ options[:web] = true
27
+ when "--mcp"
28
+ options[:mcp] = true
29
+ when "--port"
30
+ options[:port] = args[i + 1].to_i
31
+ skip_next = true
32
+ when "--host"
33
+ options[:host] = args[i + 1]
34
+ skip_next = true
35
+ when "--open", "-o"
36
+ options[:open] = true
37
+ when "--help", "-h"
38
+ help
39
+ exit 0
40
+ else
41
+ # Assume it's the environment
42
+ options[:environment] = arg unless arg.start_with?("-")
43
+ end
44
+ end
45
+
46
+ unless options[:environment]
47
+ puts "Error: environment name or path required"
48
+ puts "Run '#{Helpers.program_name} serve --help' for usage"
49
+ exit 1
50
+ end
51
+
52
+ # Default to web if no interface specified
53
+ options[:web] = true unless options[:mcp]
54
+
55
+ # Redirect stderr to a log file to avoid polluting MCP stdio
56
+ if options[:mcp] && ENV["PROMPT_OBJECTS_LOG"]
57
+ $stderr.reopen(ENV["PROMPT_OBJECTS_LOG"], "a")
58
+ $stderr.sync = true
59
+ end
60
+
61
+ # Resolve environment
62
+ env_path = Helpers.resolve_environment(options[:environment])
63
+ unless env_path
64
+ $stderr.puts "Error: environment '#{options[:environment]}' not found"
65
+ exit 1
66
+ end
67
+
68
+ # Load the runtime
69
+ runtime = PromptObjects::Runtime.new(env_path: env_path)
70
+
71
+ # Load all prompt objects
72
+ Helpers.load_all_objects(runtime, env_path)
73
+
74
+ if options[:mcp]
75
+ connector = PromptObjects::Connectors::MCP.new(runtime: runtime)
76
+ connector.start
77
+ elsif options[:web]
78
+ require "prompt_objects/server"
79
+
80
+ # Rebuild frontend to ensure built assets match source
81
+ rebuild_frontend
82
+
83
+ # Open browser if requested
84
+ if options[:open]
85
+ url = "http://#{options[:host]}:#{options[:port]}"
86
+ Helpers.open_in_browser(url)
87
+ end
88
+
89
+ PromptObjects::Server.start(
90
+ runtime: runtime,
91
+ host: options[:host],
92
+ port: options[:port],
93
+ env_path: env_path
94
+ )
95
+ end
96
+ end
97
+
98
+ def help
99
+ prog = Helpers.program_name
100
+ puts <<~HELP
101
+ Usage: #{prog} serve [options] <environment>
102
+
103
+ Run an environment as a server, exposing it through different interfaces.
104
+
105
+ Arguments:
106
+ environment Environment name or path to environment directory
107
+
108
+ Options:
109
+ --web Run as web server with React UI (default)
110
+ --mcp Run as MCP server (stdio transport, for Claude Desktop/Cursor)
111
+ --port PORT Port for web server (default: 3000)
112
+ --host HOST Host for web server (default: localhost)
113
+ --open Open browser automatically
114
+ --help, -h Show this help message
115
+
116
+ Examples:
117
+ #{prog} serve my-assistant # Web UI (default)
118
+ #{prog} serve my-assistant --port 4000 # Custom port
119
+ #{prog} serve --mcp my-assistant # MCP server mode
120
+ #{prog} serve ./my-environment # Serve from path
121
+
122
+ Claude Desktop Configuration (MCP mode):
123
+ Add to claude_desktop_config.json:
124
+ {
125
+ "mcpServers": {
126
+ "my-assistant": {
127
+ "command": "#{prog}",
128
+ "args": ["serve", "--mcp", "my-assistant"]
129
+ }
130
+ }
131
+ }
132
+ HELP
133
+ end
134
+
135
+ private
136
+
137
+ def rebuild_frontend
138
+ gem_root = File.expand_path("../../..", __dir__)
139
+ frontend_dir = File.join(gem_root, "frontend")
140
+
141
+ unless Dir.exist?(frontend_dir)
142
+ # No frontend source (e.g., installed as gem without source) — skip
143
+ return
144
+ end
145
+
146
+ node_modules = File.join(frontend_dir, "node_modules")
147
+ unless Dir.exist?(node_modules)
148
+ puts "Frontend dependencies not installed. Run: cd frontend && npm install"
149
+ return
150
+ end
151
+
152
+ print "Building frontend... "
153
+ result = system("npm run build --prefix #{frontend_dir}", out: File::NULL, err: File::NULL)
154
+ if result
155
+ puts "done"
156
+ else
157
+ puts "failed (serving stale assets)"
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end