prompt_objects 0.5.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 (292) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +142 -0
  3. data/README.md +211 -19
  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 +1350 -18
  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 +190 -432
  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 +561 -52
  65. data/lib/prompt_objects/server/app.rb +134 -1
  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 -207
  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 +774 -15
  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 +78 -0
  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 +58 -0
  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 +57 -0
  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/set_env_data.rb +101 -0
  87. data/lib/prompt_objects/version.rb +5 -0
  88. data/lib/prompt_objects.rb +36 -6
  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 +6 -2
  92. data/templates/arc-agi-1/objects/solver.md +14 -4
  93. data/templates/arc-agi-1/objects/verifier.md +6 -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 +28 -1
  230. metadata +175 -65
  231. data/CLAUDE.md +0 -176
  232. data/Gemfile +0 -10
  233. data/Gemfile.lock +0 -233
  234. data/IMPLEMENTATION_PLAN.md +0 -1073
  235. data/Rakefile +0 -27
  236. data/design-doc-v2.md +0 -1232
  237. data/frontend/.gitignore +0 -3
  238. data/frontend/index.html +0 -17
  239. data/frontend/package-lock.json +0 -4540
  240. data/frontend/package.json +0 -36
  241. data/frontend/postcss.config.js +0 -6
  242. data/frontend/src/App.tsx +0 -103
  243. data/frontend/src/canvas/CanvasView.tsx +0 -113
  244. data/frontend/src/canvas/ForceLayout.ts +0 -115
  245. data/frontend/src/canvas/SceneManager.ts +0 -587
  246. data/frontend/src/canvas/canvasStore.ts +0 -47
  247. data/frontend/src/canvas/constants.ts +0 -95
  248. data/frontend/src/canvas/controls/CameraControls.ts +0 -98
  249. data/frontend/src/canvas/edges/MessageArc.ts +0 -149
  250. data/frontend/src/canvas/inspector/InspectorPanel.tsx +0 -31
  251. data/frontend/src/canvas/inspector/POInspector.tsx +0 -262
  252. data/frontend/src/canvas/inspector/ToolCallInspector.tsx +0 -67
  253. data/frontend/src/canvas/nodes/PONode.ts +0 -249
  254. data/frontend/src/canvas/nodes/ToolCallNode.ts +0 -116
  255. data/frontend/src/canvas/types.ts +0 -24
  256. data/frontend/src/components/ContextMenu.tsx +0 -68
  257. data/frontend/src/components/Inspector.tsx +0 -232
  258. data/frontend/src/components/MarkdownMessage.tsx +0 -155
  259. data/frontend/src/components/MethodList.tsx +0 -90
  260. data/frontend/src/components/ModelSelector.tsx +0 -115
  261. data/frontend/src/components/NotificationPanel.tsx +0 -130
  262. data/frontend/src/components/ObjectList.tsx +0 -78
  263. data/frontend/src/components/PaneSlot.tsx +0 -30
  264. data/frontend/src/components/SourcePane.tsx +0 -202
  265. data/frontend/src/components/SystemBar.tsx +0 -74
  266. data/frontend/src/components/Transcript.tsx +0 -76
  267. data/frontend/src/components/UsagePanel.tsx +0 -105
  268. data/frontend/src/components/Workspace.tsx +0 -260
  269. data/frontend/src/components/index.ts +0 -12
  270. data/frontend/src/hooks/useResize.ts +0 -55
  271. data/frontend/src/hooks/useWebSocket.ts +0 -517
  272. data/frontend/src/index.css +0 -103
  273. data/frontend/src/main.tsx +0 -10
  274. data/frontend/src/store/index.ts +0 -279
  275. data/frontend/src/types/index.ts +0 -165
  276. data/frontend/tailwind.config.js +0 -44
  277. data/frontend/tsconfig.json +0 -30
  278. data/frontend/vite.config.ts +0 -29
  279. data/lib/prompt_objects/server/public/assets/index-D1myxE0l.js +0 -4345
  280. data/lib/prompt_objects/server/public/assets/index-DdCcwC-Z.css +0 -1
  281. data/lib/prompt_objects/universal/add_primitive.rb +0 -118
  282. data/lib/prompt_objects/universal/ask_human.rb +0 -109
  283. data/lib/prompt_objects/universal/create_primitive.rb +0 -174
  284. data/lib/prompt_objects/universal/list_primitives.rb +0 -145
  285. data/lib/prompt_objects/universal/think.rb +0 -41
  286. data/prompt_objects.gemspec +0 -50
  287. data/templates/basic/.gitignore +0 -2
  288. data/templates/developer/.gitignore +0 -5
  289. data/templates/empty/.gitignore +0 -5
  290. data/templates/empty/objects/.gitkeep +0 -0
  291. data/templates/minimal/.gitignore +0 -5
  292. data/templates/writer/.gitignore +0 -5
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module PromptObjects
6
+ module CLI
7
+ # `explore` command: open the Thread Explorer visualizer in a browser.
8
+ class ExploreCommand
9
+ def run(args)
10
+ options = { session_id: nil }
11
+ positional = []
12
+ skip_next = false
13
+
14
+ args.each_with_index do |arg, i|
15
+ if skip_next
16
+ skip_next = false
17
+ next
18
+ end
19
+
20
+ case arg
21
+ when "--session"
22
+ options[:session_id] = args[i + 1]
23
+ skip_next = true
24
+ when "--help", "-h"
25
+ help
26
+ exit 0
27
+ else
28
+ positional << arg
29
+ end
30
+ end
31
+
32
+ explorer_html = File.expand_path("../../../tools/thread-explorer.html", __dir__)
33
+
34
+ # No args: just open the empty visualizer
35
+ unless positional[0]
36
+ Helpers.open_in_browser("file://#{explorer_html}")
37
+ puts "Opened Thread Explorer in browser"
38
+ exit 0
39
+ end
40
+
41
+ env_path = Helpers.resolve_environment(positional[0])
42
+ unless env_path
43
+ $stderr.puts "Error: environment '#{positional[0]}' not found"
44
+ exit 1
45
+ end
46
+
47
+ db_path = File.join(env_path, "sessions.db")
48
+ unless File.exist?(db_path)
49
+ $stderr.puts "No session data found (sessions.db not found)"
50
+ $stderr.puts "Opening empty Thread Explorer..."
51
+ Helpers.open_in_browser("file://#{explorer_html}")
52
+ exit 0
53
+ end
54
+
55
+ store = PromptObjects::Session::Store.new(db_path)
56
+
57
+ if options[:session_id]
58
+ # Export a specific thread
59
+ export_and_open(store, options[:session_id], explorer_html)
60
+ else
61
+ # List sessions and let user choose, or export the most recent root thread
62
+ # Query root sessions directly (list_sessions requires po_name)
63
+ rows = store.instance_variable_get(:@db).execute(<<~SQL)
64
+ SELECT * FROM sessions
65
+ WHERE thread_type IS NULL OR thread_type = 'root'
66
+ ORDER BY created_at ASC
67
+ SQL
68
+ root_sessions = rows.map { |r| { id: r["id"], po_name: r["po_name"], name: r["name"], thread_type: r["thread_type"], created_at: r["created_at"] } }
69
+
70
+ if root_sessions.empty?
71
+ $stderr.puts "No root sessions found"
72
+ Helpers.open_in_browser("file://#{explorer_html}")
73
+ exit 0
74
+ end
75
+
76
+ # Show available sessions
77
+ puts "Root threads:"
78
+ root_sessions.last(10).each_with_index do |s, i|
79
+ name = s[:name] || "(unnamed)"
80
+ po = s[:po_name] || "?"
81
+ time = s[:created_at]
82
+ time_str = time.is_a?(Time) ? time.strftime("%Y-%m-%d %H:%M") : time.to_s[0, 16]
83
+ puts " #{i + 1}. [#{po}] #{name} (#{time_str}) #{s[:id]}"
84
+ end
85
+
86
+ puts
87
+ puts "Opening most recent thread..."
88
+ export_and_open(store, root_sessions.last[:id], explorer_html)
89
+ end
90
+
91
+ store.close
92
+ end
93
+
94
+ def help
95
+ prog = Helpers.program_name
96
+ puts <<~HELP
97
+ Usage: #{prog} explore [env] [options]
98
+
99
+ Open the Thread Explorer to visualize conversation threads.
100
+
101
+ Arguments:
102
+ env Environment name or path (optional)
103
+
104
+ Options:
105
+ --session ID Open a specific thread by session ID
106
+ --help, -h Show this help message
107
+
108
+ Examples:
109
+ #{prog} explore # Open empty explorer
110
+ #{prog} explore my-env # Open most recent thread
111
+ #{prog} explore my-env --session abc123 # Open specific thread
112
+ HELP
113
+ end
114
+
115
+ private
116
+
117
+ def export_and_open(store, session_id, explorer_html)
118
+ data = store.export_thread_tree_json(session_id)
119
+ unless data
120
+ $stderr.puts "Error: session '#{session_id}' not found"
121
+ exit 1
122
+ end
123
+
124
+ json_str = JSON.generate(data)
125
+
126
+ # Read the explorer HTML and inject the data as auto-load
127
+ html_content = File.read(explorer_html)
128
+ inject_script = <<~JS
129
+ <script>
130
+ // Auto-load embedded data
131
+ window.addEventListener('DOMContentLoaded', function() {
132
+ var data = #{json_str};
133
+ if (typeof loadData === 'function') loadData(data);
134
+ });
135
+ </script>
136
+ JS
137
+
138
+ # Insert before </body>
139
+ html_content = html_content.sub("</body>", "#{inject_script}</body>")
140
+
141
+ # Write to temp file
142
+ require "tempfile"
143
+ tmp = Tempfile.new(["thread-explorer-", ".html"])
144
+ tmp.write(html_content)
145
+ tmp.close
146
+
147
+ Helpers.open_in_browser("file://#{tmp.path}")
148
+ puts "Opened Thread Explorer for session #{session_id}"
149
+
150
+ # Keep temp file around briefly so the browser can load it
151
+ sleep 2
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptObjects
4
+ module CLI
5
+ # Shared helpers for CLI commands.
6
+ module Helpers
7
+ module_function
8
+
9
+ # The name the CLI was invoked as (e.g. "prompt_objects" or "poop").
10
+ # Used in usage/help text so each executable describes itself.
11
+ def program_name
12
+ name = File.basename($PROGRAM_NAME.to_s)
13
+ name.empty? ? "prompt_objects" : name
14
+ end
15
+
16
+ # Resolve an environment name or path to an environment directory.
17
+ # @param name_or_path [String]
18
+ # @return [String, nil] Path to the environment, or nil if not found
19
+ def resolve_environment(name_or_path)
20
+ # Check if it's already a path
21
+ if File.directory?(name_or_path)
22
+ manifest_path = File.join(name_or_path, "manifest.yml")
23
+ return name_or_path if File.exist?(manifest_path)
24
+ end
25
+
26
+ # Check in standard locations
27
+ locations = [
28
+ File.join(Dir.home, ".prompt_objects", "environments", name_or_path),
29
+ File.join(".", name_or_path)
30
+ ]
31
+
32
+ locations.each do |path|
33
+ manifest_path = File.join(path, "manifest.yml")
34
+ return path if File.exist?(manifest_path)
35
+ end
36
+
37
+ nil
38
+ end
39
+
40
+ # Load all prompt objects from an environment into a runtime.
41
+ def load_all_objects(runtime, env_path)
42
+ objects_dir = File.join(env_path, "objects")
43
+ return unless Dir.exist?(objects_dir)
44
+
45
+ Dir.glob(File.join(objects_dir, "*.md")).each do |path|
46
+ runtime.load_prompt_object(path)
47
+ rescue StandardError => e
48
+ $stderr.puts "Warning: Failed to load #{path}: #{e.message}"
49
+ end
50
+
51
+ runtime.registry.prompt_objects.each do |po|
52
+ runtime.load_dependencies(po)
53
+ end
54
+ end
55
+
56
+ # Open a URL in the platform browser.
57
+ def open_in_browser(url)
58
+ case RUBY_PLATFORM
59
+ when /darwin/
60
+ system("open", url)
61
+ when /linux/
62
+ system("xdg-open", url)
63
+ when /mswin|mingw|cygwin/
64
+ system("start", url)
65
+ else
66
+ $stderr.puts "Open this URL in your browser: #{url}"
67
+ end
68
+ end
69
+
70
+ # Print a list of bus events in the standard one-line format.
71
+ def print_events(events)
72
+ if events.empty?
73
+ puts "No events found."
74
+ return
75
+ end
76
+
77
+ events.each do |e|
78
+ ts = e[:timestamp] || e["timestamp"]
79
+ ts = ts.is_a?(Time) ? ts.strftime("%H:%M:%S") : ts&.split("T")&.last&.split(".")&.first
80
+ from = e[:from] || e["from"]
81
+ to = e[:to] || e["to"]
82
+ summary = e[:summary] || e["summary"]
83
+
84
+ line = "#{ts} #{from} -> #{to}: #{summary}"
85
+ line = line[0, 120] + "..." if line.length > 123
86
+ puts line
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,216 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module PromptObjects
6
+ module CLI
7
+ # `message` command: send a message to a PO and print the response.
8
+ # Routes through a running server when one exists, standalone otherwise.
9
+ class MessageCommand
10
+ def run(args)
11
+ options = {
12
+ json: false,
13
+ events: false,
14
+ new_thread: false,
15
+ session_id: nil
16
+ }
17
+
18
+ positional = []
19
+ skip_next = false
20
+
21
+ args.each_with_index do |arg, i|
22
+ if skip_next
23
+ skip_next = false
24
+ next
25
+ end
26
+
27
+ case arg
28
+ when "--json"
29
+ options[:json] = true
30
+ when "--events"
31
+ options[:events] = true
32
+ when "--new-thread"
33
+ options[:new_thread] = true
34
+ when "--session"
35
+ options[:session_id] = args[i + 1]
36
+ skip_next = true
37
+ when "--help", "-h"
38
+ help
39
+ exit 0
40
+ else
41
+ positional << arg
42
+ end
43
+ end
44
+
45
+ env_name = positional[0]
46
+ po_name = positional[1]
47
+ message_text = positional[2]
48
+
49
+ unless env_name && po_name && message_text
50
+ puts "Error: environment, po_name, and message are required"
51
+ puts "Run '#{Helpers.program_name} message --help' for usage"
52
+ exit 1
53
+ end
54
+
55
+ # Resolve environment
56
+ env_path = Helpers.resolve_environment(env_name)
57
+ unless env_path
58
+ $stderr.puts "Error: environment '#{env_name}' not found"
59
+ exit 1
60
+ end
61
+
62
+ # Check for running server
63
+ require "prompt_objects/server"
64
+ server_info = PromptObjects::Server.read_server_file(env_path)
65
+
66
+ if server_info
67
+ # Send via HTTP to running server
68
+ send_via_http(server_info, po_name, message_text, options)
69
+ else
70
+ # Standalone mode
71
+ send_standalone(env_path, po_name, message_text, options)
72
+ end
73
+ end
74
+
75
+ def help
76
+ prog = Helpers.program_name
77
+ puts <<~HELP
78
+ Usage: #{prog} message [options] <environment> <po_name> "message"
79
+
80
+ Send a message to a prompt object and print the response.
81
+
82
+ Arguments:
83
+ environment Environment name or path
84
+ po_name Name of the prompt object to message
85
+ message The message to send (quoted string)
86
+
87
+ Options:
88
+ --json Output response as JSON
89
+ --events Also print the event log for this interaction
90
+ --new-thread Start a new thread for this message
91
+ --session ID Continue a specific thread ID (session alias)
92
+ --help, -h Show this help
93
+
94
+ If a server is running for the environment, the message is sent via
95
+ HTTP to the running server (and streams to the web UI in real time).
96
+ Otherwise, a standalone runtime is started for the request.
97
+
98
+ Examples:
99
+ #{prog} message my-env solver "What patterns do you see?"
100
+ #{prog} message my-env solver "Solve task 1" --json
101
+ #{prog} message my-env solver "Try again" --new-thread
102
+ HELP
103
+ end
104
+
105
+ private
106
+
107
+ def send_via_http(server_info, po_name, message_text, options)
108
+ require "net/http"
109
+ require "uri"
110
+
111
+ uri = URI("http://#{server_info[:host]}:#{server_info[:port]}/api/prompt_objects/#{po_name}/message")
112
+
113
+ body = { message: message_text }
114
+ body[:new_thread] = true if options[:new_thread]
115
+ body[:session_id] = options[:session_id] if options[:session_id]
116
+ body[:client_request_id] = SecureRandom.uuid
117
+ body[:from_source] = "api:cli"
118
+
119
+ begin
120
+ response = Net::HTTP.post(uri, body.to_json, "Content-Type" => "application/json")
121
+ data = JSON.parse(response.body)
122
+
123
+ if data["error"]
124
+ $stderr.puts "Error: #{data['error']}"
125
+ exit 1
126
+ end
127
+
128
+ if options[:json]
129
+ puts JSON.pretty_generate(data)
130
+ else
131
+ puts data["response"]
132
+ end
133
+
134
+ if options[:events] && data["session_id"]
135
+ events_uri = URI("http://#{server_info[:host]}:#{server_info[:port]}/api/events/session/#{data['session_id']}")
136
+ events_response = Net::HTTP.get(events_uri)
137
+ events_data = JSON.parse(events_response)
138
+
139
+ if events_data["events"]&.any?
140
+ $stderr.puts
141
+ $stderr.puts "--- Events ---"
142
+ events_data["events"].each do |e|
143
+ $stderr.puts "#{e['timestamp']} #{e['from']} -> #{e['to']}: #{e['summary']}"
144
+ end
145
+ $stderr.puts "--------------"
146
+ end
147
+ end
148
+ rescue Errno::ECONNREFUSED
149
+ $stderr.puts "Error: Server at #{server_info[:host]}:#{server_info[:port]} is not responding"
150
+ $stderr.puts "The .server file may be stale. Try running standalone."
151
+ exit 1
152
+ end
153
+ end
154
+
155
+ def send_standalone(env_path, po_name, message_text, options)
156
+ runtime = PromptObjects::Runtime.new(env_path: env_path)
157
+ Helpers.load_all_objects(runtime, env_path)
158
+
159
+ po = runtime.registry.get(po_name)
160
+ unless po.is_a?(PromptObjects::PromptObject)
161
+ $stderr.puts "Error: prompt object '#{po_name}' not found"
162
+ $stderr.puts "Available: #{runtime.loaded_objects.join(', ')}"
163
+ exit 1
164
+ end
165
+
166
+ workspace_id = runtime.active_session(initiated_by: "api:cli")[:id]
167
+ thread_id = options[:session_id]
168
+ if options[:new_thread]
169
+ thread_id = runtime.create_runtime_thread(
170
+ workspace_session_id: workspace_id,
171
+ po_name: po_name,
172
+ thread_type: "api",
173
+ name: "CLI thread"
174
+ ).id
175
+ end
176
+ run, response = runtime.execute_message_run(
177
+ target_po: po_name,
178
+ content: message_text,
179
+ from_source: "api:cli",
180
+ workspace_session_id: workspace_id,
181
+ thread_id: thread_id,
182
+ client_request_id: SecureRandom.uuid,
183
+ tui_mode: true
184
+ )
185
+
186
+ if options[:json]
187
+ puts JSON.pretty_generate(
188
+ response: response,
189
+ po_name: po_name,
190
+ workspace_session_id: run.workspace_session_id,
191
+ thread_id: run.thread_id,
192
+ session_id: run.thread_id,
193
+ run_id: run.id,
194
+ status: run.status
195
+ )
196
+ else
197
+ puts response
198
+ end
199
+
200
+ print_run_events(runtime, run.id) if options[:events]
201
+ end
202
+
203
+ def print_run_events(runtime, run_id)
204
+ events = Repositories::EventRepository.new(runtime.session_store).run_events(run_id: run_id)
205
+ return if events.empty?
206
+
207
+ $stderr.puts
208
+ $stderr.puts "--- Run Events ---"
209
+ events.each do |event|
210
+ $stderr.puts "#{event.timestamp.strftime('%H:%M:%S')} #{event.kind}"
211
+ end
212
+ $stderr.puts "------------------"
213
+ end
214
+ end
215
+ end
216
+ end