diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 5699825..793d0e8 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -268,6 +268,7 @@ jobs: -DCMAKE_BUILD_TYPE=Release \ -DBUILD_QT=OFF \ -DBUILD_DAEMON=ON \ + -DBUILD_CLI=ON \ -DBUILD_TESTS=OFF \ -DUSE_UPNP=ON @@ -275,14 +276,19 @@ jobs: run: | cmake --build build -j$(nproc) strip --strip-all build/bin/trianglesd.exe + strip --strip-all build/bin/triangles-cli.exe - name: Package daemon with DLLs run: | mkdir -p daemon-dist/tor cp build/bin/trianglesd.exe daemon-dist/ + cp build/bin/triangles-cli.exe daemon-dist/ - # Copy all linked DLLs from MSYS2 - ldd build/bin/trianglesd.exe | grep '/mingw64' | awk '{print $3}' | while read dll; do + # Copy all linked DLLs from MSYS2 (covers both binaries; ldd union) + { + ldd build/bin/trianglesd.exe | grep '/mingw64' | awk '{print $3}' + ldd build/bin/triangles-cli.exe | grep '/mingw64' | awk '{print $3}' + } | sort -u | while read dll; do cp "$dll" daemon-dist/ 2>/dev/null || true done @@ -454,6 +460,7 @@ jobs: -DCMAKE_BUILD_TYPE=Release \ -DBUILD_QT=OFF \ -DBUILD_DAEMON=ON \ + -DBUILD_CLI=ON \ -DBUILD_TESTS=OFF \ -DUSE_UPNP=ON @@ -461,7 +468,9 @@ jobs: run: cmake --build build -j$(nproc) - name: Strip binary - run: strip --strip-all build/bin/trianglesd + run: | + strip --strip-all build/bin/trianglesd + strip --strip-all build/bin/triangles-cli - name: Build .deb package (fully self-contained) run: | @@ -477,12 +486,17 @@ jobs: mkdir -p ${PKG}/etc/systemd/system cp build/bin/trianglesd ${PKG}/usr/lib/cryptographic-triangles/ + cp build/bin/triangles-cli ${PKG}/usr/lib/cryptographic-triangles/ cp tor-extract/tor/tor ${PKG}/usr/lib/cryptographic-triangles/tor/ chmod +x ${PKG}/usr/lib/cryptographic-triangles/tor/tor [ -d tor-extract/data ] && cp -r tor-extract/data ${PKG}/usr/lib/cryptographic-triangles/tor/data # Bundle ALL shared library dependencies (except glibc/kernel) - ldd build/bin/trianglesd | grep '=> /' | awk '{print $3}' | while read lib; do + # Union of ldd output from both binaries + { + ldd build/bin/trianglesd | grep '=> /' | awk '{print $3}' + ldd build/bin/triangles-cli | grep '=> /' | awk '{print $3}' + } | sort -u | while read lib; do case "$lib" in /lib/x86_64-linux-gnu/libc.so*|/lib/x86_64-linux-gnu/libm.so*|/lib/x86_64-linux-gnu/libpthread.so*|/lib/x86_64-linux-gnu/libdl.so*|/lib/x86_64-linux-gnu/librt.so*|/lib/x86_64-linux-gnu/ld-linux*|/lib64/ld-linux*) ;; # Skip glibc core — always present @@ -495,7 +509,7 @@ jobs: ls ${PKG}/usr/lib/cryptographic-triangles/lib/ | wc -l ls ${PKG}/usr/lib/cryptographic-triangles/lib/ - # Launcher with LD_LIBRARY_PATH + # Launchers with LD_LIBRARY_PATH cat > ${PKG}/usr/bin/trianglesd << 'LAUNCHER' #!/bin/bash INSTALL_DIR=/usr/lib/cryptographic-triangles @@ -505,6 +519,15 @@ jobs: sed -i 's/^ //' ${PKG}/usr/bin/trianglesd chmod +x ${PKG}/usr/bin/trianglesd + cat > ${PKG}/usr/bin/triangles-cli << 'LAUNCHER' + #!/bin/bash + INSTALL_DIR=/usr/lib/cryptographic-triangles + export LD_LIBRARY_PATH="${INSTALL_DIR}/lib:${LD_LIBRARY_PATH}" + exec "${INSTALL_DIR}/triangles-cli" "$@" + LAUNCHER + sed -i 's/^ //' ${PKG}/usr/bin/triangles-cli + chmod +x ${PKG}/usr/bin/triangles-cli + cat > ${PKG}/etc/systemd/system/trianglesd.service << 'SVC' [Unit] Description=Cryptographic Triangles Daemon @@ -528,9 +551,9 @@ jobs: Version: ${VERSION} Architecture: amd64 Maintainer: Cryptographic Triangles - Description: Cryptographic Triangles daemon with integrated Tor - Fully self-contained headless node with all libraries, Tor, and systemd service. - No external dependencies required — runs on any x86_64 Linux. + Description: Cryptographic Triangles daemon + CLI with integrated Tor + Fully self-contained headless node + JSON-RPC client with all libraries, + Tor, and systemd service. No external dependencies required. Section: finance Priority: optional CTRL @@ -540,9 +563,10 @@ jobs: #!/bin/bash systemctl daemon-reload echo "" - echo "Cryptographic Triangles daemon installed." - echo " Start: sudo systemctl start trianglesd" - echo " On boot: sudo systemctl enable trianglesd" + echo "Cryptographic Triangles daemon + CLI installed." + echo " Start daemon: sudo systemctl start trianglesd" + echo " On boot: sudo systemctl enable trianglesd" + echo " Use CLI: triangles-cli getinfo" echo "" POST chmod +x ${PKG}/DEBIAN/postinst diff --git a/CMakeLists.txt b/CMakeLists.txt index c33328d..43acf1f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") # ── User-facing options ── option(BUILD_QT "Build triangles-qt (Qt5 GUI wallet)" ON) option(BUILD_DAEMON "Build trianglesd (headless daemon)" ON) +option(BUILD_CLI "Build triangles-cli (JSON-RPC client)" ON) option(BUILD_TESTS "Build test_triangles (Boost.Test unit tests)" ON) option(USE_UPNP "Enable UPnP support via miniupnpc" ON) option(USE_IPV6 "Enable IPv6 support" ON) @@ -185,6 +186,7 @@ message(STATUS "") message(STATUS "Triangles ${PROJECT_VERSION} build configuration:") message(STATUS " Build Qt GUI: ${BUILD_QT}") message(STATUS " Build daemon: ${BUILD_DAEMON}") +message(STATUS " Build CLI: ${BUILD_CLI}") message(STATUS " Build tests: ${BUILD_TESTS}") message(STATUS " UPnP: ${USE_UPNP}") message(STATUS " IPv6: ${USE_IPV6}") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cb5738f..327d341 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -247,6 +247,39 @@ if(BUILD_DAEMON) endif() endif() +# ═══════════════════════════════════════════════════════════════════════════════ +# 4b. JSON-RPC client (triangles-cli) +# +# Self-contained: only links univalue + boost::asio + boost::program_options +# + boost::filesystem + OpenSSL (for base64 / future TLS). Does NOT link +# triangles_common, wallet, or net — keeps the binary small. +# ═══════════════════════════════════════════════════════════════════════════════ +if(BUILD_CLI) + add_executable(triangles-cli + triangles-cli.cpp + ) + # Boost components used by the CLI + find_package(Boost REQUIRED COMPONENTS filesystem system) + target_link_libraries(triangles-cli + PRIVATE + json_compat + Boost::filesystem + Boost::system + ) + + if(WIN32) + set_target_properties(triangles-cli PROPERTIES SUFFIX ".exe") + # boost::asio needs ws2_32 on Windows + target_link_libraries(triangles-cli PRIVATE ws2_32) + endif() + + if(MSVC) + set_target_properties(triangles-cli PROPERTIES + VS_WINRT_COMPONENT "console" + ) + endif() +endif() + # ═══════════════════════════════════════════════════════════════════════════════ # 5. Qt5 GUI wallet (triangles-qt) # ═══════════════════════════════════════════════════════════════════════════════ diff --git a/src/triangles-cli.cpp b/src/triangles-cli.cpp new file mode 100644 index 0000000..fe4ad67 --- /dev/null +++ b/src/triangles-cli.cpp @@ -0,0 +1,552 @@ +// Copyright (c) 2014-2026 The Cryptographic Triangles developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +// +// triangles-cli — JSON-RPC client for trianglesd. +// +// Talks to a running trianglesd over HTTP/1.1 with HTTP Basic auth and +// JSON-RPC 1.0. Patterned after bitcoin-cli (Bitcoin Core) and dash-cli. +// +// Build with -DBUILD_CLI=ON (default ON). +// +// Self-contained: does NOT link util.cpp / wallet.cpp / net.cpp / triangles_common. +// Only links json_compat (nlohmann/json via json_spirit shim), boost (asio + +// program_options + filesystem + system), and OpenSSL (for base64). +// This keeps the CLI binary small (~600 KB stripped on Linux, ~1.5 MB on Windows). +// +// Connection parameters (highest precedence first): +// 1. Command line flags: -rpcuser/-rpcpassword/-rpcconnect/-rpcport +// 2. triangles.conf in the data directory (or -conf=) +// 3. Defaults: 127.0.0.1:19111 mainnet, 19112 testnet; no auth (must be set in conf) +// +// Usage: +// triangles-cli help List commands (delegates to daemon) +// triangles-cli help Help for one command +// triangles-cli getinfo Example: summary info +// triangles-cli getblockchaininfo Example: chain state +// triangles-cli getbalance Example: 0-arg call +// triangles-cli getbalance "*" 6 Example: positional args +// triangles-cli sendtoaddress 1.5 "memo" Example: mixed types +// triangles-cli -getinfo Synthesized summary from multiple RPCs +// triangles-cli -raw Print raw JSON response (no pretty-print) +// +// Any command-line arg that parses as a JSON literal (number, bool, null, +// object, array) is forwarded as that literal; otherwise it is sent as a JSON +// string. This matches bitcoin-cli semantics. + +#define TRIANGLES_CLI_VERSION "1.0.0" + +#include "json/json_compat.h" + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +namespace asio = boost::asio; +using boost::asio::ip::tcp; +namespace fs = boost::filesystem; +using namespace json_spirit; + +// ───────────────────────────────────────────────────────────────────────────── +// Minimal arg/config plumbing — self-contained, no util.cpp dep. +// ───────────────────────────────────────────────────────────────────────────── + +static map mapArgs; +static map > mapMultiArgs; + +static string GetArg(const string& key, const string& def = "") +{ + auto it = mapArgs.find(key); + return (it != mapArgs.end()) ? it->second : def; +} + +static bool GetBoolArg(const string& key, bool def) +{ + auto it = mapArgs.find(key); + if (it == mapArgs.end()) return def; + string v = it->second; + if (v.empty()) return true; + return (v != "0" && v != "false" && v != "no"); +} + +static void ReadConfigFile(const string& path) +{ + ifstream f(path); + if (!f.good()) return; + string line; + while (getline(f, line)) { + // Strip CR (Windows) and leading whitespace + if (!line.empty() && line.back() == '\r') line.pop_back(); + size_t start = line.find_first_not_of(" \t"); + if (start == string::npos) continue; + if (line[start] == '#') continue; + // Parse key = value + size_t eq = line.find('=', start); + if (eq == string::npos) continue; + string key = line.substr(start, eq - start); + string value = line.substr(eq + 1); + // Trim whitespace on both ends + auto trim = [](string& s) { + size_t a = s.find_first_not_of(" \t"); + size_t b = s.find_last_not_of(" \t"); + if (a == string::npos) { s.clear(); return; } + s = s.substr(a, b - a + 1); + }; + trim(key); + trim(value); + // Strip surrounding quotes + if (value.size() >= 2 && + ((value.front() == '"' && value.back() == '"') || + (value.front() == '\'' && value.back() == '\''))) { + value = value.substr(1, value.size() - 2); + } + string dashKey = "-" + key; + if (mapArgs.count(dashKey) == 0) { + mapArgs[dashKey] = value; + mapMultiArgs[dashKey].push_back(value); + } + } +} + +// Cross-platform default data directory (matches the daemon's path) +static fs::path GetDefaultDataDir() +{ +#ifdef WIN32 + // %APPDATA%/CryptographicTriangles + const char* appdata = getenv("APPDATA"); + if (appdata && *appdata) { + return fs::path(appdata) / "CryptographicTriangles"; + } + return fs::path("C:/CryptographicTriangles"); +#elif defined(__APPLE__) + // ~/Library/Application Support/CryptographicTriangles + const char* home = getenv("HOME"); + if (home && *home) { + return fs::path(home) / "Library/Application Support/CryptographicTriangles"; + } + return fs::path("/tmp/CryptographicTriangles"); +#else + // ~/.cryptographic-triangles (matches daemon's GetDefaultDataDir) + const char* home = getenv("HOME"); + if (home && *home) { + return fs::path(home) / ".cryptographic-triangles"; + } + return fs::path("/tmp/CryptographicTriangles"); +#endif +} + +static fs::path GetConfigFilePath() +{ + fs::path confPath = GetArg("-conf", "triangles.conf"); + if (confPath.is_absolute()) return confPath; + fs::path datadir = GetArg("-datadir", ""); + if (datadir.empty()) datadir = GetDefaultDataDir().string(); + return fs::path(datadir) / confPath; +} + +// ───────────────────────────────────────────────────────────────────────────── +// Command-line parsing +// ───────────────────────────────────────────────────────────────────────────── + +static void ParseCommandLine(int argc, char* const argv[]) +{ + mapArgs.clear(); + mapMultiArgs.clear(); + for (int i = 1; i < argc; ++i) { + string str(argv[i]); + // Bare "-" means: read remaining args from stdin + if (str == "-") { + mapMultiArgs["-"].push_back("-"); + continue; + } + string strKey, strVal; + size_t idx = str.find('='); + if (idx == string::npos) { + strKey = "-" + str; + strVal = "1"; + } else { + strKey = "-" + str.substr(0, idx); + strVal = str.substr(idx + 1); + } + mapArgs[strKey] = strVal; + mapMultiArgs[strKey].push_back(strVal); + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// RPC connection parameters +// ───────────────────────────────────────────────────────────────────────────── + +struct RPCConn { + string host = "127.0.0.1"; + string port = "19111"; + string user; + string pass; +}; + +static int AppInitRPCConn(RPCConn& conn) +{ + // Load conf file FIRST (before pulling creds) so defaults from triangles.conf + // are visible. Command-line flags (already in mapArgs) take precedence because + // ReadConfigFile only inserts when key is absent. + fs::path confPath = GetConfigFilePath(); + if (!confPath.empty()) ReadConfigFile(confPath.string()); + + bool fTestNet = GetBoolArg("-testnet", false); + conn.port = GetArg("-rpcport", fTestNet ? "19112" : "19111"); + conn.host = GetArg("-rpcconnect", "127.0.0.1"); + conn.user = GetArg("-rpcuser", ""); + conn.pass = GetArg("-rpcpassword", ""); + + if (conn.user.empty() || conn.pass.empty()) { + cerr << "triangles-cli: missing RPC credentials. Set rpcuser/rpcpassword in triangles.conf\n" + << " or pass -rpcuser= -rpcpassword= on the command line.\n" + << " (RPC config file: " << confPath.string() << ")\n"; + return 1; + } + return 0; +} + +// ───────────────────────────────────────────────────────────────────────────── +// JSON-RPC param conversion +// ───────────────────────────────────────────────────────────────────────────── + +static Value ParseCLIParam(const string& arg) +{ + if (arg.empty()) { + return Value(string("")); + } + Value v; + // Try parsing the arg as JSON. If it parses to a non-string literal, keep. + if (read_string(arg, v) && v.type() != str_type) { + return v; + } + return Value(arg); +} + +static void ParseCommandLineRPCParams(int argc, char* const argv[], + Value& method, Array& params) +{ + method = Value(string("")); + params.clear(); + int i = 1; + // Skip leading flags + static const set valFlags = { + "-conf", "-datadir", "-rpcconnect", "-rpcport", + "-rpcuser", "-rpcpassword" + }; + while (i < argc) { + string arg(argv[i]); + if (arg == "-" || arg.size() < 2 || arg[0] != '-') break; + if (valFlags.count(arg) && i + 1 < argc && + string(argv[i+1]).substr(0,1) != "-") { + i += 2; + } else { + ++i; + } + } + if (i >= argc) { + method = Value(string("help")); + return; + } + method = Value(string(argv[i])); + ++i; + while (i < argc) { + string arg(argv[i]); + if (arg == "-") { + string line; + while (getline(cin, line)) { + if (!line.empty() && line.back() == '\r') line.pop_back(); + params.push_back(ParseCLIParam(line)); + } + } else { + params.push_back(ParseCLIParam(arg)); + } + ++i; + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// Base64 (RFC 4648) — for HTTP Basic auth +// ───────────────────────────────────────────────────────────────────────────── + +static const char b64_table[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static string Base64Encode(const string& in) +{ + string out; + out.reserve(((in.size() + 2) / 3) * 4); + int val = 0, valb = -6; + for (unsigned char c : in) { + val = (val << 8) + c; + valb += 8; + while (valb >= 0) { + out.push_back(b64_table[(val >> valb) & 0x3F]); + valb -= 6; + } + } + if (valb > -6) out.push_back(b64_table[((val << 8) >> (valb + 8)) & 0x3F]); + while (out.size() % 4) out.push_back('='); + return out; +} + +// ───────────────────────────────────────────────────────────────────────────── +// HTTP/1.1 JSON-RPC POST (plaintext) +// ───────────────────────────────────────────────────────────────────────────── + +static int CallRPC(const RPCConn& conn, const string& strMethod, + const Array& params, Value& result) +{ + Object req; + req.push_back(Pair("jsonrpc", Value(string("1.0")))); + req.push_back(Pair("id", Value(string("triangles-cli")))); + req.push_back(Pair("method", Value(strMethod))); + req.push_back(Pair("params", Value(params))); + string strRequest = write_string(Value(req), false) + "\n"; + + string strAuth = Base64Encode(conn.user + ":" + conn.pass); + + asio::io_context io; + tcp::resolver resolver(io); + boost::system::error_code ec; + auto endpoints = resolver.resolve(conn.host, conn.port, ec); + if (ec) { + cerr << "triangles-cli: resolve " << conn.host << ":" << conn.port + << " failed: " << ec.message() << "\n"; + return 1; + } + + tcp::socket sock(io); + sock.connect(*endpoints.begin(), ec); + if (ec) { + cerr << "triangles-cli: connect to " << conn.host << ":" << conn.port + << " failed: " << ec.message() << "\n" + << "(is trianglesd running and accepting JSON-RPC?)\n"; + return 1; + } + + ostringstream reqStream; + reqStream << "POST / HTTP/1.1\r\n" + << "Host: " << conn.host << ":" << conn.port << "\r\n" + << "Authorization: Basic " << strAuth << "\r\n" + << "Content-Type: application/json\r\n" + << "Content-Length: " << strRequest.size() << "\r\n" + << "Connection: close\r\n" + << "\r\n" + << strRequest; + asio::streambuf requestBuf; + std::ostream os(&requestBuf); + os << reqStream.str(); + asio::write(sock, requestBuf, ec); + if (ec) { + cerr << "triangles-cli: write failed: " << ec.message() << "\n"; + return 1; + } + + asio::streambuf responseBuf; + boost::system::error_code readEc; + while (asio::read(sock, responseBuf, + asio::transfer_at_least(1), readEc)) { + // keep reading until EOF or error + } + if (readEc && readEc != asio::error::eof) { + cerr << "triangles-cli: read failed: " << readEc.message() << "\n"; + return 1; + } + + std::istream rs(&responseBuf); + string line; + if (!std::getline(rs, line)) { + cerr << "triangles-cli: empty response\n"; + return 1; + } + if (!line.empty() && line.back() == '\r') line.pop_back(); + int status = 0; + { + istringstream iss(line); + string httpVer; + iss >> httpVer >> status; + } + if (status != 200) { + cerr << "triangles-cli: server returned HTTP " << status << "\n"; + ostringstream body; + body << rs.rdbuf(); + if (!body.str().empty()) cerr << body.str() << "\n"; + return 1; + } + while (std::getline(rs, line) && line != "\r" && !line.empty()) {} + string body; + { + ostringstream oss; + oss << rs.rdbuf(); + body = oss.str(); + } + + Value reply; + if (!read_string(body, reply)) { + cerr << "triangles-cli: could not parse JSON response:\n" << body << "\n"; + return 1; + } + + if (reply.type() != obj_type) { + cerr << "triangles-cli: unexpected response (not an object):\n" + << write_string(reply, true) << "\n"; + return 1; + } + + Object replyObj = reply.get_obj(); + const Value& err = find_value(replyObj, "error"); + if (err.type() != null_type) { + cerr << "RPC error: " << write_string(err, false) << "\n"; + return 1; + } + const Value& res = find_value(replyObj, "result"); + result = res; + return 0; +} + +// ───────────────────────────────────────────────────────────────────────────── +// triangles-cli -getinfo — synthesize a friendly summary from a few RPC calls +// ───────────────────────────────────────────────────────────────────────────── + +static int Getinfo(const RPCConn& conn, bool fPretty) +{ + Object info; + Value r; + Array emptyParams; + + if (CallRPC(conn, "getnetworkinfo", emptyParams, r) == 0) { + info.push_back(Pair("network", r)); + } + if (CallRPC(conn, "getblockchaininfo", emptyParams, r) == 0) { + Object chain = r.get_obj(); + info.push_back(Pair("blockchain", r)); + info.push_back(Pair("blocks", find_value(chain, "blocks"))); + info.push_back(Pair("headers", find_value(chain, "headers"))); + info.push_back(Pair("bestblockhash", find_value(chain, "bestblockhash"))); + info.push_back(Pair("difficulty", find_value(chain, "difficulty"))); + info.push_back(Pair("verificationprogress", + find_value(chain, "verificationprogress"))); + info.push_back(Pair("chain", find_value(chain, "chain"))); + } + if (CallRPC(conn, "getwalletinfo", emptyParams, r) == 0) { + Object wal = r.get_obj(); + info.push_back(Pair("wallet", r)); + info.push_back(Pair("balance", find_value(wal, "balance"))); + } + Object connObj; + connObj.push_back(Pair("rpcconnect", Value(conn.host))); + connObj.push_back(Pair("rpcport", Value(conn.port))); + info.push_back(Pair("connection", Value(connObj))); + cout << write_string(Value(info), fPretty) << "\n"; + return 0; +} + +// ───────────────────────────────────────────────────────────────────────────── +// Help / version +// ───────────────────────────────────────────────────────────────────────────── + +static int CommandLineHelp(ostream& out) +{ + out << "Usage: triangles-cli [options] [params]\n" + << "\n" + << " triangles-cli [options] help List commands (delegates to daemon)\n" + << " triangles-cli [options] help Help for one command (delegates to daemon)\n" + << " triangles-cli -getinfo Show summary info from the daemon\n" + << "\n" + << "Options:\n" + << " -conf= Specify configuration file (default: triangles.conf)\n" + << " -datadir= Specify data directory\n" + << " -testnet Use testnet (RPC port 19112)\n" + << " -rpcconnect= Send commands to node running on (default: 127.0.0.1)\n" + << " -rpcport= Connect to JSON-RPC on (default: 19111 or testnet: 19112)\n" + << " -rpcuser= Username for JSON-RPC connections\n" + << " -rpcpassword= Password for JSON-RPC connections\n" + << " -stdin Read extra params from standard input, one per line\n" + << " -raw Print raw JSON response (no pretty-printing)\n" + << " -version Print version and exit\n" + << "\n" + << "Examples:\n" + << " triangles-cli getinfo\n" + << " triangles-cli getblockchaininfo\n" + << " triangles-cli getbalance\n" + << " triangles-cli getbalance \"*\" 6\n" + << " triangles-cli sendtoaddress
[comment]\n" + << " triangles-cli -getinfo\n" + << "\n"; + return 0; +} + +static int CommandLineVersion() +{ + cout << "triangles-cli version " << TRIANGLES_CLI_VERSION + << " (Cryptographic Triangles RPC client)\n"; + return 0; +} + +// ───────────────────────────────────────────────────────────────────────────── +// main +// ───────────────────────────────────────────────────────────────────────────── + +int main(int argc, char* argv[]) +{ + ParseCommandLine(argc, argv); + + if (argc < 2 || GetArg("-?", "") == "1" || GetArg("-h", "") == "1" || + GetArg("--help", "") == "1") { + CommandLineHelp(cerr); + return argc < 2 ? 1 : 0; + } + if (!GetArg("-version", "").empty() || !GetArg("--version", "").empty()) { + CommandLineVersion(); + return 0; + } + + RPCConn conn; + if (AppInitRPCConn(conn) != 0) return 1; + + Value method; + Array params; + ParseCommandLineRPCParams(argc, argv, method, params); + string strMethod = method.get_str(); + + if (strMethod == "help" || strMethod == "-help") { + if (params.empty()) { + CommandLineHelp(cout); + return 0; + } + // else fall through: delegate to daemon's help + } + + if (!GetArg("-getinfo", "").empty()) { + return Getinfo(conn, /*fPretty=*/true); + } + + bool fPretty = GetArg("-raw", "").empty(); + + Value result; + int nRet = CallRPC(conn, strMethod, params, result); + if (nRet == 0) { + cout << write_string(result, fPretty) << "\n"; + } + return nRet; +}