├── spec ├── spec_helper.cr └── caido-crystal_spec.cr ├── src ├── caido-crystal.cr └── client │ ├── graphql.cr │ ├── queries.cr │ └── mutations.cr ├── .gitignore ├── .editorconfig ├── shard.yml ├── example ├── get_sitemap.cr └── enhanced_examples.cr ├── .github └── workflows │ └── crystal.yml ├── LICENSE ├── README.md ├── COVERAGE.md ├── CHANGES.md └── ENDPOINTS.md /spec/spec_helper.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "../src/caido-crystal" 3 | -------------------------------------------------------------------------------- /spec/caido-crystal_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | describe Caido::Crystal do 4 | it "works" do 5 | true.should eq(true) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /src/caido-crystal.cr: -------------------------------------------------------------------------------- 1 | require "./client/graphql" 2 | require "./client/queries" 3 | require "./client/mutations" 4 | 5 | module Caido::Crystal 6 | VERSION = "0.1.0" 7 | end 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /docs/ 2 | /lib/ 3 | /bin/ 4 | /.shards/ 5 | *.dwarf 6 | 7 | # Libraries don't need dependency lock 8 | # Dependencies will be locked in applications that use them 9 | /shard.lock 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.cr] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 2 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /shard.yml: -------------------------------------------------------------------------------- 1 | name: caido-crystal 2 | version: 0.1.0 3 | 4 | authors: 5 | - hahwul 6 | 7 | crystal: 1.8.1 8 | 9 | dependencies: 10 | crystal-gql: 11 | github: itsezc/crystal-gql 12 | 13 | license: MIT -------------------------------------------------------------------------------- /example/get_sitemap.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "../src/client/graphql" 3 | 4 | client = CaidoClient.new "http://localhost:8080/graphql" 5 | # send GQL Query to server and get response. 6 | response = client.query "query GetProject{ 7 | requests { 8 | nodes { 9 | method 10 | host 11 | path 12 | query 13 | } 14 | } 15 | }" 16 | 17 | puts response 18 | -------------------------------------------------------------------------------- /.github/workflows/crystal.yml: -------------------------------------------------------------------------------- 1 | name: Crystal CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | container: 15 | image: crystallang/crystal 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Install dependencies 20 | run: shards install 21 | - name: Run tests 22 | run: crystal spec 23 | -------------------------------------------------------------------------------- /src/client/graphql.cr: -------------------------------------------------------------------------------- 1 | require "crystal-gql" 2 | 3 | class CaidoClient 4 | @instance : GraphQLClient 5 | @headers : Hash(String, String) 6 | 7 | # Initialize 8 | def initialize(@endpoint : String) 9 | @instance = GraphQLClient.new endpoint 10 | @headers = Hash(String, String).new 11 | 12 | if ENV.has_key? "CAIDO_AUTH_TOKEN" 13 | @instance.headers["Authorization"] = "Bearer #{ENV["CAIDO_AUTH_TOKEN"]}" 14 | @headers["Authorization"] = "Bearer #{ENV["CAIDO_AUTH_TOKEN"]}" 15 | end 16 | end 17 | 18 | # Initialize with custom headers 19 | def initialize(@endpoint : String, @headers : Hash(String, String)) 20 | @instance = GraphQLClient.new endpoint, @headers 21 | end 22 | 23 | def query(query : String) 24 | @instance.query query 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 HAHWUL 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/enhanced_examples.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "../src/client/graphql" 3 | require "../src/client/queries" 4 | require "../src/client/mutations" 5 | 6 | # Initialize the Caido client 7 | client = CaidoClient.new "http://localhost:8080/graphql" 8 | 9 | puts "=== Caido Crystal Library - Enhanced Examples ===" 10 | puts 11 | 12 | # Example 1: Get all requests using the helper 13 | puts "1. Getting requests using helper method:" 14 | query = CaidoQueries::Requests.all(first: 10) 15 | response = client.query query 16 | puts response 17 | puts 18 | 19 | # Example 2: Get sitemap root entries 20 | puts "2. Getting sitemap root entries:" 21 | query = CaidoQueries::Sitemap.root_entries 22 | response = client.query query 23 | puts response 24 | puts 25 | 26 | # Example 3: Get intercept status 27 | puts "3. Getting intercept status:" 28 | query = CaidoQueries::Intercept.status 29 | response = client.query query 30 | puts response 31 | puts 32 | 33 | # Example 4: Get current project information 34 | puts "4. Getting current project:" 35 | query = CaidoQueries::Projects.current 36 | response = client.query query 37 | puts response 38 | puts 39 | 40 | # Example 5: Get all scopes 41 | puts "5. Getting all scopes:" 42 | query = CaidoQueries::Scopes.all 43 | response = client.query query 44 | puts response 45 | puts 46 | 47 | # Example 6: Get findings 48 | puts "6. Getting findings:" 49 | query = CaidoQueries::Findings.all(first: 5) 50 | response = client.query query 51 | puts response 52 | puts 53 | 54 | # Example 7: Get viewer (current user) information 55 | puts "7. Getting viewer information:" 56 | query = CaidoQueries::Viewer.info 57 | response = client.query query 58 | puts response 59 | puts 60 | 61 | # Example 8: Get runtime information 62 | puts "8. Getting runtime information:" 63 | query = CaidoQueries::Runtime.info 64 | response = client.query query 65 | puts response 66 | puts 67 | 68 | # Example 9: Mutation - Pause intercept (commented out to avoid side effects) 69 | # puts "9. Pausing intercept:" 70 | # mutation = CaidoMutations::Intercept.pause 71 | # response = client.query mutation 72 | # puts response 73 | # puts 74 | 75 | # Example 10: Get workflows 76 | puts "10. Getting workflows:" 77 | query = CaidoQueries::Workflows.all 78 | response = client.query query 79 | puts response 80 | puts 81 | 82 | puts "=== Examples Complete ===" 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # caido.cr 2 | 3 | Crystal language client library for [Caido](https://caido.io/), a lightweight web security auditing toolkit. 4 | 5 | ## Features 6 | 7 | - **Simple GraphQL Client**: Easy-to-use wrapper for Caido's GraphQL API 8 | - **Pre-built Query Helpers**: Ready-to-use templates for common operations 9 | - **Comprehensive Mutation Support**: Helper methods for all major Caido operations 10 | - **Full API Coverage**: Supports requests, sitemap, intercept, findings, workflows, and more 11 | 12 | ## Installation 13 | 14 | Add this to your application's `shard.yml`: 15 | 16 | ```yaml 17 | dependencies: 18 | caido-crystal: 19 | github: hahwul/caido-crystal 20 | ``` 21 | 22 | Run `shards install` to install dependencies. 23 | 24 | ## Quick Start 25 | 26 | ### Basic Usage 27 | 28 | ```crystal 29 | require "caido-crystal" 30 | 31 | # Initialize the client 32 | client = CaidoClient.new "http://localhost:8080/graphql" 33 | 34 | # Get all requests 35 | query = CaidoQueries::Requests.all(first: 50) 36 | response = client.query query 37 | puts response 38 | 39 | # Get sitemap root entries 40 | query = CaidoQueries::Sitemap.root_entries 41 | response = client.query query 42 | puts response 43 | ``` 44 | 45 | ### Authentication 46 | 47 | Set the authentication token via environment variable: 48 | 49 | ```bash 50 | export CAIDO_AUTH_TOKEN="your-token-here" 51 | ``` 52 | 53 | Or provide custom headers: 54 | 55 | ```crystal 56 | headers = {"Authorization" => "Bearer your-token-here"} 57 | client = CaidoClient.new "http://localhost:8080/graphql", headers 58 | ``` 59 | 60 | ### Examples 61 | 62 | See the `example/` directory for usage examples: 63 | - `get_sitemap.cr` - Basic example 64 | - `enhanced_examples.cr` - Comprehensive examples using helper methods 65 | 66 | ## Available Endpoints 67 | 68 | The library includes helper modules for common operations: 69 | 70 | ### Query Modules (CaidoQueries) 71 | - **Requests**: HTTP requests and responses 72 | - **Sitemap**: Target site structure 73 | - **Intercept**: Proxy intercept functionality 74 | - **Scopes**: Target scope management 75 | - **Findings**: Security findings 76 | - **Projects**: Project management 77 | - **Workflows**: Automation workflows 78 | - **Replay**: Request replay sessions 79 | - **Automate**: Automated attacks 80 | - **Viewer**: Current user info 81 | - **Runtime**: Caido runtime info 82 | - And many more... 83 | 84 | ### Mutation Modules (CaidoMutations) 85 | - **Requests**: Update metadata, render requests 86 | - **Sitemap**: Create, delete entries 87 | - **Intercept**: Pause, resume, forward/drop messages 88 | - **Scopes**: Create, update, delete scopes 89 | - **Findings**: Create, update, delete findings 90 | - **Workflows**: Create, update, run workflows 91 | - **Replay**: Manage replay sessions 92 | - **Automate**: Manage automate sessions and tasks 93 | - **Tamper**: Manage tampering rules 94 | - And many more... 95 | 96 | For complete documentation of all available endpoints, see [ENDPOINTS.md](ENDPOINTS.md). 97 | 98 | ## Documentation 99 | 100 | - [Complete Endpoint Documentation](ENDPOINTS.md) - Detailed guide for all queries and mutations 101 | - [Caido Official Documentation](https://docs.caido.io/) 102 | - [Caido GraphQL Schema](https://github.com/caido/graphql-explorer/blob/main/src/assets/schema.graphql) 103 | 104 | ## Development 105 | 106 | Build the project: 107 | ```bash 108 | shards build 109 | ``` 110 | 111 | Run tests: 112 | ```bash 113 | crystal spec 114 | ``` 115 | 116 | ## Contributing 117 | 118 | 1. Fork it () 119 | 2. Create your feature branch (`git checkout -b my-new-feature`) 120 | 3. Commit your changes (`git commit -am 'Add some feature'`) 121 | 4. Push to the branch (`git push origin my-new-feature`) 122 | 5. Create a new Pull Request 123 | 124 | ## Contributors 125 | 126 | - [hahwul](https://github.com/hahwul) - creator and maintainer 127 | 128 | ## License 129 | 130 | MIT License - see [LICENSE](LICENSE) file for details -------------------------------------------------------------------------------- /COVERAGE.md: -------------------------------------------------------------------------------- 1 | # GraphQL Endpoint Coverage 2 | 3 | This document lists the GraphQL endpoints from the [Caido GraphQL schema](https://github.com/caido/graphql-explorer/blob/main/src/assets/schema.graphql) and their implementation status in caido-crystal. 4 | 5 | ## Query Endpoints (QueryRoot) 6 | 7 | | Endpoint | Module | Method | Status | 8 | |----------|--------|--------|--------| 9 | | assistantModels | CaidoQueries::Assistant | models | ✅ | 10 | | assistantSession | CaidoQueries::Assistant | - | ⚠️ Use by_id pattern if needed | 11 | | assistantSessions | CaidoQueries::Assistant | sessions | ✅ | 12 | | authenticationState | - | - | ➕ Can add if needed | 13 | | automateEntry | CaidoQueries::Automate | - | ⚠️ Use by_id pattern if needed | 14 | | automateSession | CaidoQueries::Automate | session_by_id | ✅ | 15 | | automateSessions | CaidoQueries::Automate | sessions | ✅ | 16 | | automateTasks | CaidoQueries::Automate | tasks | ✅ | 17 | | backup | - | - | ➕ Can add if needed | 18 | | backupTasks | - | - | ➕ Can add if needed | 19 | | backups | - | - | ➕ Can add if needed | 20 | | browser | - | - | ➕ Can add if needed | 21 | | currentProject | CaidoQueries::Projects | current | ✅ | 22 | | dataExport | - | - | ➕ Can add if needed | 23 | | dataExports | - | - | ➕ Can add if needed | 24 | | dnsRewrites | CaidoQueries::DNS | rewrites | ✅ | 25 | | dnsUpstreams | CaidoQueries::DNS | upstreams | ✅ | 26 | | environment | CaidoQueries::Environments | - | ⚠️ Use by_id pattern if needed | 27 | | environmentContext | CaidoQueries::Environments | context | ✅ | 28 | | environments | CaidoQueries::Environments | all | ✅ | 29 | | filterPreset | - | - | ➕ Can add if needed | 30 | | filterPresets | - | - | ➕ Can add if needed | 31 | | finding | CaidoQueries::Findings | by_id | ✅ | 32 | | findingReporters | CaidoQueries::Findings | reporters | ✅ | 33 | | findings | CaidoQueries::Findings | all | ✅ | 34 | | findingsByOffset | - | - | ⚠️ Similar to findings | 35 | | globalConfig | - | - | ➕ Can add if needed | 36 | | hostedFiles | - | - | ➕ Can add if needed | 37 | | instanceSettings | CaidoQueries::InstanceSettings | get | ✅ | 38 | | interceptEntries | CaidoQueries::Intercept | entries | ✅ | 39 | | interceptEntriesByOffset | - | - | ⚠️ Similar to entries | 40 | | interceptEntry | - | - | ⚠️ Use by_id pattern if needed | 41 | | interceptEntryOffset | - | - | ➕ Can add if needed | 42 | | interceptMessages | - | - | ➕ Can add if needed | 43 | | interceptOptions | CaidoQueries::Intercept | options | ✅ | 44 | | interceptStatus | CaidoQueries::Intercept | status | ✅ | 45 | | pluginPackages | CaidoQueries::Plugins | packages | ✅ | 46 | | projects | CaidoQueries::Projects | all | ✅ | 47 | | replayEntry | - | - | ⚠️ Use by_id pattern if needed | 48 | | replaySession | CaidoQueries::Replay | session_by_id | ✅ | 49 | | replaySessionCollections | CaidoQueries::Replay | collections | ✅ | 50 | | replaySessions | CaidoQueries::Replay | sessions | ✅ | 51 | | request | CaidoQueries::Requests | by_id | ✅ | 52 | | requestOffset | - | - | ➕ Can add if needed | 53 | | requests | CaidoQueries::Requests | all | ✅ | 54 | | requestsByOffset | CaidoQueries::Requests | by_offset | ✅ | 55 | | response | - | - | ⚠️ Included in request query | 56 | | restoreBackupTasks | - | - | ➕ Can add if needed | 57 | | runtime | CaidoQueries::Runtime | info | ✅ | 58 | | scope | CaidoQueries::Scopes | by_id | ✅ | 59 | | scopes | CaidoQueries::Scopes | all | ✅ | 60 | | sitemapDescendantEntries | CaidoQueries::Sitemap | descendant_entries | ✅ | 61 | | sitemapEntry | CaidoQueries::Sitemap | by_id | ✅ | 62 | | sitemapRootEntries | CaidoQueries::Sitemap | root_entries | ✅ | 63 | | store | - | - | ➕ Can add if needed | 64 | | stream | - | - | ➕ Can add if needed | 65 | | streamWsMessage | - | - | ➕ Can add if needed | 66 | | streamWsMessageEdit | - | - | ➕ Can add if needed | 67 | | streamWsMessages | - | - | ➕ Can add if needed | 68 | | streamWsMessagesByOffset | - | - | ➕ Can add if needed | 69 | | streams | - | - | ➕ Can add if needed | 70 | | streamsByOffset | - | - | ➕ Can add if needed | 71 | | tamperRule | CaidoQueries::Tamper | rule_by_id | ✅ | 72 | | tamperRuleCollection | - | - | ⚠️ Included in rules query | 73 | | tamperRuleCollections | CaidoQueries::Tamper | rules | ✅ | 74 | | tasks | - | - | ➕ Can add if needed | 75 | | upstreamProxiesHttp | CaidoQueries::UpstreamProxies | http | ✅ | 76 | | upstreamProxiesSocks | CaidoQueries::UpstreamProxies | socks | ✅ | 77 | | viewer | CaidoQueries::Viewer | info | ✅ | 78 | | workflow | CaidoQueries::Workflows | by_id | ✅ | 79 | | workflowNodeDefinitions | CaidoQueries::Workflows | node_definitions | ✅ | 80 | | workflows | CaidoQueries::Workflows | all | ✅ | 81 | 82 | ## Mutation Endpoints (MutationRoot) 83 | 84 | Major mutation categories covered: 85 | 86 | | Category | Methods | Status | 87 | |----------|---------|--------| 88 | | Requests | update_metadata, render | ✅ | 89 | | Sitemap | create_entries, delete_entries, clear_all | ✅ | 90 | | Intercept | pause, resume, forward_message, drop_message, set_options, delete_entries | ✅ | 91 | | Scopes | create, update, delete, rename | ✅ | 92 | | Findings | create, update, delete, hide, export | ✅ | 93 | | Projects | select, create, delete, rename | ✅ | 94 | | Workflows | create, update, delete, rename, toggle, run_active | ✅ | 95 | | Replay | create_session, create_collection, delete_sessions, delete_collection, rename_session, rename_collection, move_session, start_task | ✅ | 96 | | Automate | create_session, delete_session, rename_session, duplicate_session, start_task, pause_task, resume_task, cancel_task, delete_entries | ✅ | 97 | | Tamper | create_rule, create_collection, update_rule, delete_rule, delete_collection, toggle_rule, rename_rule, rename_collection, move_rule | ✅ | 98 | | DNS | create_rewrite, update_rewrite, delete_rewrite, toggle_rewrite, create_upstream, update_upstream, delete_upstream | ✅ | 99 | | UpstreamProxies | create_http, create_socks, delete_http, delete_socks, toggle_http, toggle_socks | ✅ | 100 | | Assistant | create_session, delete_session, rename_session, send_message | ✅ | 101 | | Authentication | start_flow, login_guest, logout | ✅ | 102 | | Tasks | cancel | ✅ | 103 | 104 | Additional mutations available in the schema (100+ total) can be added as needed using the same pattern. 105 | 106 | ## Coverage Summary 107 | 108 | - **Query Endpoints**: ~40 high-priority endpoints implemented ✅ 109 | - **Mutation Endpoints**: ~70 common mutations implemented ✅ 110 | - **Total Coverage**: Major functionality covered for all key features 111 | 112 | ## Legend 113 | 114 | - ✅ = Implemented 115 | - ⚠️ = Partially implemented or alternative available 116 | - ➕ = Can be added if needed (low priority) 117 | 118 | ## Notes 119 | 120 | 1. The implementation focuses on the most commonly used endpoints 121 | 2. All major Caido features are covered (requests, sitemap, intercept, findings, workflows, etc.) 122 | 3. Additional endpoints can be easily added following the same pattern 123 | 4. Users can still use raw GraphQL queries for any endpoint not covered by helpers 124 | 5. Some endpoints are intentionally grouped (e.g., tamperRuleCollections includes rules) 125 | 126 | ## Adding New Endpoints 127 | 128 | To add a new endpoint helper: 129 | 130 | 1. **For queries**: Add a new method in the appropriate module under `CaidoQueries` 131 | 2. **For mutations**: Add a new method in the appropriate module under `CaidoMutations` 132 | 3. Follow the existing pattern of using heredoc strings with parameterized values 133 | 4. Update this coverage document 134 | 5. Add examples to ENDPOINTS.md 135 | 136 | Example: 137 | ```crystal 138 | module CaidoQueries 139 | module MyFeature 140 | def self.my_method(param : String) 141 | %Q( 142 | query MyQuery { 143 | myEndpoint(id: "#{param}") { 144 | id 145 | name 146 | } 147 | } 148 | ) 149 | end 150 | end 151 | end 152 | ``` 153 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # GraphQL Endpoints Improvement - Change Summary 2 | 3 | ## Problem Statement 4 | 5 | The issue requested improving GraphQL endpoints by comparing with the reference schema from: 6 | https://github.com/caido/graphql-explorer/blob/main/src/assets/schema.graphql 7 | 8 | ## Before 9 | 10 | The repository had: 11 | - Basic GraphQL client wrapper (`CaidoClient`) 12 | - Single example showing raw GraphQL query usage 13 | - No helper methods for common operations 14 | - Users had to manually write GraphQL queries for everything 15 | 16 | **Example of old usage:** 17 | ```crystal 18 | client = CaidoClient.new "http://localhost:8080/graphql" 19 | response = client.query "query GetProject{ 20 | requests { 21 | nodes { 22 | method 23 | host 24 | path 25 | query 26 | } 27 | } 28 | }" 29 | ``` 30 | 31 | ## After 32 | 33 | The repository now has: 34 | - **16 query helper modules** covering 40+ endpoints 35 | - **13 mutation helper modules** covering 70+ operations 36 | - Comprehensive documentation (ENDPOINTS.md, COVERAGE.md) 37 | - Enhanced examples demonstrating helper methods 38 | - Updated README with quick start guide 39 | 40 | **Example of new usage:** 41 | ```crystal 42 | require "caido-crystal" 43 | 44 | client = CaidoClient.new "http://localhost:8080/graphql" 45 | 46 | # Using helper methods - much simpler! 47 | query = CaidoQueries::Requests.all(first: 50, filter: "host:example.com") 48 | response = client.query query 49 | 50 | query = CaidoQueries::Sitemap.root_entries 51 | response = client.query query 52 | 53 | mutation = CaidoMutations::Findings.create( 54 | request_id: "req-123", 55 | title: "SQL Injection", 56 | description: "Found in login form" 57 | ) 58 | response = client.query mutation 59 | ``` 60 | 61 | ## What Was Added 62 | 63 | ### 1. Query Helper Modules (src/client/queries.cr) 64 | 65 | **CaidoQueries** module with 16 sub-modules: 66 | 67 | 1. **Requests** - HTTP requests/responses management 68 | - `all(first, filter)` - Get paginated requests 69 | - `by_id(id)` - Get single request 70 | - `by_offset(offset, limit)` - Alternative pagination 71 | 72 | 2. **Sitemap** - Site structure exploration 73 | - `root_entries(scope_id)` - Get root sitemap entries 74 | - `descendant_entries(parent_id, depth)` - Get descendants 75 | - `by_id(id)` - Get single entry 76 | 77 | 3. **Intercept** - Proxy intercept functionality 78 | - `entries(first, filter)` - Get intercept queue 79 | - `status()` - Get intercept status 80 | - `options()` - Get intercept configuration 81 | 82 | 4. **Scopes** - Target scope management 83 | - `all()` - Get all scopes 84 | - `by_id(id)` - Get single scope 85 | 86 | 5. **Findings** - Security findings management 87 | - `all(first)` - Get all findings 88 | - `by_id(id)` - Get single finding 89 | - `reporters()` - Get available reporters 90 | 91 | 6. **Projects** - Project management 92 | - `current()` - Get current project 93 | - `all()` - Get all projects (cloud) 94 | 95 | 7. **Workflows** - Automation workflows 96 | - `all()` - Get all workflows 97 | - `by_id(id)` - Get single workflow 98 | - `node_definitions()` - Get available nodes 99 | 100 | 8. **Replay** - Request replay sessions 101 | - `sessions(first)` - Get replay sessions 102 | - `session_by_id(id)` - Get single session 103 | - `collections(first)` - Get collections 104 | 105 | 9. **Automate** - Automated attack sessions 106 | - `sessions(first)` - Get automate sessions 107 | - `session_by_id(id)` - Get single session 108 | - `tasks(first)` - Get tasks 109 | 110 | 10. **Viewer** - Current user information 111 | - `info()` - Get user details 112 | 113 | 11. **Runtime** - Caido runtime information 114 | - `info()` - Get runtime details 115 | 116 | 12. **InstanceSettings** - Instance configuration 117 | - `get()` - Get settings 118 | 119 | 13. **DNS** - DNS configuration 120 | - `rewrites()` - Get DNS rewrites 121 | - `upstreams()` - Get DNS upstreams 122 | 123 | 14. **UpstreamProxies** - Proxy chaining 124 | - `http()` - Get HTTP proxies 125 | - `socks()` - Get SOCKS proxies 126 | 127 | 15. **Tamper** - Request/response tampering 128 | - `rules()` - Get all rules 129 | - `rule_by_id(id)` - Get single rule 130 | 131 | 16. **Assistant** - AI assistant (cloud) 132 | - `sessions()` - Get sessions 133 | - `models()` - Get available models 134 | 135 | 17. **Environments** - Environment variables (cloud) 136 | - `all()` - Get all environments 137 | - `context()` - Get current context 138 | 139 | 18. **Plugins** - Plugin management 140 | - `packages()` - Get installed plugins 141 | 142 | ### 2. Mutation Helper Modules (src/client/mutations.cr) 143 | 144 | **CaidoMutations** module with 13 sub-modules: 145 | 146 | 1. **Requests** - Request operations 147 | - `update_metadata(id, color, label)` 148 | - `render(id)` 149 | 150 | 2. **Sitemap** - Sitemap operations 151 | - `create_entries(request_id)` 152 | - `delete_entries(ids)` 153 | - `clear_all()` 154 | 155 | 3. **Intercept** - Intercept control 156 | - `pause()`, `resume()` 157 | - `forward_message(id, request, response)` 158 | - `drop_message(id)` 159 | - `set_options(...)` 160 | - `delete_entries(filter)` 161 | 162 | 4. **Scopes** - Scope management 163 | - `create(name, allowlist, denylist)` 164 | - `update(id, name, allowlist, denylist)` 165 | - `delete(id)` 166 | - `rename(id, name)` 167 | 168 | 5. **Findings** - Finding management 169 | - `create(request_id, title, description)` 170 | - `update(id, title, description)` 171 | - `delete(ids)` 172 | - `hide(ids)` 173 | - `export(ids)` 174 | 175 | 6. **Projects** - Project operations 176 | - `select(id)` 177 | - `create(name, path)` 178 | - `delete(id)` 179 | - `rename(id, name)` 180 | 181 | 7. **Workflows** - Workflow operations 182 | - `create(name, kind, definition)` 183 | - `update(id, name, definition)` 184 | - `delete(id)` 185 | - `rename(id, name)` 186 | - `toggle(id, enabled)` 187 | - `run_active(id, request_id)` 188 | 189 | 8. **Replay** - Replay operations 190 | - `create_session(name, source, collection_id)` 191 | - `create_collection(name)` 192 | - `delete_sessions(ids)` 193 | - `delete_collection(id)` 194 | - `rename_session(id, name)` 195 | - `rename_collection(id, name)` 196 | - `move_session(id, collection_id)` 197 | - `start_task(session_id)` 198 | 199 | 9. **Automate** - Automate operations 200 | - `create_session(name, host, port, is_tls)` 201 | - `delete_session(id)` 202 | - `rename_session(id, name)` 203 | - `duplicate_session(id)` 204 | - `start_task(session_id)` 205 | - `pause_task(id)`, `resume_task(id)`, `cancel_task(id)` 206 | - `delete_entries(ids)` 207 | 208 | 10. **Tamper** - Tamper rule operations 209 | - `create_rule(name, condition, strategy, collection_id)` 210 | - `create_collection(name)` 211 | - `update_rule(id, name, condition, strategy)` 212 | - `delete_rule(id)`, `delete_collection(id)` 213 | - `toggle_rule(id, enabled)` 214 | - `rename_rule(id, name)`, `rename_collection(id, name)` 215 | - `move_rule(id, collection_id)` 216 | 217 | 11. **DNS** - DNS operations 218 | - `create_rewrite(name, strategy, source, destination)` 219 | - `update_rewrite(id, ...)` 220 | - `delete_rewrite(id)` 221 | - `toggle_rewrite(id, enabled)` 222 | - `create_upstream(name, kind, address)` 223 | - `update_upstream(id, ...)` 224 | - `delete_upstream(id)` 225 | 226 | 12. **UpstreamProxies** - Proxy operations 227 | - `create_http(kind, address, allowlist, denylist)` 228 | - `create_socks(kind, address, allowlist, denylist)` 229 | - `delete_http(id)`, `delete_socks(id)` 230 | - `toggle_http(id, enabled)`, `toggle_socks(id, enabled)` 231 | 232 | 13. **Assistant** - AI operations (cloud) 233 | - `create_session(model_id, name)` 234 | - `delete_session(id)` 235 | - `rename_session(id, name)` 236 | - `send_message(session_id, message)` 237 | 238 | 14. **Authentication** - Auth operations (cloud) 239 | - `start_flow()` 240 | - `login_guest()` 241 | - `logout()` 242 | 243 | 15. **Tasks** - Task management 244 | - `cancel(id)` 245 | 246 | ### 3. Documentation Files 247 | 248 | **ENDPOINTS.md** (13KB) 249 | - Complete usage guide for all helpers 250 | - Code examples for each module 251 | - Organized by feature area 252 | - Authentication and error handling info 253 | 254 | **COVERAGE.md** (7KB) 255 | - Detailed endpoint coverage matrix 256 | - Maps reference schema to implemented helpers 257 | - Guidelines for adding new endpoints 258 | - Status indicators for each endpoint 259 | 260 | **README.md** (updated) 261 | - Feature overview 262 | - Quick start guide 263 | - Installation instructions 264 | - Links to detailed documentation 265 | 266 | ### 4. Examples 267 | 268 | **example/enhanced_examples.cr** 269 | - Demonstrates query helpers 270 | - Shows common use cases 271 | - Real-world examples 272 | 273 | ## Impact 274 | 275 | ### Before (Line Count) 276 | - Total: ~120 lines of actual code 277 | - Just basic GraphQL client wrapper 278 | - One simple example 279 | 280 | ### After (Line Count) 281 | - Total: ~3,000+ lines added 282 | - 700+ lines of query helpers 283 | - 1,000+ lines of mutation helpers 284 | - 13KB of endpoint documentation 285 | - 7KB of coverage tracking 286 | - Enhanced examples 287 | 288 | ### Benefits 289 | 290 | 1. **Ease of Use**: Developers don't need to write raw GraphQL queries 291 | 2. **Type Safety**: Method parameters provide structure 292 | 3. **Discoverability**: Easy to explore available operations 293 | 4. **Documentation**: Comprehensive guides with examples 294 | 5. **Maintainability**: Centralized query/mutation templates 295 | 6. **Coverage**: All major Caido features covered 296 | 7. **Extensibility**: Easy pattern to add more endpoints 297 | 298 | ## Comparison with Reference Schema 299 | 300 | **Reference Schema**: https://github.com/caido/graphql-explorer/blob/main/src/assets/schema.graphql 301 | - 4,716 lines 302 | - 76 QueryRoot fields 303 | - 144 MutationRoot fields 304 | - 58 SubscriptionRoot fields 305 | 306 | **Our Implementation**: 307 | - ✅ Covers 40+ high-priority query endpoints 308 | - ✅ Covers 70+ common mutation operations 309 | - ✅ All major features implemented 310 | - ✅ Users can still use raw GraphQL for anything not covered 311 | - ⚠️ Subscriptions not yet implemented (can be added if needed) 312 | 313 | ## Missing Items That Were Added 314 | 315 | Comparing with the original repository, we added helpers for ALL major Caido features: 316 | 317 | **Queries that were missing (all added):** 318 | - ✅ Requests (all, by_id, by_offset) 319 | - ✅ Sitemap (root, descendants, by_id) 320 | - ✅ Intercept (entries, status, options) 321 | - ✅ Scopes (all, by_id) 322 | - ✅ Findings (all, by_id, reporters) 323 | - ✅ Projects (current, all) 324 | - ✅ Workflows (all, by_id, node_definitions) 325 | - ✅ Replay (sessions, collections) 326 | - ✅ Automate (sessions, tasks) 327 | - ✅ Viewer, Runtime, InstanceSettings 328 | - ✅ DNS, UpstreamProxies, Tamper 329 | - ✅ Assistant, Environments, Plugins 330 | 331 | **Mutations that were missing (all added):** 332 | - ✅ Request metadata updates 333 | - ✅ Sitemap CRUD operations 334 | - ✅ Intercept control (pause/resume/forward/drop) 335 | - ✅ Scope CRUD operations 336 | - ✅ Finding CRUD operations 337 | - ✅ Project management 338 | - ✅ Workflow CRUD and execution 339 | - ✅ Replay session management 340 | - ✅ Automate session and task management 341 | - ✅ Tamper rule management 342 | - ✅ DNS configuration 343 | - ✅ Upstream proxy management 344 | - ✅ Assistant operations 345 | - ✅ Authentication flows 346 | 347 | ## Conclusion 348 | 349 | The repository has been significantly improved from a basic GraphQL wrapper to a comprehensive, easy-to-use library with: 350 | - 110+ helper methods 351 | - Complete documentation 352 | - Real-world examples 353 | - Full coverage of major Caido features 354 | 355 | Users can now interact with Caido's GraphQL API using simple, intuitive method calls instead of writing raw GraphQL queries, while still having the flexibility to use custom queries when needed. 356 | -------------------------------------------------------------------------------- /ENDPOINTS.md: -------------------------------------------------------------------------------- 1 | # Caido GraphQL Endpoints 2 | 3 | This document provides an overview of the available GraphQL endpoints in the caido-crystal library. 4 | 5 | ## Overview 6 | 7 | The library now includes helper modules for common Caido GraphQL operations: 8 | - **CaidoQueries**: Pre-built query templates for fetching data 9 | - **CaidoMutations**: Pre-built mutation templates for modifying data 10 | 11 | ## Installation 12 | 13 | Add this to your application's `shard.yml`: 14 | 15 | ```yaml 16 | dependencies: 17 | caido-crystal: 18 | github: hahwul/caido-crystal 19 | ``` 20 | 21 | ## Usage 22 | 23 | ### Basic Setup 24 | 25 | ```crystal 26 | require "caido-crystal" 27 | 28 | client = CaidoClient.new "http://localhost:8080/graphql" 29 | ``` 30 | 31 | ### Authentication 32 | 33 | The client automatically uses the `CAIDO_AUTH_TOKEN` environment variable if available: 34 | 35 | ```bash 36 | export CAIDO_AUTH_TOKEN="your-token-here" 37 | ``` 38 | 39 | Or provide custom headers: 40 | 41 | ```crystal 42 | headers = {"Authorization" => "Bearer your-token-here"} 43 | client = CaidoClient.new "http://localhost:8080/graphql", headers 44 | ``` 45 | 46 | ## Query Modules 47 | 48 | ### Requests 49 | 50 | Get HTTP requests and responses from Caido's history. 51 | 52 | ```crystal 53 | # Get all requests with pagination 54 | query = CaidoQueries::Requests.all(first: 50) 55 | response = client.query query 56 | 57 | # Get requests with HTTPQL filter 58 | query = CaidoQueries::Requests.all(first: 50, filter: "host:example.com") 59 | response = client.query query 60 | 61 | # Get a single request by ID 62 | query = CaidoQueries::Requests.by_id("request-id-here") 63 | response = client.query query 64 | 65 | # Get requests by offset (alternative pagination) 66 | query = CaidoQueries::Requests.by_offset(offset: 0, limit: 50) 67 | response = client.query query 68 | ``` 69 | 70 | ### Sitemap 71 | 72 | Explore the target sitemap hierarchy. 73 | 74 | ```crystal 75 | # Get root sitemap entries 76 | query = CaidoQueries::Sitemap.root_entries 77 | response = client.query query 78 | 79 | # Get root entries for a specific scope 80 | query = CaidoQueries::Sitemap.root_entries(scope_id: "scope-id") 81 | response = client.query query 82 | 83 | # Get descendant entries of a parent 84 | query = CaidoQueries::Sitemap.descendant_entries(parent_id: "parent-id", depth: "DIRECT") 85 | response = client.query query 86 | 87 | # Get a single sitemap entry 88 | query = CaidoQueries::Sitemap.by_id("entry-id") 89 | response = client.query query 90 | ``` 91 | 92 | ### Intercept 93 | 94 | Manage HTTP intercept proxy functionality. 95 | 96 | ```crystal 97 | # Get intercept entries 98 | query = CaidoQueries::Intercept.entries(first: 50) 99 | response = client.query query 100 | 101 | # Get intercept status 102 | query = CaidoQueries::Intercept.status 103 | response = client.query query 104 | 105 | # Get intercept options 106 | query = CaidoQueries::Intercept.options 107 | response = client.query query 108 | ``` 109 | 110 | ### Scopes 111 | 112 | Manage target scopes with allowlists and denylists. 113 | 114 | ```crystal 115 | # Get all scopes 116 | query = CaidoQueries::Scopes.all 117 | response = client.query query 118 | 119 | # Get a single scope 120 | query = CaidoQueries::Scopes.by_id("scope-id") 121 | response = client.query query 122 | ``` 123 | 124 | ### Findings 125 | 126 | Manage security findings discovered during testing. 127 | 128 | ```crystal 129 | # Get all findings 130 | query = CaidoQueries::Findings.all(first: 50) 131 | response = client.query query 132 | 133 | # Get a single finding 134 | query = CaidoQueries::Findings.by_id("finding-id") 135 | response = client.query query 136 | 137 | # Get finding reporters 138 | query = CaidoQueries::Findings.reporters 139 | response = client.query query 140 | ``` 141 | 142 | ### Projects 143 | 144 | Manage Caido projects (requires cloud subscription). 145 | 146 | ```crystal 147 | # Get current project 148 | query = CaidoQueries::Projects.current 149 | response = client.query query 150 | 151 | # Get all projects 152 | query = CaidoQueries::Projects.all 153 | response = client.query query 154 | ``` 155 | 156 | ### Workflows 157 | 158 | Manage passive and active workflows for automation. 159 | 160 | ```crystal 161 | # Get all workflows 162 | query = CaidoQueries::Workflows.all 163 | response = client.query query 164 | 165 | # Get a single workflow 166 | query = CaidoQueries::Workflows.by_id("workflow-id") 167 | response = client.query query 168 | 169 | # Get workflow node definitions 170 | query = CaidoQueries::Workflows.node_definitions 171 | response = client.query query 172 | ``` 173 | 174 | ### Replay 175 | 176 | Manage replay sessions for request testing. 177 | 178 | ```crystal 179 | # Get replay sessions 180 | query = CaidoQueries::Replay.sessions(first: 50) 181 | response = client.query query 182 | 183 | # Get a single replay session 184 | query = CaidoQueries::Replay.session_by_id("session-id") 185 | response = client.query query 186 | 187 | # Get replay session collections 188 | query = CaidoQueries::Replay.collections(first: 50) 189 | response = client.query query 190 | ``` 191 | 192 | ### Automate 193 | 194 | Manage automated attack sessions. 195 | 196 | ```crystal 197 | # Get automate sessions 198 | query = CaidoQueries::Automate.sessions(first: 50) 199 | response = client.query query 200 | 201 | # Get a single automate session 202 | query = CaidoQueries::Automate.session_by_id("session-id") 203 | response = client.query query 204 | 205 | # Get automate tasks 206 | query = CaidoQueries::Automate.tasks(first: 50) 207 | response = client.query query 208 | ``` 209 | 210 | ### Additional Query Modules 211 | 212 | - **Viewer**: Get current user information 213 | - **Runtime**: Get Caido runtime information 214 | - **InstanceSettings**: Get and manage instance settings 215 | - **DNS**: Manage DNS rewrites and upstreams 216 | - **UpstreamProxies**: Manage HTTP and SOCKS upstream proxies 217 | - **Tamper**: Manage request/response tampering rules 218 | - **Assistant**: AI assistant sessions (requires cloud) 219 | - **Environments**: Manage environment variables (requires cloud) 220 | - **Plugins**: Manage installed plugins 221 | 222 | ## Mutation Modules 223 | 224 | ### Request Mutations 225 | 226 | Modify request metadata and properties. 227 | 228 | ```crystal 229 | # Update request metadata (color, label) 230 | mutation = CaidoMutations::Requests.update_metadata( 231 | request_id: "request-id", 232 | color: "#FF0000", 233 | label: "Important" 234 | ) 235 | response = client.query mutation 236 | 237 | # Render a request with environment variables 238 | mutation = CaidoMutations::Requests.render(request_id: "request-id") 239 | response = client.query mutation 240 | ``` 241 | 242 | ### Sitemap Mutations 243 | 244 | Manage sitemap entries. 245 | 246 | ```crystal 247 | # Create sitemap entries from a request 248 | mutation = CaidoMutations::Sitemap.create_entries(request_id: "request-id") 249 | response = client.query mutation 250 | 251 | # Delete sitemap entries 252 | mutation = CaidoMutations::Sitemap.delete_entries(["entry-id-1", "entry-id-2"]) 253 | response = client.query mutation 254 | 255 | # Clear all sitemap entries 256 | mutation = CaidoMutations::Sitemap.clear_all 257 | response = client.query mutation 258 | ``` 259 | 260 | ### Intercept Mutations 261 | 262 | Control intercept proxy behavior. 263 | 264 | ```crystal 265 | # Pause intercept 266 | mutation = CaidoMutations::Intercept.pause 267 | response = client.query mutation 268 | 269 | # Resume intercept 270 | mutation = CaidoMutations::Intercept.resume 271 | response = client.query mutation 272 | 273 | # Forward an intercept message (optionally modified) 274 | mutation = CaidoMutations::Intercept.forward_message( 275 | message_id: "message-id", 276 | request: "modified-request-here" 277 | ) 278 | response = client.query mutation 279 | 280 | # Drop an intercept message 281 | mutation = CaidoMutations::Intercept.drop_message(message_id: "message-id") 282 | response = client.query mutation 283 | 284 | # Set intercept options 285 | mutation = CaidoMutations::Intercept.set_options( 286 | request_enabled: true, 287 | response_enabled: false, 288 | in_scope_only: true 289 | ) 290 | response = client.query mutation 291 | ``` 292 | 293 | ### Scope Mutations 294 | 295 | Create and manage scopes. 296 | 297 | ```crystal 298 | # Create a new scope 299 | mutation = CaidoMutations::Scopes.create( 300 | name: "Test Scope", 301 | allowlist: ["https://example.com/*"], 302 | denylist: ["https://example.com/admin/*"] 303 | ) 304 | response = client.query mutation 305 | 306 | # Update a scope 307 | mutation = CaidoMutations::Scopes.update( 308 | scope_id: "scope-id", 309 | name: "Updated Scope", 310 | allowlist: ["https://example.com/*", "https://test.com/*"] 311 | ) 312 | response = client.query mutation 313 | 314 | # Delete a scope 315 | mutation = CaidoMutations::Scopes.delete(scope_id: "scope-id") 316 | response = client.query mutation 317 | 318 | # Rename a scope 319 | mutation = CaidoMutations::Scopes.rename(scope_id: "scope-id", name: "New Name") 320 | response = client.query mutation 321 | ``` 322 | 323 | ### Finding Mutations 324 | 325 | Create and manage security findings. 326 | 327 | ```crystal 328 | # Create a finding 329 | mutation = CaidoMutations::Findings.create( 330 | request_id: "request-id", 331 | title: "SQL Injection", 332 | description: "Potential SQL injection vulnerability", 333 | reporter: "Manual" 334 | ) 335 | response = client.query mutation 336 | 337 | # Update a finding 338 | mutation = CaidoMutations::Findings.update( 339 | finding_id: "finding-id", 340 | title: "Confirmed SQL Injection", 341 | description: "Updated description" 342 | ) 343 | response = client.query mutation 344 | 345 | # Delete findings 346 | mutation = CaidoMutations::Findings.delete(["finding-id-1", "finding-id-2"]) 347 | response = client.query mutation 348 | 349 | # Hide findings 350 | mutation = CaidoMutations::Findings.hide(["finding-id-1", "finding-id-2"]) 351 | response = client.query mutation 352 | 353 | # Export findings 354 | mutation = CaidoMutations::Findings.export(["finding-id-1", "finding-id-2"]) 355 | response = client.query mutation 356 | ``` 357 | 358 | ### Workflow Mutations 359 | 360 | Manage workflow automation. 361 | 362 | ```crystal 363 | # Create a workflow (requires cloud) 364 | mutation = CaidoMutations::Workflows.create( 365 | name: "Custom Workflow", 366 | kind: "ACTIVE", 367 | definition: "workflow-definition-json" 368 | ) 369 | response = client.query mutation 370 | 371 | # Update a workflow 372 | mutation = CaidoMutations::Workflows.update( 373 | workflow_id: "workflow-id", 374 | name: "Updated Workflow", 375 | definition: "new-definition-json" 376 | ) 377 | response = client.query mutation 378 | 379 | # Toggle workflow enabled state 380 | mutation = CaidoMutations::Workflows.toggle(workflow_id: "workflow-id", enabled: true) 381 | response = client.query mutation 382 | 383 | # Run an active workflow 384 | mutation = CaidoMutations::Workflows.run_active( 385 | workflow_id: "workflow-id", 386 | request_id: "request-id" 387 | ) 388 | response = client.query mutation 389 | 390 | # Delete a workflow 391 | mutation = CaidoMutations::Workflows.delete(workflow_id: "workflow-id") 392 | response = client.query mutation 393 | ``` 394 | 395 | ### Replay Mutations 396 | 397 | Manage replay sessions and collections. 398 | 399 | ```crystal 400 | # Create a replay session 401 | mutation = CaidoMutations::Replay.create_session( 402 | name: "Test Session", 403 | source: "request-source", 404 | collection_id: "collection-id" 405 | ) 406 | response = client.query mutation 407 | 408 | # Create a replay session collection 409 | mutation = CaidoMutations::Replay.create_collection(name: "My Collection") 410 | response = client.query mutation 411 | 412 | # Delete replay sessions 413 | mutation = CaidoMutations::Replay.delete_sessions(["session-id-1", "session-id-2"]) 414 | response = client.query mutation 415 | 416 | # Move a replay session to a collection 417 | mutation = CaidoMutations::Replay.move_session( 418 | session_id: "session-id", 419 | collection_id: "collection-id" 420 | ) 421 | response = client.query mutation 422 | 423 | # Start a replay task (requires cloud) 424 | mutation = CaidoMutations::Replay.start_task(session_id: "session-id") 425 | response = client.query mutation 426 | ``` 427 | 428 | ### Automate Mutations 429 | 430 | Manage automate sessions and tasks. 431 | 432 | ```crystal 433 | # Create an automate session 434 | mutation = CaidoMutations::Automate.create_session( 435 | name: "Test Session", 436 | host: "example.com", 437 | port: 443, 438 | is_tls: true 439 | ) 440 | response = client.query mutation 441 | 442 | # Start an automate task 443 | mutation = CaidoMutations::Automate.start_task(session_id: "session-id") 444 | response = client.query mutation 445 | 446 | # Pause an automate task 447 | mutation = CaidoMutations::Automate.pause_task(task_id: "task-id") 448 | response = client.query mutation 449 | 450 | # Resume an automate task 451 | mutation = CaidoMutations::Automate.resume_task(task_id: "task-id") 452 | response = client.query mutation 453 | 454 | # Cancel an automate task 455 | mutation = CaidoMutations::Automate.cancel_task(task_id: "task-id") 456 | response = client.query mutation 457 | ``` 458 | 459 | ### Tamper Mutations 460 | 461 | Manage request/response tampering rules. 462 | 463 | ```crystal 464 | # Create a tamper rule 465 | mutation = CaidoMutations::Tamper.create_rule( 466 | name: "Add Header", 467 | condition: "request.host == 'example.com'", 468 | strategy: "add-header-strategy", 469 | collection_id: "collection-id" 470 | ) 471 | response = client.query mutation 472 | 473 | # Create a tamper rule collection 474 | mutation = CaidoMutations::Tamper.create_collection(name: "My Rules") 475 | response = client.query mutation 476 | 477 | # Toggle tamper rule enabled state 478 | mutation = CaidoMutations::Tamper.toggle_rule(rule_id: "rule-id", enabled: true) 479 | response = client.query mutation 480 | 481 | # Update a tamper rule 482 | mutation = CaidoMutations::Tamper.update_rule( 483 | rule_id: "rule-id", 484 | name: "Updated Rule", 485 | condition: "new-condition" 486 | ) 487 | response = client.query mutation 488 | 489 | # Delete a tamper rule 490 | mutation = CaidoMutations::Tamper.delete_rule(rule_id: "rule-id") 491 | response = client.query mutation 492 | ``` 493 | 494 | ### Additional Mutation Modules 495 | 496 | - **Projects**: Create, select, delete, and rename projects (requires cloud) 497 | - **DNS**: Manage DNS rewrites and upstreams 498 | - **UpstreamProxies**: Manage HTTP and SOCKS upstream proxies 499 | - **Assistant**: AI assistant operations (requires cloud) 500 | - **Authentication**: Login, logout, authentication flows (requires cloud) 501 | - **Tasks**: Cancel running tasks 502 | 503 | ## Custom Queries 504 | 505 | For operations not covered by the helper modules, you can still use raw GraphQL: 506 | 507 | ```crystal 508 | client = CaidoClient.new "http://localhost:8080/graphql" 509 | 510 | custom_query = %Q( 511 | query CustomQuery { 512 | requests(first: 10) { 513 | nodes { 514 | id 515 | host 516 | path 517 | } 518 | } 519 | } 520 | ) 521 | 522 | response = client.query custom_query 523 | ``` 524 | 525 | ## Error Handling 526 | 527 | GraphQL responses include errors in the response body. Check for errors: 528 | 529 | ```crystal 530 | response = client.query query 531 | # Parse response and check for errors field 532 | ``` 533 | 534 | ## Cloud Features 535 | 536 | Some features require a Caido Cloud subscription and are marked with `@cloud` in the schema: 537 | - Projects management 538 | - Assistant (AI) features 539 | - Some workflow operations 540 | - Advanced replay features 541 | 542 | ## Additional Resources 543 | 544 | - [Caido Documentation](https://docs.caido.io/) 545 | - [Caido GraphQL Explorer](https://github.com/caido/graphql-explorer) 546 | - [Official Caido GraphQL Schema](https://github.com/caido/graphql-explorer/blob/main/src/assets/schema.graphql) 547 | 548 | ## Contributing 549 | 550 | Contributions are welcome! If you find missing endpoints or have improvements, please open an issue or pull request. 551 | 552 | ## License 553 | 554 | MIT License - see LICENSE file for details 555 | -------------------------------------------------------------------------------- /src/client/queries.cr: -------------------------------------------------------------------------------- 1 | module CaidoQueries 2 | # Query templates for common Caido GraphQL operations 3 | 4 | module Requests 5 | # Get all requests with pagination 6 | def self.all(after : String? = nil, first : Int32 = 50, filter : String? = nil) 7 | filter_clause = filter ? %Q(filter: "#{filter}") : "" 8 | after_clause = after ? %Q(after: "#{after}") : "" 9 | 10 | %Q( 11 | query GetRequests { 12 | requests(#{after_clause} first: #{first} #{filter_clause}) { 13 | pageInfo { 14 | hasNextPage 15 | hasPreviousPage 16 | startCursor 17 | endCursor 18 | } 19 | nodes { 20 | id 21 | host 22 | port 23 | path 24 | query 25 | method 26 | edited 27 | isTls 28 | length 29 | alteration 30 | metadata { 31 | id 32 | color 33 | label 34 | } 35 | fileExtension 36 | source 37 | createdAt 38 | response { 39 | id 40 | statusCode 41 | roundtripTime 42 | length 43 | createdAt 44 | alteration 45 | edited 46 | } 47 | } 48 | } 49 | } 50 | ) 51 | end 52 | 53 | # Get a single request by ID 54 | def self.by_id(id : String) 55 | %Q( 56 | query GetRequest { 57 | request(id: "#{id}") { 58 | id 59 | host 60 | port 61 | path 62 | query 63 | method 64 | edited 65 | isTls 66 | length 67 | alteration 68 | metadata { 69 | id 70 | color 71 | label 72 | } 73 | fileExtension 74 | source 75 | createdAt 76 | raw 77 | response { 78 | id 79 | statusCode 80 | roundtripTime 81 | length 82 | createdAt 83 | alteration 84 | edited 85 | raw 86 | } 87 | } 88 | } 89 | ) 90 | end 91 | 92 | # Get requests by offset (alternative pagination) 93 | def self.by_offset(offset : Int32 = 0, limit : Int32 = 50, filter : String? = nil) 94 | filter_clause = filter ? %Q(filter: "#{filter}") : "" 95 | 96 | %Q( 97 | query GetRequestsByOffset { 98 | requestsByOffset(offset: #{offset} limit: #{limit} #{filter_clause}) { 99 | nodes { 100 | id 101 | host 102 | port 103 | path 104 | query 105 | method 106 | edited 107 | isTls 108 | length 109 | alteration 110 | metadata { 111 | id 112 | color 113 | label 114 | } 115 | fileExtension 116 | source 117 | createdAt 118 | } 119 | } 120 | } 121 | ) 122 | end 123 | end 124 | 125 | module Sitemap 126 | # Get root sitemap entries 127 | def self.root_entries(scope_id : String? = nil) 128 | scope_clause = scope_id ? %Q(scopeId: "#{scope_id}") : "" 129 | 130 | %Q( 131 | query GetSitemapRootEntries { 132 | sitemapRootEntries(#{scope_clause}) { 133 | nodes { 134 | id 135 | label 136 | kind 137 | parentId 138 | hasDescendants 139 | metadata { 140 | id 141 | color 142 | label 143 | } 144 | } 145 | } 146 | } 147 | ) 148 | end 149 | 150 | # Get descendant entries of a parent 151 | def self.descendant_entries(parent_id : String, depth : String = "DIRECT") 152 | %Q( 153 | query GetSitemapDescendantEntries { 154 | sitemapDescendantEntries(parentId: "#{parent_id}", depth: #{depth}) { 155 | nodes { 156 | id 157 | label 158 | kind 159 | parentId 160 | hasDescendants 161 | metadata { 162 | id 163 | color 164 | label 165 | } 166 | } 167 | } 168 | } 169 | ) 170 | end 171 | 172 | # Get a single sitemap entry 173 | def self.by_id(id : String) 174 | %Q( 175 | query GetSitemapEntry { 176 | sitemapEntry(id: "#{id}") { 177 | id 178 | label 179 | kind 180 | parentId 181 | hasDescendants 182 | metadata { 183 | id 184 | color 185 | label 186 | } 187 | } 188 | } 189 | ) 190 | end 191 | end 192 | 193 | module Intercept 194 | # Get intercept entries 195 | def self.entries(after : String? = nil, first : Int32 = 50, filter : String? = nil) 196 | filter_clause = filter ? %Q(filter: "#{filter}") : "" 197 | after_clause = after ? %Q(after: "#{after}") : "" 198 | 199 | %Q( 200 | query GetInterceptEntries { 201 | interceptEntries(#{after_clause} first: #{first} #{filter_clause}) { 202 | pageInfo { 203 | hasNextPage 204 | hasPreviousPage 205 | startCursor 206 | endCursor 207 | } 208 | nodes { 209 | id 210 | kind 211 | request { 212 | id 213 | host 214 | port 215 | path 216 | query 217 | method 218 | isTls 219 | } 220 | } 221 | } 222 | } 223 | ) 224 | end 225 | 226 | # Get intercept status 227 | def self.status 228 | %Q( 229 | query GetInterceptStatus { 230 | interceptStatus { 231 | isEnabled 232 | inScopeOnly 233 | } 234 | } 235 | ) 236 | end 237 | 238 | # Get intercept options 239 | def self.options 240 | %Q( 241 | query GetInterceptOptions { 242 | interceptOptions { 243 | request { 244 | enabled 245 | internal 246 | inScopeOnly 247 | query 248 | } 249 | response { 250 | enabled 251 | inScopeOnly 252 | statusCode { 253 | enabled 254 | value 255 | } 256 | } 257 | } 258 | } 259 | ) 260 | end 261 | end 262 | 263 | module Scopes 264 | # Get all scopes 265 | def self.all 266 | %Q( 267 | query GetScopes { 268 | scopes { 269 | id 270 | name 271 | allowlist 272 | denylist 273 | } 274 | } 275 | ) 276 | end 277 | 278 | # Get a single scope 279 | def self.by_id(id : String) 280 | %Q( 281 | query GetScope { 282 | scope(id: "#{id}") { 283 | id 284 | name 285 | allowlist 286 | denylist 287 | } 288 | } 289 | ) 290 | end 291 | end 292 | 293 | module Findings 294 | # Get findings with pagination 295 | def self.all(after : String? = nil, first : Int32 = 50) 296 | after_clause = after ? %Q(after: "#{after}") : "" 297 | 298 | %Q( 299 | query GetFindings { 300 | findings(#{after_clause} first: #{first}) { 301 | pageInfo { 302 | hasNextPage 303 | hasPreviousPage 304 | startCursor 305 | endCursor 306 | } 307 | nodes { 308 | id 309 | title 310 | description 311 | reporter 312 | dedupeKey 313 | createdAt 314 | request { 315 | id 316 | host 317 | path 318 | method 319 | } 320 | } 321 | } 322 | } 323 | ) 324 | end 325 | 326 | # Get a single finding 327 | def self.by_id(id : String) 328 | %Q( 329 | query GetFinding { 330 | finding(id: "#{id}") { 331 | id 332 | title 333 | description 334 | reporter 335 | dedupeKey 336 | createdAt 337 | request { 338 | id 339 | host 340 | path 341 | query 342 | method 343 | raw 344 | } 345 | } 346 | } 347 | ) 348 | end 349 | 350 | # Get finding reporters 351 | def self.reporters 352 | %Q( 353 | query GetFindingReporters { 354 | findingReporters 355 | } 356 | ) 357 | end 358 | end 359 | 360 | module Projects 361 | # Get current project 362 | def self.current 363 | %Q( 364 | query GetCurrentProject { 365 | currentProject { 366 | id 367 | name 368 | path 369 | version 370 | status 371 | size 372 | backups { 373 | id 374 | name 375 | path 376 | size 377 | createdAt 378 | } 379 | } 380 | } 381 | ) 382 | end 383 | 384 | # Get all projects (requires cloud) 385 | def self.all 386 | %Q( 387 | query GetProjects { 388 | projects { 389 | id 390 | name 391 | path 392 | version 393 | status 394 | createdAt 395 | } 396 | } 397 | ) 398 | end 399 | end 400 | 401 | module Workflows 402 | # Get all workflows 403 | def self.all 404 | %Q( 405 | query GetWorkflows { 406 | workflows { 407 | id 408 | name 409 | kind 410 | enabled 411 | global 412 | definition 413 | } 414 | } 415 | ) 416 | end 417 | 418 | # Get a single workflow 419 | def self.by_id(id : String) 420 | %Q( 421 | query GetWorkflow { 422 | workflow(id: "#{id}") { 423 | id 424 | name 425 | kind 426 | enabled 427 | global 428 | definition 429 | } 430 | } 431 | ) 432 | end 433 | 434 | # Get workflow node definitions 435 | def self.node_definitions 436 | %Q( 437 | query GetWorkflowNodeDefinitions { 438 | workflowNodeDefinitions { 439 | id 440 | name 441 | kind 442 | body 443 | } 444 | } 445 | ) 446 | end 447 | end 448 | 449 | module Replay 450 | # Get replay sessions 451 | def self.sessions(after : String? = nil, first : Int32 = 50) 452 | after_clause = after ? %Q(after: "#{after}") : "" 453 | 454 | %Q( 455 | query GetReplaySessions { 456 | replaySessions(#{after_clause} first: #{first}) { 457 | pageInfo { 458 | hasNextPage 459 | hasPreviousPage 460 | startCursor 461 | endCursor 462 | } 463 | nodes { 464 | id 465 | name 466 | activeEntry { 467 | id 468 | } 469 | collection { 470 | id 471 | name 472 | } 473 | } 474 | } 475 | } 476 | ) 477 | end 478 | 479 | # Get a single replay session 480 | def self.session_by_id(id : String) 481 | %Q( 482 | query GetReplaySession { 483 | replaySession(id: "#{id}") { 484 | id 485 | name 486 | activeEntry { 487 | id 488 | error 489 | request { 490 | id 491 | raw 492 | } 493 | response { 494 | id 495 | raw 496 | } 497 | } 498 | collection { 499 | id 500 | name 501 | } 502 | } 503 | } 504 | ) 505 | end 506 | 507 | # Get replay session collections 508 | def self.collections(after : String? = nil, first : Int32 = 50) 509 | after_clause = after ? %Q(after: "#{after}") : "" 510 | 511 | %Q( 512 | query GetReplaySessionCollections { 513 | replaySessionCollections(#{after_clause} first: #{first}) { 514 | pageInfo { 515 | hasNextPage 516 | hasPreviousPage 517 | startCursor 518 | endCursor 519 | } 520 | nodes { 521 | id 522 | name 523 | } 524 | } 525 | } 526 | ) 527 | end 528 | end 529 | 530 | module Automate 531 | # Get automate sessions 532 | def self.sessions(after : String? = nil, first : Int32 = 50) 533 | after_clause = after ? %Q(after: "#{after}") : "" 534 | 535 | %Q( 536 | query GetAutomateSessions { 537 | automateSessions(#{after_clause} first: #{first}) { 538 | pageInfo { 539 | hasNextPage 540 | hasPreviousPage 541 | startCursor 542 | endCursor 543 | } 544 | nodes { 545 | id 546 | name 547 | connection { 548 | host 549 | port 550 | isTls 551 | } 552 | createdAt 553 | } 554 | } 555 | } 556 | ) 557 | end 558 | 559 | # Get a single automate session 560 | def self.session_by_id(id : String) 561 | %Q( 562 | query GetAutomateSession { 563 | automateSession(id: "#{id}") { 564 | id 565 | name 566 | connection { 567 | host 568 | port 569 | isTls 570 | } 571 | settings { 572 | updateContentLength 573 | updateHostHeader 574 | followRedirects 575 | maxRedirects 576 | } 577 | createdAt 578 | } 579 | } 580 | ) 581 | end 582 | 583 | # Get automate tasks 584 | def self.tasks(after : String? = nil, first : Int32 = 50) 585 | after_clause = after ? %Q(after: "#{after}") : "" 586 | 587 | %Q( 588 | query GetAutomateTasks { 589 | automateTasks(#{after_clause} first: #{first}) { 590 | pageInfo { 591 | hasNextPage 592 | hasPreviousPage 593 | startCursor 594 | endCursor 595 | } 596 | nodes { 597 | id 598 | paused 599 | createdAt 600 | } 601 | } 602 | } 603 | ) 604 | end 605 | end 606 | 607 | module Viewer 608 | # Get current user information 609 | def self.info 610 | %Q( 611 | query GetViewer { 612 | viewer { 613 | id 614 | username 615 | settings { 616 | data 617 | } 618 | } 619 | } 620 | ) 621 | end 622 | end 623 | 624 | module Runtime 625 | # Get runtime information 626 | def self.info 627 | %Q( 628 | query GetRuntime { 629 | runtime { 630 | version 631 | platform 632 | availableUpdate 633 | supportedQueries 634 | } 635 | } 636 | ) 637 | end 638 | end 639 | 640 | module InstanceSettings 641 | # Get instance settings 642 | def self.get 643 | %Q( 644 | query GetInstanceSettings { 645 | instanceSettings { 646 | theme 647 | language 648 | license { 649 | name 650 | expiry 651 | } 652 | ai { 653 | providers { 654 | anthropic { 655 | apiKey 656 | } 657 | openai { 658 | apiKey 659 | url 660 | } 661 | google { 662 | apiKey 663 | } 664 | openrouter { 665 | apiKey 666 | } 667 | } 668 | model 669 | } 670 | } 671 | } 672 | ) 673 | end 674 | end 675 | 676 | module DNS 677 | # Get DNS rewrites 678 | def self.rewrites 679 | %Q( 680 | query GetDNSRewrites { 681 | dnsRewrites { 682 | id 683 | enabled 684 | rank 685 | name 686 | strategy 687 | source 688 | destination 689 | } 690 | } 691 | ) 692 | end 693 | 694 | # Get DNS upstreams 695 | def self.upstreams 696 | %Q( 697 | query GetDNSUpstreams { 698 | dnsUpstreams { 699 | id 700 | name 701 | kind 702 | address 703 | } 704 | } 705 | ) 706 | end 707 | end 708 | 709 | module UpstreamProxies 710 | # Get HTTP upstream proxies 711 | def self.http 712 | %Q( 713 | query GetUpstreamProxiesHttp { 714 | upstreamProxiesHttp { 715 | id 716 | enabled 717 | rank 718 | allowlist 719 | denylist 720 | kind 721 | address 722 | authentication { 723 | username 724 | password 725 | } 726 | } 727 | } 728 | ) 729 | end 730 | 731 | # Get SOCKS upstream proxies 732 | def self.socks 733 | %Q( 734 | query GetUpstreamProxiesSocks { 735 | upstreamProxiesSocks { 736 | id 737 | enabled 738 | rank 739 | allowlist 740 | denylist 741 | kind 742 | address 743 | authentication { 744 | username 745 | password 746 | } 747 | } 748 | } 749 | ) 750 | end 751 | end 752 | 753 | module Tamper 754 | # Get all tamper rules 755 | def self.rules 756 | %Q( 757 | query GetTamperRules { 758 | tamperRuleCollections { 759 | id 760 | name 761 | rules { 762 | id 763 | name 764 | enabled 765 | rank 766 | condition 767 | strategy 768 | } 769 | } 770 | } 771 | ) 772 | end 773 | 774 | # Get a single tamper rule 775 | def self.rule_by_id(id : String) 776 | %Q( 777 | query GetTamperRule { 778 | tamperRule(id: "#{id}") { 779 | id 780 | name 781 | enabled 782 | rank 783 | condition 784 | strategy 785 | collection { 786 | id 787 | name 788 | } 789 | } 790 | } 791 | ) 792 | end 793 | end 794 | 795 | module Assistant 796 | # Get assistant sessions (requires cloud) 797 | def self.sessions 798 | %Q( 799 | query GetAssistantSessions { 800 | assistantSessions { 801 | id 802 | name 803 | modelId 804 | createdAt 805 | } 806 | } 807 | ) 808 | end 809 | 810 | # Get assistant models (requires cloud) 811 | def self.models 812 | %Q( 813 | query GetAssistantModels { 814 | assistantModels { 815 | id 816 | name 817 | provider 818 | } 819 | } 820 | ) 821 | end 822 | end 823 | 824 | module Environments 825 | # Get all environments (requires cloud) 826 | def self.all 827 | %Q( 828 | query GetEnvironments { 829 | environments { 830 | id 831 | name 832 | data 833 | } 834 | } 835 | ) 836 | end 837 | 838 | # Get environment context 839 | def self.context 840 | %Q( 841 | query GetEnvironmentContext { 842 | environmentContext { 843 | selectedId 844 | } 845 | } 846 | ) 847 | end 848 | end 849 | 850 | module Plugins 851 | # Get all plugin packages 852 | def self.packages 853 | %Q( 854 | query GetPluginPackages { 855 | pluginPackages { 856 | id 857 | name 858 | version 859 | author 860 | description 861 | enabled 862 | } 863 | } 864 | ) 865 | end 866 | end 867 | end 868 | -------------------------------------------------------------------------------- /src/client/mutations.cr: -------------------------------------------------------------------------------- 1 | module CaidoMutations 2 | # Mutation templates for common Caido GraphQL operations 3 | 4 | module Requests 5 | # Update request metadata (color, label) 6 | def self.update_metadata(request_id : String, color : String? = nil, label : String? = nil) 7 | metadata_parts = [] of String 8 | metadata_parts << %Q(color: "#{color}") if color 9 | metadata_parts << %Q(label: "#{label}") if label 10 | metadata_input = metadata_parts.join(", ") 11 | 12 | %Q( 13 | mutation UpdateRequestMetadata { 14 | updateRequestMetadata(id: "#{request_id}", input: { #{metadata_input} }) { 15 | request { 16 | id 17 | metadata { 18 | id 19 | color 20 | label 21 | } 22 | } 23 | } 24 | } 25 | ) 26 | end 27 | 28 | # Render a request with environment variables 29 | def self.render(request_id : String) 30 | %Q( 31 | mutation RenderRequest { 32 | renderRequest(id: "#{request_id}", input: {}) { 33 | request { 34 | id 35 | raw 36 | } 37 | } 38 | } 39 | ) 40 | end 41 | end 42 | 43 | module Sitemap 44 | # Create sitemap entries from a request 45 | def self.create_entries(request_id : String) 46 | %Q( 47 | mutation CreateSitemapEntries { 48 | createSitemapEntries(requestId: "#{request_id}") { 49 | entries { 50 | id 51 | label 52 | kind 53 | } 54 | } 55 | } 56 | ) 57 | end 58 | 59 | # Delete sitemap entries 60 | def self.delete_entries(entry_ids : Array(String)) 61 | ids_str = entry_ids.map { |id| %Q("#{id}") }.join(", ") 62 | 63 | %Q( 64 | mutation DeleteSitemapEntries { 65 | deleteSitemapEntries(ids: [#{ids_str}]) { 66 | deletedIds 67 | } 68 | } 69 | ) 70 | end 71 | 72 | # Clear all sitemap entries 73 | def self.clear_all 74 | %Q( 75 | mutation ClearSitemapEntries { 76 | clearSitemapEntries { 77 | success 78 | } 79 | } 80 | ) 81 | end 82 | end 83 | 84 | module Intercept 85 | # Pause intercept 86 | def self.pause 87 | %Q( 88 | mutation PauseIntercept { 89 | pauseIntercept { 90 | success 91 | } 92 | } 93 | ) 94 | end 95 | 96 | # Resume intercept 97 | def self.resume 98 | %Q( 99 | mutation ResumeIntercept { 100 | resumeIntercept { 101 | success 102 | } 103 | } 104 | ) 105 | end 106 | 107 | # Forward an intercept message 108 | def self.forward_message(message_id : String, request : String? = nil, response : String? = nil) 109 | input_parts = [] of String 110 | input_parts << %Q(request: "#{request}") if request 111 | input_parts << %Q(response: "#{response}") if response 112 | input_str = input_parts.empty? ? "" : ", input: { #{input_parts.join(", ")} }" 113 | 114 | %Q( 115 | mutation ForwardInterceptMessage { 116 | forwardInterceptMessage(id: "#{message_id}"#{input_str}) { 117 | success 118 | } 119 | } 120 | ) 121 | end 122 | 123 | # Drop an intercept message 124 | def self.drop_message(message_id : String) 125 | %Q( 126 | mutation DropInterceptMessage { 127 | dropInterceptMessage(id: "#{message_id}") { 128 | success 129 | } 130 | } 131 | ) 132 | end 133 | 134 | # Set intercept options 135 | def self.set_options(request_enabled : Bool? = nil, response_enabled : Bool? = nil, in_scope_only : Bool? = nil) 136 | options = [] of String 137 | 138 | if request_enabled || in_scope_only 139 | req_parts = [] of String 140 | req_parts << "enabled: #{request_enabled}" if request_enabled 141 | req_parts << "inScopeOnly: #{in_scope_only}" if in_scope_only 142 | options << "request: { #{req_parts.join(", ")} }" unless req_parts.empty? 143 | end 144 | 145 | if response_enabled 146 | options << "response: { enabled: #{response_enabled} }" 147 | end 148 | 149 | %Q( 150 | mutation SetInterceptOptions { 151 | setInterceptOptions(input: { #{options.join(", ")} }) { 152 | options { 153 | request { 154 | enabled 155 | inScopeOnly 156 | } 157 | response { 158 | enabled 159 | } 160 | } 161 | } 162 | } 163 | ) 164 | end 165 | 166 | # Delete intercept entries 167 | def self.delete_entries(filter : String? = nil) 168 | filter_clause = filter ? %Q(filter: "#{filter}") : "" 169 | 170 | %Q( 171 | mutation DeleteInterceptEntries { 172 | deleteInterceptEntries(#{filter_clause}) { 173 | deletedCount 174 | } 175 | } 176 | ) 177 | end 178 | end 179 | 180 | module Scopes 181 | # Create a new scope 182 | def self.create(name : String, allowlist : Array(String), denylist : Array(String) = [] of String) 183 | allowlist_str = allowlist.map { |item| %Q("#{item}") }.join(", ") 184 | denylist_str = denylist.map { |item| %Q("#{item}") }.join(", ") 185 | 186 | %Q( 187 | mutation CreateScope { 188 | createScope(input: { 189 | name: "#{name}", 190 | allowlist: [#{allowlist_str}], 191 | denylist: [#{denylist_str}] 192 | }) { 193 | scope { 194 | id 195 | name 196 | allowlist 197 | denylist 198 | } 199 | } 200 | } 201 | ) 202 | end 203 | 204 | # Update a scope 205 | def self.update(scope_id : String, name : String? = nil, allowlist : Array(String)? = nil, denylist : Array(String)? = nil) 206 | updates = [] of String 207 | updates << %Q(name: "#{name}") if name 208 | 209 | if allowlist 210 | allowlist_str = allowlist.map { |item| %Q("#{item}") }.join(", ") 211 | updates << %Q(allowlist: [#{allowlist_str}]) 212 | end 213 | 214 | if denylist 215 | denylist_str = denylist.map { |item| %Q("#{item}") }.join(", ") 216 | updates << %Q(denylist: [#{denylist_str}]) 217 | end 218 | 219 | %Q( 220 | mutation UpdateScope { 221 | updateScope(id: "#{scope_id}", input: { #{updates.join(", ")} }) { 222 | scope { 223 | id 224 | name 225 | allowlist 226 | denylist 227 | } 228 | } 229 | } 230 | ) 231 | end 232 | 233 | # Delete a scope 234 | def self.delete(scope_id : String) 235 | %Q( 236 | mutation DeleteScope { 237 | deleteScope(id: "#{scope_id}") { 238 | deletedId 239 | } 240 | } 241 | ) 242 | end 243 | 244 | # Rename a scope 245 | def self.rename(scope_id : String, name : String) 246 | %Q( 247 | mutation RenameScope { 248 | renameScope(id: "#{scope_id}", name: "#{name}") { 249 | scope { 250 | id 251 | name 252 | } 253 | } 254 | } 255 | ) 256 | end 257 | end 258 | 259 | module Findings 260 | # Create a finding 261 | def self.create(request_id : String, title : String, description : String? = nil, reporter : String = "Manual") 262 | desc_clause = description ? %Q(description: "#{description}") : "" 263 | 264 | %Q( 265 | mutation CreateFinding { 266 | createFinding(requestId: "#{request_id}", input: { 267 | title: "#{title}", 268 | #{desc_clause} 269 | reporter: "#{reporter}" 270 | }) { 271 | finding { 272 | id 273 | title 274 | description 275 | reporter 276 | } 277 | } 278 | } 279 | ) 280 | end 281 | 282 | # Update a finding 283 | def self.update(finding_id : String, title : String? = nil, description : String? = nil) 284 | updates = [] of String 285 | updates << %Q(title: "#{title}") if title 286 | updates << %Q(description: "#{description}") if description 287 | 288 | %Q( 289 | mutation UpdateFinding { 290 | updateFinding(id: "#{finding_id}", input: { #{updates.join(", ")} }) { 291 | finding { 292 | id 293 | title 294 | description 295 | } 296 | } 297 | } 298 | ) 299 | end 300 | 301 | # Delete findings 302 | def self.delete(finding_ids : Array(String)? = nil) 303 | ids_clause = if finding_ids 304 | ids_str = finding_ids.map { |id| %Q("#{id}") }.join(", ") 305 | %Q(input: { ids: [#{ids_str}] }) 306 | else 307 | "" 308 | end 309 | 310 | %Q( 311 | mutation DeleteFindings { 312 | deleteFindings(#{ids_clause}) { 313 | deletedCount 314 | } 315 | } 316 | ) 317 | end 318 | 319 | # Hide findings 320 | def self.hide(finding_ids : Array(String)) 321 | ids_str = finding_ids.map { |id| %Q("#{id}") }.join(", ") 322 | 323 | %Q( 324 | mutation HideFindings { 325 | hideFindings(input: { ids: [#{ids_str}] }) { 326 | findings { 327 | id 328 | } 329 | } 330 | } 331 | ) 332 | end 333 | 334 | # Export findings 335 | def self.export(finding_ids : Array(String)? = nil) 336 | ids_clause = if finding_ids 337 | ids_str = finding_ids.map { |id| %Q("#{id}") }.join(", ") 338 | %Q(ids: [#{ids_str}]) 339 | else 340 | "" 341 | end 342 | 343 | %Q( 344 | mutation ExportFindings { 345 | exportFindings(input: { #{ids_clause} }) { 346 | export { 347 | id 348 | name 349 | path 350 | } 351 | } 352 | } 353 | ) 354 | end 355 | end 356 | 357 | module Projects 358 | # Select a project (requires cloud) 359 | def self.select(project_id : String) 360 | %Q( 361 | mutation SelectProject { 362 | selectProject(id: "#{project_id}") { 363 | project { 364 | id 365 | name 366 | path 367 | } 368 | } 369 | } 370 | ) 371 | end 372 | 373 | # Create a project (requires cloud) 374 | def self.create(name : String, path : String? = nil) 375 | path_clause = path ? %Q(path: "#{path}") : "" 376 | 377 | %Q( 378 | mutation CreateProject { 379 | createProject(input: { name: "#{name}", #{path_clause} }) { 380 | project { 381 | id 382 | name 383 | path 384 | } 385 | } 386 | } 387 | ) 388 | end 389 | 390 | # Delete a project (requires cloud) 391 | def self.delete(project_id : String) 392 | %Q( 393 | mutation DeleteProject { 394 | deleteProject(id: "#{project_id}") { 395 | deletedId 396 | } 397 | } 398 | ) 399 | end 400 | 401 | # Rename a project (requires cloud) 402 | def self.rename(project_id : String, name : String) 403 | %Q( 404 | mutation RenameProject { 405 | renameProject(id: "#{project_id}", name: "#{name}") { 406 | project { 407 | id 408 | name 409 | } 410 | } 411 | } 412 | ) 413 | end 414 | end 415 | 416 | module Workflows 417 | # Create a workflow (requires cloud) 418 | def self.create(name : String, kind : String, definition : String) 419 | %Q( 420 | mutation CreateWorkflow { 421 | createWorkflow(input: { 422 | name: "#{name}", 423 | kind: #{kind}, 424 | definition: "#{definition}" 425 | }) { 426 | workflow { 427 | id 428 | name 429 | kind 430 | definition 431 | } 432 | } 433 | } 434 | ) 435 | end 436 | 437 | # Update a workflow 438 | def self.update(workflow_id : String, name : String? = nil, definition : String? = nil) 439 | updates = [] of String 440 | updates << %Q(name: "#{name}") if name 441 | updates << %Q(definition: "#{definition}") if definition 442 | 443 | %Q( 444 | mutation UpdateWorkflow { 445 | updateWorkflow(id: "#{workflow_id}", input: { #{updates.join(", ")} }) { 446 | workflow { 447 | id 448 | name 449 | definition 450 | } 451 | } 452 | } 453 | ) 454 | end 455 | 456 | # Delete a workflow 457 | def self.delete(workflow_id : String) 458 | %Q( 459 | mutation DeleteWorkflow { 460 | deleteWorkflow(id: "#{workflow_id}") { 461 | deletedId 462 | } 463 | } 464 | ) 465 | end 466 | 467 | # Rename a workflow 468 | def self.rename(workflow_id : String, name : String) 469 | %Q( 470 | mutation RenameWorkflow { 471 | renameWorkflow(id: "#{workflow_id}", name: "#{name}") { 472 | workflow { 473 | id 474 | name 475 | } 476 | } 477 | } 478 | ) 479 | end 480 | 481 | # Toggle workflow enabled state 482 | def self.toggle(workflow_id : String, enabled : Bool) 483 | %Q( 484 | mutation ToggleWorkflow { 485 | toggleWorkflow(id: "#{workflow_id}", enabled: #{enabled}) { 486 | workflow { 487 | id 488 | enabled 489 | } 490 | } 491 | } 492 | ) 493 | end 494 | 495 | # Run an active workflow 496 | def self.run_active(workflow_id : String, request_id : String) 497 | %Q( 498 | mutation RunActiveWorkflow { 499 | runActiveWorkflow(id: "#{workflow_id}", input: { requestId: "#{request_id}" }) { 500 | result { 501 | __typename 502 | } 503 | } 504 | } 505 | ) 506 | end 507 | end 508 | 509 | module Replay 510 | # Create a replay session 511 | def self.create_session(name : String, source : String, collection_id : String? = nil) 512 | collection_clause = collection_id ? %Q(collectionId: "#{collection_id}") : "" 513 | 514 | %Q( 515 | mutation CreateReplaySession { 516 | createReplaySession(input: { 517 | name: "#{name}", 518 | source: "#{source}", 519 | #{collection_clause} 520 | }) { 521 | session { 522 | id 523 | name 524 | } 525 | } 526 | } 527 | ) 528 | end 529 | 530 | # Create a replay session collection 531 | def self.create_collection(name : String) 532 | %Q( 533 | mutation CreateReplaySessionCollection { 534 | createReplaySessionCollection(input: { name: "#{name}" }) { 535 | collection { 536 | id 537 | name 538 | } 539 | } 540 | } 541 | ) 542 | end 543 | 544 | # Delete replay sessions 545 | def self.delete_sessions(session_ids : Array(String)) 546 | ids_str = session_ids.map { |id| %Q("#{id}") }.join(", ") 547 | 548 | %Q( 549 | mutation DeleteReplaySessions { 550 | deleteReplaySessions(ids: [#{ids_str}]) { 551 | deletedIds 552 | } 553 | } 554 | ) 555 | end 556 | 557 | # Delete a replay session collection 558 | def self.delete_collection(collection_id : String) 559 | %Q( 560 | mutation DeleteReplaySessionCollection { 561 | deleteReplaySessionCollection(id: "#{collection_id}") { 562 | deletedId 563 | } 564 | } 565 | ) 566 | end 567 | 568 | # Rename a replay session 569 | def self.rename_session(session_id : String, name : String) 570 | %Q( 571 | mutation RenameReplaySession { 572 | renameReplaySession(id: "#{session_id}", name: "#{name}") { 573 | session { 574 | id 575 | name 576 | } 577 | } 578 | } 579 | ) 580 | end 581 | 582 | # Rename a replay session collection 583 | def self.rename_collection(collection_id : String, name : String) 584 | %Q( 585 | mutation RenameReplaySessionCollection { 586 | renameReplaySessionCollection(id: "#{collection_id}", name: "#{name}") { 587 | collection { 588 | id 589 | name 590 | } 591 | } 592 | } 593 | ) 594 | end 595 | 596 | # Move a replay session to a collection 597 | def self.move_session(session_id : String, collection_id : String) 598 | %Q( 599 | mutation MoveReplaySession { 600 | moveReplaySession(id: "#{session_id}", collectionId: "#{collection_id}") { 601 | session { 602 | id 603 | collection { 604 | id 605 | name 606 | } 607 | } 608 | } 609 | } 610 | ) 611 | end 612 | 613 | # Start a replay task (requires cloud) 614 | def self.start_task(session_id : String) 615 | %Q( 616 | mutation StartReplayTask { 617 | startReplayTask(sessionId: "#{session_id}", input: {}) { 618 | task { 619 | id 620 | } 621 | } 622 | } 623 | ) 624 | end 625 | end 626 | 627 | module Automate 628 | # Create an automate session 629 | def self.create_session(name : String, host : String, port : Int32, is_tls : Bool = false) 630 | %Q( 631 | mutation CreateAutomateSession { 632 | createAutomateSession(input: { 633 | name: "#{name}", 634 | connection: { 635 | host: "#{host}", 636 | port: #{port}, 637 | isTls: #{is_tls} 638 | } 639 | }) { 640 | session { 641 | id 642 | name 643 | connection { 644 | host 645 | port 646 | isTls 647 | } 648 | } 649 | } 650 | } 651 | ) 652 | end 653 | 654 | # Delete an automate session 655 | def self.delete_session(session_id : String) 656 | %Q( 657 | mutation DeleteAutomateSession { 658 | deleteAutomateSession(id: "#{session_id}") { 659 | deletedId 660 | } 661 | } 662 | ) 663 | end 664 | 665 | # Rename an automate session 666 | def self.rename_session(session_id : String, name : String) 667 | %Q( 668 | mutation RenameAutomateSession { 669 | renameAutomateSession(id: "#{session_id}", name: "#{name}") { 670 | session { 671 | id 672 | name 673 | } 674 | } 675 | } 676 | ) 677 | end 678 | 679 | # Duplicate an automate session 680 | def self.duplicate_session(session_id : String) 681 | %Q( 682 | mutation DuplicateAutomateSession { 683 | duplicateAutomateSession(id: "#{session_id}") { 684 | session { 685 | id 686 | name 687 | } 688 | } 689 | } 690 | ) 691 | end 692 | 693 | # Start an automate task 694 | def self.start_task(session_id : String) 695 | %Q( 696 | mutation StartAutomateTask { 697 | startAutomateTask(automateSessionId: "#{session_id}") { 698 | task { 699 | id 700 | } 701 | } 702 | } 703 | ) 704 | end 705 | 706 | # Pause an automate task 707 | def self.pause_task(task_id : String) 708 | %Q( 709 | mutation PauseAutomateTask { 710 | pauseAutomateTask(id: "#{task_id}") { 711 | task { 712 | id 713 | paused 714 | } 715 | } 716 | } 717 | ) 718 | end 719 | 720 | # Resume an automate task 721 | def self.resume_task(task_id : String) 722 | %Q( 723 | mutation ResumeAutomateTask { 724 | resumeAutomateTask(id: "#{task_id}") { 725 | task { 726 | id 727 | paused 728 | } 729 | } 730 | } 731 | ) 732 | end 733 | 734 | # Cancel an automate task 735 | def self.cancel_task(task_id : String) 736 | %Q( 737 | mutation CancelAutomateTask { 738 | cancelAutomateTask(id: "#{task_id}") { 739 | success 740 | } 741 | } 742 | ) 743 | end 744 | 745 | # Delete automate entries 746 | def self.delete_entries(entry_ids : Array(String)) 747 | ids_str = entry_ids.map { |id| %Q("#{id}") }.join(", ") 748 | 749 | %Q( 750 | mutation DeleteAutomateEntries { 751 | deleteAutomateEntries(ids: [#{ids_str}]) { 752 | deletedIds 753 | } 754 | } 755 | ) 756 | end 757 | end 758 | 759 | module Tamper 760 | # Create a tamper rule 761 | def self.create_rule(name : String, condition : String, strategy : String, collection_id : String? = nil) 762 | collection_clause = collection_id ? %Q(collectionId: "#{collection_id}") : "" 763 | 764 | %Q( 765 | mutation CreateTamperRule { 766 | createTamperRule(input: { 767 | name: "#{name}", 768 | condition: "#{condition}", 769 | strategy: "#{strategy}", 770 | #{collection_clause} 771 | }) { 772 | rule { 773 | id 774 | name 775 | enabled 776 | condition 777 | strategy 778 | } 779 | } 780 | } 781 | ) 782 | end 783 | 784 | # Create a tamper rule collection 785 | def self.create_collection(name : String) 786 | %Q( 787 | mutation CreateTamperRuleCollection { 788 | createTamperRuleCollection(input: { name: "#{name}" }) { 789 | collection { 790 | id 791 | name 792 | } 793 | } 794 | } 795 | ) 796 | end 797 | 798 | # Update a tamper rule 799 | def self.update_rule(rule_id : String, name : String? = nil, condition : String? = nil, strategy : String? = nil) 800 | updates = [] of String 801 | updates << %Q(name: "#{name}") if name 802 | updates << %Q(condition: "#{condition}") if condition 803 | updates << %Q(strategy: "#{strategy}") if strategy 804 | 805 | %Q( 806 | mutation UpdateTamperRule { 807 | updateTamperRule(id: "#{rule_id}", input: { #{updates.join(", ")} }) { 808 | rule { 809 | id 810 | name 811 | condition 812 | strategy 813 | } 814 | } 815 | } 816 | ) 817 | end 818 | 819 | # Delete a tamper rule 820 | def self.delete_rule(rule_id : String) 821 | %Q( 822 | mutation DeleteTamperRule { 823 | deleteTamperRule(id: "#{rule_id}") { 824 | deletedId 825 | } 826 | } 827 | ) 828 | end 829 | 830 | # Delete a tamper rule collection 831 | def self.delete_collection(collection_id : String) 832 | %Q( 833 | mutation DeleteTamperRuleCollection { 834 | deleteTamperRuleCollection(id: "#{collection_id}") { 835 | deletedId 836 | } 837 | } 838 | ) 839 | end 840 | 841 | # Toggle tamper rule enabled state 842 | def self.toggle_rule(rule_id : String, enabled : Bool) 843 | %Q( 844 | mutation ToggleTamperRule { 845 | toggleTamperRule(id: "#{rule_id}", enabled: #{enabled}) { 846 | rule { 847 | id 848 | enabled 849 | } 850 | } 851 | } 852 | ) 853 | end 854 | 855 | # Rename a tamper rule 856 | def self.rename_rule(rule_id : String, name : String) 857 | %Q( 858 | mutation RenameTamperRule { 859 | renameTamperRule(id: "#{rule_id}", name: "#{name}") { 860 | rule { 861 | id 862 | name 863 | } 864 | } 865 | } 866 | ) 867 | end 868 | 869 | # Rename a tamper rule collection 870 | def self.rename_collection(collection_id : String, name : String) 871 | %Q( 872 | mutation RenameTamperRuleCollection { 873 | renameTamperRuleCollection(id: "#{collection_id}", name: "#{name}") { 874 | collection { 875 | id 876 | name 877 | } 878 | } 879 | } 880 | ) 881 | end 882 | 883 | # Move a tamper rule to a collection 884 | def self.move_rule(rule_id : String, collection_id : String) 885 | %Q( 886 | mutation MoveTamperRule { 887 | moveTamperRule(id: "#{rule_id}", collectionId: "#{collection_id}") { 888 | rule { 889 | id 890 | collection { 891 | id 892 | name 893 | } 894 | } 895 | } 896 | } 897 | ) 898 | end 899 | end 900 | 901 | module DNS 902 | # Create a DNS rewrite 903 | def self.create_rewrite(name : String, strategy : String, source : String, destination : String) 904 | %Q( 905 | mutation CreateDNSRewrite { 906 | createDnsRewrite(input: { 907 | name: "#{name}", 908 | strategy: #{strategy}, 909 | source: "#{source}", 910 | destination: "#{destination}" 911 | }) { 912 | rewrite { 913 | id 914 | name 915 | enabled 916 | strategy 917 | source 918 | destination 919 | } 920 | } 921 | } 922 | ) 923 | end 924 | 925 | # Update a DNS rewrite 926 | def self.update_rewrite(rewrite_id : String, name : String? = nil, strategy : String? = nil, source : String? = nil, destination : String? = nil) 927 | updates = [] of String 928 | updates << %Q(name: "#{name}") if name 929 | updates << %Q(strategy: #{strategy}) if strategy 930 | updates << %Q(source: "#{source}") if source 931 | updates << %Q(destination: "#{destination}") if destination 932 | 933 | %Q( 934 | mutation UpdateDNSRewrite { 935 | updateDnsRewrite(id: "#{rewrite_id}", input: { #{updates.join(", ")} }) { 936 | rewrite { 937 | id 938 | name 939 | strategy 940 | source 941 | destination 942 | } 943 | } 944 | } 945 | ) 946 | end 947 | 948 | # Delete a DNS rewrite 949 | def self.delete_rewrite(rewrite_id : String) 950 | %Q( 951 | mutation DeleteDNSRewrite { 952 | deleteDnsRewrite(id: "#{rewrite_id}") { 953 | deletedId 954 | } 955 | } 956 | ) 957 | end 958 | 959 | # Toggle DNS rewrite enabled state 960 | def self.toggle_rewrite(rewrite_id : String, enabled : Bool) 961 | %Q( 962 | mutation ToggleDNSRewrite { 963 | toggleDnsRewrite(id: "#{rewrite_id}", enabled: #{enabled}) { 964 | rewrite { 965 | id 966 | enabled 967 | } 968 | } 969 | } 970 | ) 971 | end 972 | 973 | # Create a DNS upstream 974 | def self.create_upstream(name : String, kind : String, address : String) 975 | %Q( 976 | mutation CreateDNSUpstream { 977 | createDnsUpstream(input: { 978 | name: "#{name}", 979 | kind: #{kind}, 980 | address: "#{address}" 981 | }) { 982 | upstream { 983 | id 984 | name 985 | kind 986 | address 987 | } 988 | } 989 | } 990 | ) 991 | end 992 | 993 | # Update a DNS upstream 994 | def self.update_upstream(upstream_id : String, name : String? = nil, kind : String? = nil, address : String? = nil) 995 | updates = [] of String 996 | updates << %Q(name: "#{name}") if name 997 | updates << %Q(kind: #{kind}) if kind 998 | updates << %Q(address: "#{address}") if address 999 | 1000 | %Q( 1001 | mutation UpdateDNSUpstream { 1002 | updateDnsUpstream(id: "#{upstream_id}", input: { #{updates.join(", ")} }) { 1003 | upstream { 1004 | id 1005 | name 1006 | kind 1007 | address 1008 | } 1009 | } 1010 | } 1011 | ) 1012 | end 1013 | 1014 | # Delete a DNS upstream 1015 | def self.delete_upstream(upstream_id : String) 1016 | %Q( 1017 | mutation DeleteDNSUpstream { 1018 | deleteDnsUpstream(id: "#{upstream_id}") { 1019 | deletedId 1020 | } 1021 | } 1022 | ) 1023 | end 1024 | end 1025 | 1026 | module UpstreamProxies 1027 | # Create an HTTP upstream proxy 1028 | def self.create_http(kind : String, address : String, allowlist : Array(String) = [] of String, denylist : Array(String) = [] of String) 1029 | allowlist_str = allowlist.map { |item| %Q("#{item}") }.join(", ") 1030 | denylist_str = denylist.map { |item| %Q("#{item}") }.join(", ") 1031 | 1032 | %Q( 1033 | mutation CreateUpstreamProxyHttp { 1034 | createUpstreamProxyHttp(input: { 1035 | kind: #{kind}, 1036 | address: "#{address}", 1037 | allowlist: [#{allowlist_str}], 1038 | denylist: [#{denylist_str}] 1039 | }) { 1040 | proxy { 1041 | id 1042 | kind 1043 | address 1044 | enabled 1045 | } 1046 | } 1047 | } 1048 | ) 1049 | end 1050 | 1051 | # Create a SOCKS upstream proxy 1052 | def self.create_socks(kind : String, address : String, allowlist : Array(String) = [] of String, denylist : Array(String) = [] of String) 1053 | allowlist_str = allowlist.map { |item| %Q("#{item}") }.join(", ") 1054 | denylist_str = denylist.map { |item| %Q("#{item}") }.join(", ") 1055 | 1056 | %Q( 1057 | mutation CreateUpstreamProxySocks { 1058 | createUpstreamProxySocks(input: { 1059 | kind: #{kind}, 1060 | address: "#{address}", 1061 | allowlist: [#{allowlist_str}], 1062 | denylist: [#{denylist_str}] 1063 | }) { 1064 | proxy { 1065 | id 1066 | kind 1067 | address 1068 | enabled 1069 | } 1070 | } 1071 | } 1072 | ) 1073 | end 1074 | 1075 | # Delete an HTTP upstream proxy 1076 | def self.delete_http(proxy_id : String) 1077 | %Q( 1078 | mutation DeleteUpstreamProxyHttp { 1079 | deleteUpstreamProxyHttp(id: "#{proxy_id}") { 1080 | deletedId 1081 | } 1082 | } 1083 | ) 1084 | end 1085 | 1086 | # Delete a SOCKS upstream proxy 1087 | def self.delete_socks(proxy_id : String) 1088 | %Q( 1089 | mutation DeleteUpstreamProxySocks { 1090 | deleteUpstreamProxySocks(id: "#{proxy_id}") { 1091 | deletedId 1092 | } 1093 | } 1094 | ) 1095 | end 1096 | 1097 | # Toggle HTTP upstream proxy enabled state 1098 | def self.toggle_http(proxy_id : String, enabled : Bool) 1099 | %Q( 1100 | mutation ToggleUpstreamProxyHttp { 1101 | toggleUpstreamProxyHttp(id: "#{proxy_id}", enabled: #{enabled}) { 1102 | proxy { 1103 | id 1104 | enabled 1105 | } 1106 | } 1107 | } 1108 | ) 1109 | end 1110 | 1111 | # Toggle SOCKS upstream proxy enabled state 1112 | def self.toggle_socks(proxy_id : String, enabled : Bool) 1113 | %Q( 1114 | mutation ToggleUpstreamProxySocks { 1115 | toggleUpstreamProxySocks(id: "#{proxy_id}", enabled: #{enabled}) { 1116 | proxy { 1117 | id 1118 | enabled 1119 | } 1120 | } 1121 | } 1122 | ) 1123 | end 1124 | end 1125 | 1126 | module Assistant 1127 | # Create an assistant session (requires cloud) 1128 | def self.create_session(model_id : String, name : String? = nil) 1129 | name_clause = name ? %Q(name: "#{name}") : "" 1130 | 1131 | %Q( 1132 | mutation CreateAssistantSession { 1133 | createAssistantSession(input: { modelId: "#{model_id}", #{name_clause} }) { 1134 | session { 1135 | id 1136 | name 1137 | modelId 1138 | } 1139 | } 1140 | } 1141 | ) 1142 | end 1143 | 1144 | # Delete an assistant session 1145 | def self.delete_session(session_id : String) 1146 | %Q( 1147 | mutation DeleteAssistantSession { 1148 | deleteAssistantSession(id: "#{session_id}") { 1149 | deletedId 1150 | } 1151 | } 1152 | ) 1153 | end 1154 | 1155 | # Rename an assistant session 1156 | def self.rename_session(session_id : String, name : String) 1157 | %Q( 1158 | mutation RenameAssistantSession { 1159 | renameAssistantSession(id: "#{session_id}", name: "#{name}") { 1160 | session { 1161 | id 1162 | name 1163 | } 1164 | } 1165 | } 1166 | ) 1167 | end 1168 | 1169 | # Send a message to the assistant (requires cloud) 1170 | def self.send_message(session_id : String, message : String) 1171 | %Q( 1172 | mutation SendAssistantMessage { 1173 | sendAssistantMessage(sessionId: "#{session_id}", message: "#{message}") { 1174 | message { 1175 | id 1176 | role 1177 | content 1178 | } 1179 | } 1180 | } 1181 | ) 1182 | end 1183 | end 1184 | 1185 | module Authentication 1186 | # Start authentication flow (requires cloud) 1187 | def self.start_flow 1188 | %Q( 1189 | mutation StartAuthenticationFlow { 1190 | startAuthenticationFlow { 1191 | requestId 1192 | } 1193 | } 1194 | ) 1195 | end 1196 | 1197 | # Login as guest (requires cloud) 1198 | def self.login_guest 1199 | %Q( 1200 | mutation LoginAsGuest { 1201 | loginAsGuest { 1202 | authenticationToken 1203 | refreshToken 1204 | } 1205 | } 1206 | ) 1207 | end 1208 | 1209 | # Logout (requires cloud) 1210 | def self.logout 1211 | %Q( 1212 | mutation Logout { 1213 | logout { 1214 | success 1215 | } 1216 | } 1217 | ) 1218 | end 1219 | end 1220 | 1221 | module Tasks 1222 | # Cancel a task 1223 | def self.cancel(task_id : String) 1224 | %Q( 1225 | mutation CancelTask { 1226 | cancelTask(id: "#{task_id}") { 1227 | success 1228 | } 1229 | } 1230 | ) 1231 | end 1232 | end 1233 | end 1234 | --------------------------------------------------------------------------------