#!/usr/bin/env ruby # # Piped SDK - Simple Ruby client for executing pipelines # # @param pipeline [String] The pipeline string to execute (e.g., "cat | grep hello | wc -l") # @param input [String, IO] Optional stdin for the pipeline (default: ""). Pass binary string for binary input. # @param key [String] API key. Falls back to PIPED_API_KEY env var if omitted. # @param server_url [String] Optional server URL (default: "https://piped.sh") # @return [String] The pipeline output as a string # @raise [StandardError] If the request fails or returns an error # # @example # # Key from PIPED_API_KEY env var # result = piped("cat | grep hello") # puts result # # @example # # With input and explicit key # result = piped("cat | grep hello", "hello world\nfoo bar", key: "pk_...") # puts result def piped(pipeline, input = "", key: nil, server_url: "https://piped.sh") require "net/http" require "json" require "uri" require "time" require "cgi" if pipeline.nil? || !pipeline.is_a?(String) || pipeline.empty? raise ArgumentError, "pipeline is required and must be a string" end api_key = key || ENV["PIPED_API_KEY"] || "" if api_key.empty? raise ArgumentError, "API key is required: pass key: 'pk_...' or set PIPED_API_KEY env var" end base = server_url.chomp("/") headers = { "X-API-Key" => api_key, "X-User-Time" => Time.now.iso8601, } # Binary input (e.g. piped stdin): raw body with pipeline in query; server detects type from magic bytes if input.is_a?(String) && (input.encoding == Encoding::ASCII_8BIT || input.encoding == Encoding::BINARY) url = URI("#{base}/?pipeline=#{CGI.escape(pipeline)}") request = Net::HTTP::Post.new(url.request_uri) request["Content-Type"] = "application/octet-stream" headers.each { |k, v| request[k] = v } request.body = input else url = URI("#{base}/") body = { pipeline: pipeline, input: input || "" }.to_json request = Net::HTTP::Post.new(url.path) request["Content-Type"] = "application/json" headers.each { |k, v| request[k] = v } request.body = body end http = Net::HTTP.new(url.host, url.port) http.use_ssl = url.scheme == "https" response = http.request(request) unless response.is_a?(Net::HTTPSuccess) error_message = "Request failed with status #{response.code}" begin error_data = JSON.parse(response.body) error_message = error_data["error"] || error_message rescue JSON::ParserError error_message = response.message || error_message end raise StandardError, error_message end response.body end # Command-line interface - run stand-alone: ruby piped.rb [--mcp] if __FILE__ == $0 server_url = (ENV["PIPED_SERVER_URL"] || "https://piped.sh").chomp("/") api_key = ENV["PIPED_API_KEY"] repl_prompt = ENV["PIPED_REPL_PROMPT"] || "piped> " # --mcp: write .mcp.json in current directory (only if missing) if ARGV.length >= 1 && ARGV[0] == "--mcp" require "json" mcp_path = File.join(Dir.pwd, ".mcp.json") if File.exist?(mcp_path) $stderr.puts ".mcp.json already exists" exit 0 end api_key_for_mcp = (api_key && !api_key.empty?) ? api_key : "pk_your_api_key_here" mcp_json = { "mcpServers" => { "piped" => { "type" => "http", "url" => "#{server_url}/mcp", "headers" => { "X-API-Key" => api_key_for_mcp }, }, }, } File.write(mcp_path, JSON.pretty_generate(mcp_json) + "\n") $stderr.puts "Wrote .mcp.json" exit 0 end def usage(script) $stderr.puts "Error: pipeline is required" $stderr.puts "Usage: #{script} [--mcp] " $stderr.puts " --mcp Write .mcp.json in current directory (only if missing)." $stderr.puts "With no arguments and a TTY, enters REPL mode." $stderr.puts "Note: Set PIPED_API_KEY environment variable with your API key" if ENV["PIPED_API_KEY"].to_s.empty? end if ARGV.empty? && !$stdin.tty? usage($0) exit 1 end if ARGV.empty? && $stdin.tty? require "net/http" require "json" require "uri" use_readline = begin require "readline" Readline.set_history_length(100) if Readline.respond_to?(:set_history_length) true rescue LoadError false end read_line = if use_readline -> { Readline.readline(repl_prompt) } else -> { $stdout.write repl_prompt $stdout.flush $stdin.gets } end loop do line = read_line.call break if line.nil? line = line.chomp l = line.strip.sub(/\A\//, "").strip case l.downcase when "exit", "quit" break when "" next end if api_key.to_s.empty? $stderr.puts "Error: PIPED_API_KEY environment variable is required" next end begin result = piped(l, key: api_key, server_url: server_url) print result print "\n" unless result.end_with?("\n") if use_readline && !l.empty? n = Readline::HISTORY.length Readline::HISTORY << l if n.zero? || Readline::HISTORY[n - 1] != l Readline::HISTORY.shift while Readline::HISTORY.length > 100 end rescue StandardError => e $stderr.puts "Error: #{e.message}" end end exit 0 end # Handle "project --manage": POST /project?manage, then open browser if ARGV == ["project", "--config"] require "net/http" require "json" require "uri" if api_key.to_s.empty? $stderr.puts "Error: PIPED_API_KEY environment variable is required" exit 1 end begin url = URI("#{server_url}/project?manage") http = Net::HTTP.new(url.host, url.port) http.use_ssl = url.scheme == "https" req = Net::HTTP::Post.new(url.request_uri) req["X-API-Key"] = api_key req["Content-Length"] = "0" response = http.request(req) unless response.is_a?(Net::HTTPSuccess) begin err = JSON.parse(response.body)["error"] || response.message rescue err = response.message end $stderr.puts "Error: #{err}" exit 1 end config_url = response.body.strip open_cmd = RUBY_PLATFORM =~ /darwin/ ? "open '#{config_url}'" : "xdg-open '#{config_url}'" system(open_cmd) puts "Opening project manager in browser: #{config_url}" exit 0 rescue StandardError => e $stderr.puts "Error: #{e.message}" exit 1 end end # Single-shot: pipeline provided as arguments # When invoked from piped.sh with one arg, that arg is the pre-joined pipeline (quotes preserved). # Otherwise quote each arg and join. pipeline = if ARGV.length == 1 ARGV[0] else def quote_arg(arg) if arg =~ /[\s"'\\$`(){}\[\];&|<>]/ "\"#{arg.gsub(/\\/, '\\\\').gsub(/"/, '\\"')}\"" else arg end end ARGV.map { |a| quote_arg(a) }.join(" ") end if api_key.nil? || api_key.empty? $stderr.puts "Error: PIPED_API_KEY environment variable is required" $stderr.puts "Set it with: export PIPED_API_KEY=\"pk_your_api_key_here\"" exit 1 end input = if $stdin.tty? "" else $stdin.binmode $stdin.read end begin result = piped(pipeline, input, key: api_key, server_url: server_url) puts result exit 0 rescue StandardError => e $stderr.puts "Error: #{e.message}" exit 1 end end