stakwork-cli 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c532528e8fdf9e6ce51774140b9d9b35e30a5aa9d2ef178e2ea211fe5692b974
4
+ data.tar.gz: 0be1d458ffd97d73ac60f7256da4bb151a29c891b3432e092f325e608dd8263e
5
+ SHA512:
6
+ metadata.gz: 83f75216f8be15134d037be12848ccaae6654ac8cb4c76a24445f42eb56e8839ceb7cb3f2d9d2b0fa8f00aec6d1575d261db328ac42232c0690ca4d9489845a6
7
+ data.tar.gz: b7bb3fbcb4d6219f9f64cb77011e70c693b86b03e0b2c091a873ec9e5e90632684ca2e0c8a551a31b6b6ab4090a38241384979ef0c380406bb853e696c95bc10
data/bin/stakwork ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.join(__dir__, '../lib')
3
+ require 'stakwork_cli'
4
+ begin
5
+ StakworkCli::CLI.start(ARGV)
6
+ rescue StakworkCli::ApiError => e
7
+ $stderr.puts e.body
8
+ exit 1
9
+ end
@@ -0,0 +1,84 @@
1
+ require 'net/http'
2
+ require 'json'
3
+ require 'uri'
4
+
5
+ module StakworkCli
6
+ class ApiError < StandardError
7
+ attr_reader :status, :body
8
+
9
+ def initialize(status, body)
10
+ @status = status
11
+ @body = body
12
+ super("API error #{status}: #{body}")
13
+ end
14
+ end
15
+
16
+ class Client
17
+ def initialize(token:, base_url:)
18
+ @token = token
19
+ @base_url = base_url.chomp('/')
20
+ @uri = URI.parse(@base_url)
21
+ warn_if_insecure
22
+ end
23
+
24
+ def get(path, params: {})
25
+ uri = build_uri(path, params)
26
+ request = Net::HTTP::Get.new(uri)
27
+ perform(request, uri)
28
+ end
29
+
30
+ def post(path, body: {})
31
+ uri = build_uri(path)
32
+ request = Net::HTTP::Post.new(uri)
33
+ request.body = body.to_json
34
+ perform(request, uri)
35
+ end
36
+
37
+ def put(path, body: {})
38
+ uri = build_uri(path)
39
+ request = Net::HTTP::Put.new(uri)
40
+ request.body = body.to_json
41
+ perform(request, uri)
42
+ end
43
+
44
+ def delete(path)
45
+ uri = build_uri(path)
46
+ request = Net::HTTP::Delete.new(uri)
47
+ perform(request, uri)
48
+ end
49
+
50
+ private
51
+
52
+ def build_uri(path, params = {})
53
+ uri = URI.parse("#{@base_url}#{path}")
54
+ unless params.empty?
55
+ uri.query = URI.encode_www_form(params.reject { |_, v| v.nil? })
56
+ end
57
+ uri
58
+ end
59
+
60
+ def perform(request, uri)
61
+ request['Content-Type'] = 'application/json'
62
+ request['Accept'] = 'application/json'
63
+ request['Authorization'] = "Token token=\"#{@token}\""
64
+
65
+ http = Net::HTTP.new(uri.host, uri.port)
66
+ http.use_ssl = (uri.scheme == 'https')
67
+
68
+ response = http.request(request)
69
+
70
+ unless response.is_a?(Net::HTTPSuccess)
71
+ raise ApiError.new(response.code.to_i, response.body)
72
+ end
73
+
74
+ response.body
75
+ end
76
+
77
+ def warn_if_insecure
78
+ return if @uri.scheme == 'https'
79
+ host = @uri.host.to_s
80
+ return if host == 'localhost' || host == 'lvh.me' || host.start_with?('127.')
81
+ $stderr.puts 'Warning: connecting over HTTP to a non-local host. Consider using HTTPS.'
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,41 @@
1
+ require 'thor'
2
+ require 'json'
3
+ require_relative '../config'
4
+
5
+ module StakworkCli
6
+ module Commands
7
+ class Auth < Thor
8
+ desc 'login', 'Save credentials to ~/.stakwork/config.yml'
9
+ option :token, required: true, desc: 'API token'
10
+ option :host, default: Config::DEFAULT_HOST, desc: 'API host URL'
11
+ def login
12
+ Config.save(token: options[:token], host: options[:host])
13
+ puts "✅ Credentials saved to ~/.stakwork/config.yml"
14
+ puts " Host: #{options[:host]}"
15
+ puts " Token: #{mask_token(options[:token])}"
16
+ end
17
+
18
+ desc 'whoami', 'Show current configured credentials'
19
+ option :token, desc: 'Inline token override'
20
+ option :host, desc: 'Inline host override'
21
+ option :pretty, type: :boolean, default: false, desc: 'Pretty-print output'
22
+ def whoami
23
+ cfg = Config.load(token: options[:token], host: options[:host])
24
+ result = {
25
+ host: cfg[:host],
26
+ token: mask_token(cfg[:token].to_s)
27
+ }
28
+ output = options[:pretty] ? JSON.pretty_generate(result) : result.to_json
29
+ puts output
30
+ end
31
+
32
+ private
33
+
34
+ def mask_token(token)
35
+ return '(not set)' if token.nil? || token.empty?
36
+ return token if token.length <= 8
37
+ "#{token[0, 4]}...#{token[-4..]}"
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,37 @@
1
+ require 'thor'
2
+ require 'json'
3
+ require_relative '../config'
4
+ require_relative '../client'
5
+
6
+ module StakworkCli
7
+ module Commands
8
+ class Customers < Thor
9
+ class_option :token, desc: 'API token (overrides config/env)'
10
+ class_option :host, desc: 'API host (overrides config/env)'
11
+ class_option :pretty, type: :boolean, default: false, desc: 'Pretty-print JSON output'
12
+
13
+ desc 'create', 'Create a new customer'
14
+ option :name, required: true, desc: 'Customer name'
15
+ def create
16
+ body = { customer: { name: options[:name] } }
17
+ result = client.post('/api/v1/customers', body: body)
18
+ print_output(result)
19
+ end
20
+
21
+ private
22
+
23
+ def client
24
+ cfg = Config.load(token: options[:token], host: options[:host])
25
+ Client.new(token: cfg[:token], base_url: cfg[:host])
26
+ end
27
+
28
+ def print_output(raw)
29
+ if options[:pretty]
30
+ puts JSON.pretty_generate(JSON.parse(raw))
31
+ else
32
+ puts raw
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,76 @@
1
+ require 'thor'
2
+ require 'json'
3
+ require_relative '../config'
4
+ require_relative '../client'
5
+
6
+ module StakworkCli
7
+ module Commands
8
+ class Projects < Thor
9
+ class_option :token, desc: 'API token (overrides config/env)'
10
+ class_option :host, desc: 'API host (overrides config/env)'
11
+ class_option :pretty, type: :boolean, default: false, desc: 'Pretty-print JSON output'
12
+
13
+ desc 'status ID', 'Get the status of a project run'
14
+ def status(id)
15
+ result = client.get("/api/v1/projects/#{id}/status")
16
+ print_output(result)
17
+ end
18
+
19
+ desc 'steps ID', 'Get the steps of a project run'
20
+ option :page, type: :numeric, desc: 'Page number'
21
+ option :'step-id', type: :numeric, desc: 'Filter by step ID'
22
+ def steps(id)
23
+ params = {}
24
+ params[:page] = options[:page] if options[:page]
25
+ params[:step_id] = options[:'step-id'] if options[:'step-id']
26
+ result = client.get("/api/v1/projects/#{id}/steps", params: params)
27
+ print_output(result)
28
+ end
29
+
30
+ desc 'retry ID', 'Retry a failed project run'
31
+ def retry(id)
32
+ result = client.post("/api/v1/projects/#{id}/retry")
33
+ print_output(result)
34
+ end
35
+
36
+ desc 'stop ID', 'Stop a running project'
37
+ def stop(id)
38
+ result = client.post("/api/v1/projects/#{id}/stop")
39
+ print_output(result)
40
+ end
41
+
42
+ desc 'pause ID', 'Pause a running project'
43
+ option :reason, desc: 'Reason for pausing'
44
+ def pause(id)
45
+ body = {}
46
+ body[:reason] = options[:reason] if options[:reason]
47
+ result = client.post("/api/v1/projects/#{id}/pause", body: body)
48
+ print_output(result)
49
+ end
50
+
51
+ desc 'resume ID', 'Resume a paused project'
52
+ option :instructions, desc: 'Instructions for resuming'
53
+ def resume(id)
54
+ body = {}
55
+ body[:instructions] = options[:instructions] if options[:instructions]
56
+ result = client.post("/api/v1/projects/#{id}/resume", body: body)
57
+ print_output(result)
58
+ end
59
+
60
+ private
61
+
62
+ def client
63
+ cfg = Config.load(token: options[:token], host: options[:host])
64
+ Client.new(token: cfg[:token], base_url: cfg[:host])
65
+ end
66
+
67
+ def print_output(raw)
68
+ if options[:pretty]
69
+ puts JSON.pretty_generate(JSON.parse(raw))
70
+ else
71
+ puts raw
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,71 @@
1
+ require 'thor'
2
+ require 'json'
3
+ require_relative '../config'
4
+ require_relative '../client'
5
+
6
+ module StakworkCli
7
+ module Commands
8
+ class Prompts < Thor
9
+ class_option :token, desc: 'API token (overrides config/env)'
10
+ class_option :host, desc: 'API host (overrides config/env)'
11
+ class_option :pretty, type: :boolean, default: false, desc: 'Pretty-print JSON output'
12
+
13
+ desc 'list', 'List all prompts'
14
+ def list
15
+ result = client.get('/api/v1/prompts')
16
+ print_output(result)
17
+ end
18
+
19
+ desc 'show ID', 'Show a prompt by ID'
20
+ def show(id)
21
+ result = client.get("/api/v1/prompts/#{id}")
22
+ print_output(result)
23
+ end
24
+
25
+ desc 'create', 'Create a new prompt'
26
+ option :name, required: true, desc: 'Prompt name'
27
+ option :value, required: true, desc: 'Prompt value/body'
28
+ def create
29
+ body = { prompt: { name: options[:name], value: options[:value] } }
30
+ result = client.post('/api/v1/prompts', body: body)
31
+ print_output(result)
32
+ end
33
+
34
+ desc 'update ID', 'Update an existing prompt'
35
+ option :value, required: true, desc: 'New prompt value/body'
36
+ def update(id)
37
+ body = { prompt: { value: options[:value] } }
38
+ result = client.put("/api/v1/prompts/#{id}", body: body)
39
+ print_output(result)
40
+ end
41
+
42
+ desc 'delete ID', 'Delete a prompt'
43
+ def delete(id)
44
+ result = client.delete("/api/v1/prompts/#{id}")
45
+ print_output(result)
46
+ end
47
+
48
+ desc 'publish ID', 'Publish a specific version of a prompt'
49
+ option :version, required: true, desc: 'Version ID to publish'
50
+ def publish(id)
51
+ result = client.post("/api/v1/prompts/#{id}/versions/#{options[:version]}/publish")
52
+ print_output(result)
53
+ end
54
+
55
+ private
56
+
57
+ def client
58
+ cfg = Config.load(token: options[:token], host: options[:host])
59
+ Client.new(token: cfg[:token], base_url: cfg[:host])
60
+ end
61
+
62
+ def print_output(raw)
63
+ if options[:pretty]
64
+ puts JSON.pretty_generate(JSON.parse(raw))
65
+ else
66
+ puts raw
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,46 @@
1
+ require 'thor'
2
+ require 'json'
3
+ require_relative '../config'
4
+ require_relative '../client'
5
+
6
+ module StakworkCli
7
+ module Commands
8
+ class Skills < Thor
9
+ class_option :token, desc: 'API token (overrides config/env)'
10
+ class_option :host, desc: 'API host (overrides config/env)'
11
+ class_option :pretty, type: :boolean, default: false, desc: 'Pretty-print JSON output'
12
+
13
+ desc 'list', 'List available skills'
14
+ option :search, desc: 'Filter by search query'
15
+ option :page, type: :numeric, desc: 'Page number'
16
+ def list
17
+ params = {}
18
+ params[:search] = options[:search] if options[:search]
19
+ params[:page] = options[:page] if options[:page]
20
+ result = client.get('/api/v1/skills', params: params)
21
+ print_output(result)
22
+ end
23
+
24
+ desc 'examples ID', 'Show input/output examples for a skill'
25
+ def examples(id)
26
+ result = client.get("/api/v1/skills/#{id}/examples")
27
+ print_output(result)
28
+ end
29
+
30
+ private
31
+
32
+ def client
33
+ cfg = Config.load(token: options[:token], host: options[:host])
34
+ Client.new(token: cfg[:token], base_url: cfg[:host])
35
+ end
36
+
37
+ def print_output(raw)
38
+ if options[:pretty]
39
+ puts JSON.pretty_generate(JSON.parse(raw))
40
+ else
41
+ puts raw
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,104 @@
1
+ require 'thor'
2
+ require 'json'
3
+ require_relative '../config'
4
+ require_relative '../client'
5
+
6
+ module StakworkCli
7
+ module Commands
8
+ class Workflows < Thor
9
+ class_option :token, desc: 'API token (overrides config/env)'
10
+ class_option :host, desc: 'API host (overrides config/env)'
11
+ class_option :pretty, type: :boolean, default: false, desc: 'Pretty-print JSON output'
12
+
13
+ desc 'list', 'List workflows'
14
+ option :page, type: :numeric, default: 1, desc: 'Page number'
15
+ def list
16
+ result = client.get('/api/v1/workflows', params: { page: options[:page] })
17
+ print_output(result)
18
+ end
19
+
20
+ desc 'search QUERY', 'Search workflows by name'
21
+ def search(query)
22
+ result = client.get('/api/v1/workflows/search', params: { search: query })
23
+ print_output(result)
24
+ end
25
+
26
+ desc 'show ID', 'Show a workflow by ID'
27
+ def show(id)
28
+ result = client.get("/api/v1/workflows/#{id}")
29
+ print_output(result)
30
+ end
31
+
32
+ desc 'create', 'Create a new workflow'
33
+ option :workflow, required: true, desc: 'JSON workflow definition'
34
+ option :'child-workflows', desc: 'JSON array of child workflow definitions'
35
+ def create
36
+ begin
37
+ workflow = JSON.parse(options[:workflow])
38
+ rescue JSON::ParserError => e
39
+ $stderr.puts "Error: --workflow is not valid JSON: #{e.message}"
40
+ exit 1
41
+ end
42
+
43
+ body = { workflow: workflow }
44
+
45
+ if options[:'child-workflows']
46
+ begin
47
+ body[:child_workflows] = JSON.parse(options[:'child-workflows'])
48
+ rescue JSON::ParserError => e
49
+ $stderr.puts "Error: --child-workflows is not valid JSON: #{e.message}"
50
+ exit 1
51
+ end
52
+ end
53
+
54
+ result = client.post('/api/v1/workflows', body: body)
55
+ print_output(result)
56
+ end
57
+
58
+ desc 'publish ID', 'Publish a workflow'
59
+ def publish(id)
60
+ result = client.post("/api/v1/workflows/#{id}/publish")
61
+ print_output(result)
62
+ end
63
+
64
+ desc 'run ID', 'Trigger a workflow run'
65
+ option :name, required: true, desc: 'Name for this run'
66
+ option :params, default: '{}', desc: 'JSON workflow params'
67
+ def trigger(id)
68
+ begin
69
+ workflow_params = JSON.parse(options[:params])
70
+ rescue JSON::ParserError => e
71
+ $stderr.puts "Error: --params is not valid JSON: #{e.message}"
72
+ exit 1
73
+ end
74
+
75
+ body = {
76
+ project: {
77
+ workflow_id: id.to_i,
78
+ name: options[:name],
79
+ workflow_params: workflow_params
80
+ }
81
+ }
82
+ result = client.post('/api/v1/projects', body: body)
83
+ print_output(result)
84
+ end
85
+
86
+ map 'run' => :trigger
87
+
88
+ private
89
+
90
+ def client
91
+ cfg = Config.load(token: options[:token], host: options[:host])
92
+ Client.new(token: cfg[:token], base_url: cfg[:host])
93
+ end
94
+
95
+ def print_output(raw)
96
+ if options[:pretty]
97
+ puts JSON.pretty_generate(JSON.parse(raw))
98
+ else
99
+ puts raw
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,43 @@
1
+ require 'yaml'
2
+ require 'fileutils'
3
+
4
+ module StakworkCli
5
+ module Config
6
+ CONFIG_DIR = File.expand_path('~/.stakwork')
7
+ CONFIG_PATH = File.join(CONFIG_DIR, 'config.yml')
8
+ DEFAULT_HOST = 'https://api.stakwork.com'
9
+
10
+ # Resolves token and host in precedence order:
11
+ # 1. Inline argument
12
+ # 2. Environment variable
13
+ # 3. ~/.stakwork/config.yml
14
+ # 4. Default host
15
+ def self.load(token: nil, host: nil)
16
+ file_config = load_file
17
+
18
+ resolved_token = token ||
19
+ ENV['STAKWORK_TOKEN'] ||
20
+ file_config['token']
21
+
22
+ resolved_host = host ||
23
+ ENV['STAKWORK_HOST'] ||
24
+ file_config['host'] ||
25
+ DEFAULT_HOST
26
+
27
+ { token: resolved_token, host: resolved_host }
28
+ end
29
+
30
+ def self.save(token:, host:)
31
+ FileUtils.mkdir_p(CONFIG_DIR)
32
+ File.write(CONFIG_PATH, { 'token' => token, 'host' => host }.to_yaml)
33
+ File.chmod(0o600, CONFIG_PATH)
34
+ end
35
+
36
+ def self.load_file
37
+ return {} unless File.exist?(CONFIG_PATH)
38
+ YAML.safe_load(File.read(CONFIG_PATH)) || {}
39
+ rescue Psych::SyntaxError
40
+ {}
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,3 @@
1
+ module StakworkCli
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,48 @@
1
+ require 'thor'
2
+ require_relative 'stakwork_cli/version'
3
+ require_relative 'stakwork_cli/config'
4
+ require_relative 'stakwork_cli/client'
5
+ require_relative 'stakwork_cli/commands/auth'
6
+ require_relative 'stakwork_cli/commands/workflows'
7
+ require_relative 'stakwork_cli/commands/projects'
8
+ require_relative 'stakwork_cli/commands/prompts'
9
+ require_relative 'stakwork_cli/commands/skills'
10
+ require_relative 'stakwork_cli/commands/customers'
11
+
12
+ module StakworkCli
13
+ class CLI < Thor
14
+ desc 'login', 'Save API credentials to ~/.stakwork/config.yml'
15
+ option :token, required: true, desc: 'API token'
16
+ option :host, default: Config::DEFAULT_HOST, desc: 'API host URL'
17
+ def login
18
+ Commands::Auth.new.invoke(:login, [], options.to_h.transform_keys(&:to_sym))
19
+ end
20
+
21
+ desc 'whoami', 'Show current configured credentials'
22
+ option :token, desc: 'Inline token override'
23
+ option :host, desc: 'Inline host override'
24
+ option :pretty, type: :boolean, default: false, desc: 'Pretty-print output'
25
+ def whoami
26
+ Commands::Auth.new.invoke(:whoami, [], options.to_h.transform_keys(&:to_sym))
27
+ end
28
+
29
+ desc 'workflows SUBCOMMAND', 'Manage workflows'
30
+ subcommand 'workflows', Commands::Workflows
31
+
32
+ desc 'projects SUBCOMMAND', 'Manage project runs'
33
+ subcommand 'projects', Commands::Projects
34
+
35
+ desc 'prompts SUBCOMMAND', 'Manage prompts'
36
+ subcommand 'prompts', Commands::Prompts
37
+
38
+ desc 'skills SUBCOMMAND', 'Browse skills'
39
+ subcommand 'skills', Commands::Skills
40
+
41
+ desc 'customers SUBCOMMAND', 'Manage customers'
42
+ subcommand 'customers', Commands::Customers
43
+
44
+ def self.exit_on_failure?
45
+ true
46
+ end
47
+ end
48
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stakwork-cli
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Stakwork
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-03-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.4'
27
+ description: A command-line interface for interacting with the Stakwork REST API.
28
+ Manage workflows, projects, prompts, and skills from the terminal.
29
+ email:
30
+ executables:
31
+ - stakwork
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - bin/stakwork
36
+ - lib/stakwork_cli.rb
37
+ - lib/stakwork_cli/client.rb
38
+ - lib/stakwork_cli/commands/auth.rb
39
+ - lib/stakwork_cli/commands/customers.rb
40
+ - lib/stakwork_cli/commands/projects.rb
41
+ - lib/stakwork_cli/commands/prompts.rb
42
+ - lib/stakwork_cli/commands/skills.rb
43
+ - lib/stakwork_cli/commands/workflows.rb
44
+ - lib/stakwork_cli/config.rb
45
+ - lib/stakwork_cli/version.rb
46
+ homepage:
47
+ licenses: []
48
+ metadata: {}
49
+ post_install_message:
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '2.7'
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ requirements: []
64
+ rubygems_version: 3.2.33
65
+ signing_key:
66
+ specification_version: 4
67
+ summary: CLI for the Stakwork API
68
+ test_files: []