prompt_objects 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CLAUDE.md +108 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +231 -0
- data/IMPLEMENTATION_PLAN.md +1073 -0
- data/LICENSE +21 -0
- data/README.md +73 -0
- data/Rakefile +27 -0
- data/design-doc-v2.md +1232 -0
- data/exe/prompt_objects +572 -0
- data/exe/prompt_objects_mcp +34 -0
- data/frontend/.gitignore +3 -0
- data/frontend/index.html +13 -0
- data/frontend/package-lock.json +4417 -0
- data/frontend/package.json +32 -0
- data/frontend/postcss.config.js +6 -0
- data/frontend/src/App.tsx +95 -0
- data/frontend/src/components/CapabilitiesPanel.tsx +44 -0
- data/frontend/src/components/ChatPanel.tsx +251 -0
- data/frontend/src/components/Dashboard.tsx +83 -0
- data/frontend/src/components/Header.tsx +141 -0
- data/frontend/src/components/MarkdownMessage.tsx +153 -0
- data/frontend/src/components/MessageBus.tsx +55 -0
- data/frontend/src/components/ModelSelector.tsx +112 -0
- data/frontend/src/components/NotificationPanel.tsx +134 -0
- data/frontend/src/components/POCard.tsx +56 -0
- data/frontend/src/components/PODetail.tsx +117 -0
- data/frontend/src/components/PromptPanel.tsx +51 -0
- data/frontend/src/components/SessionsPanel.tsx +174 -0
- data/frontend/src/components/ThreadsSidebar.tsx +119 -0
- data/frontend/src/components/index.ts +11 -0
- data/frontend/src/hooks/useWebSocket.ts +363 -0
- data/frontend/src/index.css +37 -0
- data/frontend/src/main.tsx +10 -0
- data/frontend/src/store/index.ts +246 -0
- data/frontend/src/types/index.ts +146 -0
- data/frontend/tailwind.config.js +25 -0
- data/frontend/tsconfig.json +30 -0
- data/frontend/vite.config.ts +29 -0
- data/lib/prompt_objects/capability.rb +46 -0
- data/lib/prompt_objects/cli.rb +431 -0
- data/lib/prompt_objects/connectors/base.rb +73 -0
- data/lib/prompt_objects/connectors/mcp.rb +524 -0
- data/lib/prompt_objects/environment/exporter.rb +83 -0
- data/lib/prompt_objects/environment/git.rb +118 -0
- data/lib/prompt_objects/environment/importer.rb +159 -0
- data/lib/prompt_objects/environment/manager.rb +401 -0
- data/lib/prompt_objects/environment/manifest.rb +218 -0
- data/lib/prompt_objects/environment.rb +283 -0
- data/lib/prompt_objects/human_queue.rb +144 -0
- data/lib/prompt_objects/llm/anthropic_adapter.rb +137 -0
- data/lib/prompt_objects/llm/factory.rb +84 -0
- data/lib/prompt_objects/llm/gemini_adapter.rb +209 -0
- data/lib/prompt_objects/llm/openai_adapter.rb +104 -0
- data/lib/prompt_objects/llm/response.rb +61 -0
- data/lib/prompt_objects/loader.rb +32 -0
- data/lib/prompt_objects/mcp/server.rb +167 -0
- data/lib/prompt_objects/mcp/tools/get_conversation.rb +60 -0
- data/lib/prompt_objects/mcp/tools/get_pending_requests.rb +54 -0
- data/lib/prompt_objects/mcp/tools/inspect_po.rb +73 -0
- data/lib/prompt_objects/mcp/tools/list_prompt_objects.rb +37 -0
- data/lib/prompt_objects/mcp/tools/respond_to_request.rb +68 -0
- data/lib/prompt_objects/mcp/tools/send_message.rb +71 -0
- data/lib/prompt_objects/message_bus.rb +97 -0
- data/lib/prompt_objects/primitive.rb +13 -0
- data/lib/prompt_objects/primitives/http_get.rb +72 -0
- data/lib/prompt_objects/primitives/list_files.rb +95 -0
- data/lib/prompt_objects/primitives/read_file.rb +81 -0
- data/lib/prompt_objects/primitives/write_file.rb +73 -0
- data/lib/prompt_objects/prompt_object.rb +415 -0
- data/lib/prompt_objects/registry.rb +88 -0
- data/lib/prompt_objects/server/api/routes.rb +297 -0
- data/lib/prompt_objects/server/app.rb +174 -0
- data/lib/prompt_objects/server/file_watcher.rb +113 -0
- data/lib/prompt_objects/server/public/assets/index-2acS2FYZ.js +77 -0
- data/lib/prompt_objects/server/public/assets/index-DXU5uRXQ.css +1 -0
- data/lib/prompt_objects/server/public/index.html +14 -0
- data/lib/prompt_objects/server/websocket_handler.rb +619 -0
- data/lib/prompt_objects/server.rb +166 -0
- data/lib/prompt_objects/session/store.rb +826 -0
- data/lib/prompt_objects/universal/add_capability.rb +74 -0
- data/lib/prompt_objects/universal/add_primitive.rb +113 -0
- data/lib/prompt_objects/universal/ask_human.rb +109 -0
- data/lib/prompt_objects/universal/create_capability.rb +219 -0
- data/lib/prompt_objects/universal/create_primitive.rb +170 -0
- data/lib/prompt_objects/universal/list_capabilities.rb +55 -0
- data/lib/prompt_objects/universal/list_primitives.rb +145 -0
- data/lib/prompt_objects/universal/modify_primitive.rb +180 -0
- data/lib/prompt_objects/universal/request_primitive.rb +287 -0
- data/lib/prompt_objects/universal/think.rb +41 -0
- data/lib/prompt_objects/universal/verify_primitive.rb +173 -0
- data/lib/prompt_objects.rb +62 -0
- data/objects/coordinator.md +48 -0
- data/objects/greeter.md +30 -0
- data/objects/reader.md +33 -0
- data/prompt_objects.gemspec +50 -0
- data/templates/basic/.gitignore +2 -0
- data/templates/basic/manifest.yml +7 -0
- data/templates/basic/objects/basic.md +32 -0
- data/templates/developer/.gitignore +5 -0
- data/templates/developer/manifest.yml +17 -0
- data/templates/developer/objects/code_reviewer.md +33 -0
- data/templates/developer/objects/coordinator.md +39 -0
- data/templates/developer/objects/debugger.md +35 -0
- data/templates/empty/.gitignore +5 -0
- data/templates/empty/manifest.yml +14 -0
- data/templates/empty/objects/.gitkeep +0 -0
- data/templates/empty/objects/assistant.md +41 -0
- data/templates/minimal/.gitignore +5 -0
- data/templates/minimal/manifest.yml +7 -0
- data/templates/minimal/objects/assistant.md +41 -0
- data/templates/writer/.gitignore +5 -0
- data/templates/writer/manifest.yml +17 -0
- data/templates/writer/objects/coordinator.md +33 -0
- data/templates/writer/objects/editor.md +33 -0
- data/templates/writer/objects/researcher.md +34 -0
- metadata +343 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "uri"
|
|
5
|
+
|
|
6
|
+
module PromptObjects
|
|
7
|
+
module Primitives
|
|
8
|
+
# Primitive capability to fetch content from a URL.
|
|
9
|
+
class HttpGet < Primitive
|
|
10
|
+
def name
|
|
11
|
+
"http_get"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def description
|
|
15
|
+
"Fetch content from a URL via HTTP GET request"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def parameters
|
|
19
|
+
{
|
|
20
|
+
type: "object",
|
|
21
|
+
properties: {
|
|
22
|
+
url: {
|
|
23
|
+
type: "string",
|
|
24
|
+
description: "The URL to fetch"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
required: ["url"]
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def receive(message, context:)
|
|
32
|
+
url = message[:url] || message["url"]
|
|
33
|
+
|
|
34
|
+
return "Error: URL is required" if url.nil? || url.empty?
|
|
35
|
+
|
|
36
|
+
begin
|
|
37
|
+
uri = URI.parse(url)
|
|
38
|
+
|
|
39
|
+
unless %w[http https].include?(uri.scheme)
|
|
40
|
+
return "Error: Only http and https URLs are supported"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
response = Net::HTTP.get_response(uri)
|
|
44
|
+
|
|
45
|
+
case response
|
|
46
|
+
when Net::HTTPSuccess
|
|
47
|
+
content = response.body.to_s
|
|
48
|
+
|
|
49
|
+
# Truncate very large responses
|
|
50
|
+
if content.length > 50_000
|
|
51
|
+
content = content[0, 50_000] + "\n\n... [truncated, response is #{content.length} bytes]"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
content
|
|
55
|
+
when Net::HTTPRedirection
|
|
56
|
+
"Redirected to: #{response['location']}"
|
|
57
|
+
else
|
|
58
|
+
"HTTP Error: #{response.code} #{response.message}"
|
|
59
|
+
end
|
|
60
|
+
rescue URI::InvalidURIError
|
|
61
|
+
"Error: Invalid URL format"
|
|
62
|
+
rescue SocketError => e
|
|
63
|
+
"Error: Could not connect - #{e.message}"
|
|
64
|
+
rescue Timeout::Error
|
|
65
|
+
"Error: Request timed out"
|
|
66
|
+
rescue StandardError => e
|
|
67
|
+
"Error fetching URL: #{e.message}"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PromptObjects
|
|
4
|
+
module Primitives
|
|
5
|
+
# Primitive capability to list files in a directory.
|
|
6
|
+
class ListFiles < Primitive
|
|
7
|
+
def name
|
|
8
|
+
"list_files"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def description
|
|
12
|
+
"List files and directories in a given path"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def parameters
|
|
16
|
+
{
|
|
17
|
+
type: "object",
|
|
18
|
+
properties: {
|
|
19
|
+
path: {
|
|
20
|
+
type: "string",
|
|
21
|
+
description: "The directory path to list (defaults to current directory)"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
required: []
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def receive(message, context:)
|
|
29
|
+
path = extract_path(message)
|
|
30
|
+
path = "." if path.nil? || path.empty?
|
|
31
|
+
|
|
32
|
+
safe_path = File.expand_path(path)
|
|
33
|
+
|
|
34
|
+
unless File.exist?(safe_path)
|
|
35
|
+
return "Error: Path not found: #{path}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
unless File.directory?(safe_path)
|
|
39
|
+
return "Error: Not a directory: #{path}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
entries = Dir.entries(safe_path)
|
|
43
|
+
.reject { |e| e.start_with?(".") } # Hide hidden files
|
|
44
|
+
.sort
|
|
45
|
+
.map { |entry| format_entry(safe_path, entry) }
|
|
46
|
+
|
|
47
|
+
if entries.empty?
|
|
48
|
+
"Directory is empty: #{path}"
|
|
49
|
+
else
|
|
50
|
+
entries.join("\n")
|
|
51
|
+
end
|
|
52
|
+
rescue Errno::EACCES
|
|
53
|
+
"Error: Permission denied: #{path}"
|
|
54
|
+
rescue StandardError => e
|
|
55
|
+
"Error listing directory: #{e.message}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def extract_path(message)
|
|
61
|
+
case message
|
|
62
|
+
when Hash
|
|
63
|
+
message[:path] || message["path"]
|
|
64
|
+
when String
|
|
65
|
+
message
|
|
66
|
+
when NilClass
|
|
67
|
+
"."
|
|
68
|
+
else
|
|
69
|
+
message.to_s
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def format_entry(base_path, entry)
|
|
74
|
+
full_path = File.join(base_path, entry)
|
|
75
|
+
|
|
76
|
+
if File.directory?(full_path)
|
|
77
|
+
"#{entry}/"
|
|
78
|
+
else
|
|
79
|
+
size = File.size(full_path)
|
|
80
|
+
"#{entry} (#{human_size(size)})"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def human_size(bytes)
|
|
85
|
+
return "0 B" if bytes == 0
|
|
86
|
+
|
|
87
|
+
units = ["B", "KB", "MB", "GB"]
|
|
88
|
+
exp = (Math.log(bytes) / Math.log(1024)).to_i
|
|
89
|
+
exp = units.length - 1 if exp >= units.length
|
|
90
|
+
|
|
91
|
+
"%.1f %s" % [bytes.to_f / (1024 ** exp), units[exp]]
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PromptObjects
|
|
4
|
+
module Primitives
|
|
5
|
+
# Primitive capability to read file contents.
|
|
6
|
+
class ReadFile < Primitive
|
|
7
|
+
def name
|
|
8
|
+
"read_file"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def description
|
|
12
|
+
"Read the contents of a text file"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def parameters
|
|
16
|
+
{
|
|
17
|
+
type: "object",
|
|
18
|
+
properties: {
|
|
19
|
+
path: {
|
|
20
|
+
type: "string",
|
|
21
|
+
description: "The path to the file to read"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
required: ["path"]
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def receive(message, context:)
|
|
29
|
+
path = extract_path(message)
|
|
30
|
+
safe_path = validate_path(path)
|
|
31
|
+
|
|
32
|
+
unless File.exist?(safe_path)
|
|
33
|
+
return "Error: File not found: #{path}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
unless File.file?(safe_path)
|
|
37
|
+
return "Error: Not a file: #{path}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
content = File.read(safe_path, encoding: "UTF-8")
|
|
41
|
+
|
|
42
|
+
# Truncate very large files
|
|
43
|
+
if content.length > 50_000
|
|
44
|
+
content = content[0, 50_000] + "\n\n... [truncated, file is #{content.length} bytes]"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
content
|
|
48
|
+
rescue Errno::EACCES
|
|
49
|
+
"Error: Permission denied: #{path}"
|
|
50
|
+
rescue StandardError => e
|
|
51
|
+
"Error reading file: #{e.message}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def extract_path(message)
|
|
57
|
+
case message
|
|
58
|
+
when Hash
|
|
59
|
+
message[:path] || message["path"]
|
|
60
|
+
when String
|
|
61
|
+
message
|
|
62
|
+
else
|
|
63
|
+
message.to_s
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def validate_path(path)
|
|
68
|
+
# Expand and normalize the path
|
|
69
|
+
expanded = File.expand_path(path)
|
|
70
|
+
|
|
71
|
+
# Basic safety: don't allow reading outside current directory tree
|
|
72
|
+
# In a real implementation, you'd want more robust sandboxing
|
|
73
|
+
cwd = File.expand_path(".")
|
|
74
|
+
|
|
75
|
+
# Allow absolute paths but warn if outside cwd
|
|
76
|
+
# For now, we'll be permissive but could restrict this
|
|
77
|
+
expanded
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PromptObjects
|
|
4
|
+
module Primitives
|
|
5
|
+
# Primitive capability to write content to a file.
|
|
6
|
+
class WriteFile < Primitive
|
|
7
|
+
def name
|
|
8
|
+
"write_file"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def description
|
|
12
|
+
"Write content to a file (creates or overwrites)"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def parameters
|
|
16
|
+
{
|
|
17
|
+
type: "object",
|
|
18
|
+
properties: {
|
|
19
|
+
path: {
|
|
20
|
+
type: "string",
|
|
21
|
+
description: "The path to the file to write"
|
|
22
|
+
},
|
|
23
|
+
content: {
|
|
24
|
+
type: "string",
|
|
25
|
+
description: "The content to write to the file"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
required: ["path", "content"]
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def receive(message, context:)
|
|
33
|
+
path = extract_param(message, :path)
|
|
34
|
+
content = extract_param(message, :content)
|
|
35
|
+
|
|
36
|
+
if path.nil? || path.empty?
|
|
37
|
+
return "Error: path is required"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
if content.nil?
|
|
41
|
+
return "Error: content is required"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
safe_path = File.expand_path(path)
|
|
45
|
+
|
|
46
|
+
# Create parent directories if they don't exist
|
|
47
|
+
dir = File.dirname(safe_path)
|
|
48
|
+
FileUtils.mkdir_p(dir) unless File.directory?(dir)
|
|
49
|
+
|
|
50
|
+
File.write(safe_path, content, encoding: "UTF-8")
|
|
51
|
+
|
|
52
|
+
"Successfully wrote #{content.length} bytes to #{path}"
|
|
53
|
+
rescue Errno::EACCES
|
|
54
|
+
"Error: Permission denied: #{path}"
|
|
55
|
+
rescue StandardError => e
|
|
56
|
+
"Error writing file: #{e.message}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def extract_param(message, key)
|
|
62
|
+
case message
|
|
63
|
+
when Hash
|
|
64
|
+
message[key] || message[key.to_s]
|
|
65
|
+
else
|
|
66
|
+
nil
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
require "fileutils"
|