diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..93ebe8c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,207 @@
+### NotepadPP template
+# Notepad++ backups #
+*.bak
+
+### VisualStudioCode template
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
+
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Linux template
+*~
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+# .nfs files are created when an open file is removed but is still being accessed
+.nfs*
+
+### Go template
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, built with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# Dependency directories (remove the comment below to include it)
+# vendor/
+
+### Windows template
+# Windows thumbnail cache files
+Thumbs.db
+Thumbs.db:encryptable
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+### macOS template
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+### SublimeText template
+# Cache files for Sublime Text
+*.tmlanguage.cache
+*.tmPreferences.cache
+*.stTheme.cache
+
+# Workspace files are user-specific
+*.sublime-workspace
+
+# Project files should be checked into the repository, unless a significant
+# proportion of contributors will probably not be using Sublime Text
+# *.sublime-project
+
+# SFTP configuration file
+sftp-config.json
+sftp-config-alt*.json
+
+# Package control specific files
+Package Control.last-run
+Package Control.ca-list
+Package Control.ca-bundle
+Package Control.system-ca-bundle
+Package Control.cache/
+Package Control.ca-certs/
+Package Control.merged-ca-bundle
+Package Control.user-ca-bundle
+oscrypto-ca-bundle.crt
+bh_unicode_properties.cache
+
+# Sublime-github package stores a github token in this file
+# https://packagecontrol.io/packages/sublime-github
+GitHub.sublime-settings
+
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..1c2fda5
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/gamequery-cli.iml b/.idea/gamequery-cli.iml
new file mode 100644
index 0000000..338a266
--- /dev/null
+++ b/.idea/gamequery-cli.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..44c9b61
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..9661ac7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gamequery-cli b/gamequery-cli
new file mode 100644
index 0000000..4c231ce
Binary files /dev/null and b/gamequery-cli differ
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..e4872b9
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,5 @@
+module github.com/Neo-Desktop/gamequery-cli
+
+go 1.18
+
+require github.com/wisp-gg/gamequery v0.0.0-20211108193149-8c623c640ded // indirect
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..f9a2020
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,2 @@
+github.com/wisp-gg/gamequery v0.0.0-20211108193149-8c623c640ded h1:g4gxZ/7DDLqI0l6nYubEeBSZnYO8QQ/NnKgkpI/Gwzg=
+github.com/wisp-gg/gamequery v0.0.0-20211108193149-8c623c640ded/go.mod h1:4ZdFgc6g8gMu5f3kuJJTf6ZZil3YwxQ4erZvDsfZYLA=
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..ca13ea8
--- /dev/null
+++ b/main.go
@@ -0,0 +1,121 @@
+package main
+
+import (
+ "encoding/json"
+ "flag"
+ "fmt"
+ "os"
+ "strconv"
+ "time"
+
+ "github.com/wisp-gg/gamequery"
+ "github.com/wisp-gg/gamequery/api"
+)
+
+var (
+ IP string
+ PORT uint16
+ TIMEOUT int
+ INDENT bool
+)
+
+func init() {
+ flagIP := flag.String("ip", "", "Required: IP address to query [env: GQ_IP]")
+ flagPORT := flag.Int("port", 0, "Required: Port number to test [env: GQ_PORT]")
+ flagTIMEOUT := flag.Int("timeout", 5, "Timeout value in milliseconds [env: GQ_TIMEOUT]")
+ flagHELP := flag.Bool("help", false, "Displays this help message")
+ flagINDENT := flag.Bool("indent", false, "Should the output be indented")
+
+ envIP := os.Getenv("GQ_IP")
+ envPORT := os.Getenv("GQ_PORT")
+ envTIMEOUT := os.Getenv("GQ_TIMEOUT")
+
+ flag.Parse()
+
+ if flagIP != nil && *flagIP != "" {
+ IP = *flagIP
+ } else if envIP != "" {
+ IP = envIP
+ } else {
+ fmt.Fprintf(os.Stderr, "please specify an IP\n\n")
+ flag.Usage()
+ os.Exit(1)
+ }
+
+ if flagPORT != nil && *flagPORT > 0 {
+ PORT = uint16(*flagPORT)
+ } else if envPORT != "" {
+ PORT16, err := strconv.ParseInt(envPORT, 10, 16)
+ if err != nil || PORT16 <= 0 {
+ fmt.Fprintf(os.Stderr, "unable to process GQ_PORT (%s)\n\n", envPORT)
+ flag.Usage()
+ os.Exit(1)
+ }
+ PORT = uint16(PORT16)
+ } else {
+ fmt.Fprintf(os.Stderr, "please specify a Port\n\n")
+ flag.Usage()
+ os.Exit(1)
+ }
+
+ if flagTIMEOUT != nil && *flagTIMEOUT >= 0 {
+ TIMEOUT = *flagTIMEOUT
+ } else if envTIMEOUT != "" {
+ TIMEOUT64, err := strconv.ParseInt(envTIMEOUT, 10, 32)
+ if err != nil || TIMEOUT64 < 0 {
+ fmt.Fprintf(os.Stderr, "unable to process GQ_TIMEOUT (%s)\n\n", envPORT)
+ flag.Usage()
+ os.Exit(1)
+ }
+ TIMEOUT = int(TIMEOUT64)
+ } else {
+ fmt.Fprintf(os.Stderr, "please specify a Timeout\n\n")
+ flag.Usage()
+ os.Exit(1)
+ }
+
+ if *flagHELP {
+ flag.Usage()
+ os.Exit(1)
+ }
+
+ INDENT = *flagINDENT
+}
+
+type results struct {
+ Protocol string
+ *api.Response
+}
+
+func main() {
+ timeout := time.Duration(TIMEOUT) * time.Millisecond
+
+ res, protocol, err := gamequery.Detect(api.Request{
+ IP: IP,
+ Port: PORT,
+ Timeout: &timeout,
+ })
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "failed to query: %s\n\n", err)
+ os.Exit(2)
+ }
+
+ out := results{
+ Protocol: protocol,
+ Response: &res,
+ }
+
+ var outBytes []byte
+
+ if INDENT {
+ outBytes, err = json.MarshalIndent(out, "", " ")
+ } else {
+ outBytes, err = json.Marshal(out)
+ }
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "failed to package response: %s\n\n", err)
+ os.Exit(2)
+ }
+
+ fmt.Fprintf(os.Stdout, "%s\n\n", outBytes)
+}