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,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