Compare commits

...

1 Commits

Author SHA1 Message Date
9c98ffcb86 docs: Update 2 files
Some checks failed
Build-Test-Publish / build (linux/amd64) (push) Failing after 48s
Build-Test-Publish / build (linux/arm64) (push) Failing after 1m13s
Build-Test-Publish / test-install-from-scratch (linux/amd64) (push) Has been skipped
Build-Test-Publish / test-install-from-scratch (linux/arm64) (push) Has been skipped
2025-07-20 17:54:42 +12:00
2 changed files with 243 additions and 105 deletions

View File

@ -85,7 +85,9 @@ Based on analysis of the current codebase, the multi-server support feature need
- Add server URL validation and user feedback - Add server URL validation and user feedback
- _Requirements: 1.1, 1.2, 1.3_ - _Requirements: 1.1, 1.2, 1.3_
- [-] 7. Update existing commands for multi-server support - [x] 7. Update existing commands for multi-server support

View File

@ -165,25 +165,47 @@ int install_tool(int argc, char* argv[]) {
std::filesystem::path configDir = std::filesystem::path(home) / ".config/getpkg"; std::filesystem::path configDir = std::filesystem::path(home) / ".config/getpkg";
std::filesystem::path binDir = std::filesystem::path(home) / ".getpkg" / toolName; std::filesystem::path binDir = std::filesystem::path(home) / ".getpkg" / toolName;
std::filesystem::path archivePath = tempDir.path() / (toolName + ".tgz"); std::filesystem::path archivePath = tempDir.path() / (toolName + ".tgz");
std::filesystem::path toolInfoPath = configDir / (toolName + ".json");
// Initialize ServerManager and get server list
ServerManager serverManager;
if (!serverManager.loadConfiguration()) {
std::cerr << "Failed to load server configuration" << std::endl;
return 1;
}
std::vector<std::string> servers = serverManager.getServers();
if (servers.empty()) {
std::cerr << "No servers configured" << std::endl;
return 1;
}
// Initialize PackageMetadataManager
PackageMetadataManager packageManager(configDir);
if (!packageManager.ensurePackagesDirectory()) {
std::cerr << "Failed to create packages directory" << std::endl;
return 1;
}
// Check if tool needs update or install // Check if tool needs update or install
if (std::filesystem::exists(toolInfoPath)) { bool isUpdate = false;
PackageMetadata existingMetadata;
if (packageManager.packageExists(toolName)) {
// Tool exists, check if update needed // Tool exists, check if update needed
std::ifstream tfile(toolInfoPath); existingMetadata = packageManager.loadPackageMetadata(toolName);
json toolInfo; if (!existingMetadata.isValid()) {
tfile >> toolInfo; std::cerr << "Warning: Invalid existing package metadata for " << toolName << std::endl;
tfile.close(); }
std::string localHash = toolInfo.value("hash", ""); std::string localHash = existingMetadata.hash;
std::string localArch = toolInfo.value("arch", arch); std::string localArch = existingMetadata.arch.empty() ? arch : existingMetadata.arch;
// Get remote hash to compare - use the same arch that was originally installed // Get remote hash to compare - use multi-server GetbinClient
GetbinClient getbin; GetbinClient getbin(servers);
std::string remoteHash; std::string remoteHash;
if (getbin.getHash(toolName, localArch, remoteHash) && !remoteHash.empty()) { if (getbin.getHash(toolName, localArch, remoteHash) && !remoteHash.empty()) {
if (localHash != remoteHash) { if (localHash != remoteHash) {
std::cout << "Updating " << toolName << "..." << std::endl; std::cout << "Updating " << toolName << "..." << std::endl;
isUpdate = true;
} else { } else {
std::cout << toolName << " is already up to date." << std::endl; std::cout << toolName << " is already up to date." << std::endl;
return 0; return 0;
@ -191,6 +213,7 @@ int install_tool(int argc, char* argv[]) {
} else { } else {
// If we can't get remote hash, assume update is needed // If we can't get remote hash, assume update is needed
std::cout << "Updating " << toolName << "..." << std::endl; std::cout << "Updating " << toolName << "..." << std::endl;
isUpdate = true;
} }
} else { } else {
std::cout << "Installing " << toolName << "..." << std::endl; std::cout << "Installing " << toolName << "..." << std::endl;
@ -210,9 +233,10 @@ int install_tool(int argc, char* argv[]) {
if (std::filesystem::exists(binDir)) if (std::filesystem::exists(binDir))
std::filesystem::remove_all(binDir); std::filesystem::remove_all(binDir);
// Download tool - try arch-specific version first, then universal fallback // Download tool using multi-server GetbinClient - try arch-specific version first, then universal fallback
GetbinClient getbin2; GetbinClient getbin2(servers);
std::string downloadArch = arch; std::string downloadArch = arch;
std::string sourceServer;
// Progress callback for downloads // Progress callback for downloads
auto progressCallback = [&toolName](size_t downloaded, size_t total) -> bool { auto progressCallback = [&toolName](size_t downloaded, size_t total) -> bool {
@ -237,6 +261,12 @@ int install_tool(int argc, char* argv[]) {
} }
clearAndPrint("Downloading " + toolName + "... done\n"); clearAndPrint("Downloading " + toolName + "... done\n");
// Find which server provided the package
if (!getbin2.findPackageServer(toolName, downloadArch, sourceServer)) {
// Fallback to first server if we can't determine the source
sourceServer = servers[0];
}
// Unpack tool // Unpack tool
std::cout << "Unpacking..." << std::flush; std::cout << "Unpacking..." << std::flush;
if (!common::unpack_tgz(archivePath.string(), binDir.string())) { if (!common::unpack_tgz(archivePath.string(), binDir.string())) {
@ -272,16 +302,11 @@ int install_tool(int argc, char* argv[]) {
std::cerr << "Warning: Failed to get version for " << toolName << std::endl; std::cerr << "Warning: Failed to get version for " << toolName << std::endl;
} }
// Save tool info // Create and save enhanced package metadata
json toolInfo = { PackageMetadata metadata(toolName, version, hash, downloadArch, sourceServer);
{"name", toolName}, if (!packageManager.savePackageMetadata(metadata)) {
{"version", version}, std::cerr << "Warning: Failed to save package metadata for " << toolName << std::endl;
{"hash", hash}, }
{"arch", downloadArch}
};
std::ofstream toolInfoFile(toolInfoPath);
toolInfoFile << toolInfo.dump(2);
toolInfoFile.close();
// Run setup script if exists // Run setup script if exists
std::filesystem::path setupScriptPath = binDir / "setup_script.sh"; std::filesystem::path setupScriptPath = binDir / "setup_script.sh";
@ -297,11 +322,27 @@ int install_tool(int argc, char* argv[]) {
int publish_tool(int argc, char* argv[]) { int publish_tool(int argc, char* argv[]) {
if (argc < 4) { if (argc < 4) {
std::cerr << "Usage: getpkg publish <tool_name:ARCH> <folder>" << std::endl; std::cerr << "Usage: getpkg publish [--server <url>] <tool_name:ARCH> <folder>" << std::endl;
std::cerr << " getpkg publish <tool_name:ARCH> <folder>" << std::endl;
return 1; return 1;
} }
std::string labeltag = argv[2];
std::string folder = argv[3]; // Parse arguments for --server option
std::string targetServer;
std::string labeltag;
std::string folder;
int argIndex = 2;
if (argc >= 5 && std::string(argv[2]) == "--server") {
targetServer = argv[3];
labeltag = argv[4];
folder = argv[5];
argIndex = 5;
} else {
labeltag = argv[2];
folder = argv[3];
argIndex = 3;
}
// If no ARCH is provided (no colon in labeltag), append ":universal" for cross-platform tools // If no ARCH is provided (no colon in labeltag), append ":universal" for cross-platform tools
if (labeltag.find(':') == std::string::npos) { if (labeltag.find(':') == std::string::npos) {
@ -316,6 +357,49 @@ int publish_tool(int argc, char* argv[]) {
} }
} }
} }
// Initialize ServerManager
ServerManager serverManager;
if (!serverManager.loadConfiguration()) {
std::cerr << "Failed to load server configuration" << std::endl;
return 1;
}
// Determine target server
std::string publishServer;
if (!targetServer.empty()) {
// User specified a server, validate it exists in configuration
std::vector<std::string> servers = serverManager.getServers();
if (std::find(servers.begin(), servers.end(), targetServer) == servers.end()) {
std::cerr << "Error: Server '" << targetServer << "' is not configured" << std::endl;
std::cerr << "Use 'getpkg server add " << targetServer << "' to add it first" << std::endl;
return 1;
}
publishServer = targetServer;
} else {
// Use default publish server (first server with write token)
publishServer = serverManager.getDefaultPublishServer();
if (publishServer.empty()) {
std::cerr << "Error: No servers with write tokens configured" << std::endl;
std::cerr << "Use 'getpkg server add <url>' and provide a write token" << std::endl;
return 1;
}
}
// Get write token for the target server
std::string token = serverManager.getWriteToken(publishServer);
if (token.empty()) {
// Check environment variable as fallback
const char* envToken = std::getenv("SOS_WRITE_TOKEN");
if (envToken && std::strlen(envToken) > 0) {
token = envToken;
} else {
std::cerr << "Error: No write token found for server '" << publishServer << "'" << std::endl;
std::cerr << "Set SOS_WRITE_TOKEN environment variable or configure token for this server" << std::endl;
return 1;
}
}
std::string home = get_home(); std::string home = get_home();
std::filesystem::path archivePath = std::filesystem::path(home) / ".tmp" / (labeltag + ".tgz"); std::filesystem::path archivePath = std::filesystem::path(home) / ".tmp" / (labeltag + ".tgz");
std::filesystem::create_directories(archivePath.parent_path()); std::filesystem::create_directories(archivePath.parent_path());
@ -324,24 +408,10 @@ int publish_tool(int argc, char* argv[]) {
std::cerr << "Failed to create archive." << std::endl; std::cerr << "Failed to create archive." << std::endl;
return 1; return 1;
} }
std::string token;
const char* envToken = std::getenv("SOS_WRITE_TOKEN"); // Initialize GetbinClient with server list
if (envToken && std::strlen(envToken) > 0) { std::vector<std::string> servers = serverManager.getServers();
token = envToken; GetbinClient getbin(servers);
} else {
std::filesystem::path tokenPath = std::filesystem::path(home) / ".config/getpkg.xyz/write_token.txt";
if (std::filesystem::exists(tokenPath)) {
std::ifstream tfile(tokenPath);
std::getline(tfile, token);
} else {
std::cout << "Enter getpkg.xyz write token: ";
std::getline(std::cin, token);
std::filesystem::create_directories(tokenPath.parent_path());
std::ofstream tfile(tokenPath);
tfile << token << std::endl;
}
}
GetbinClient getbin;
std::string url, hash; std::string url, hash;
// Progress callback for upload // Progress callback for upload
@ -355,13 +425,14 @@ int publish_tool(int argc, char* argv[]) {
return true; // Continue upload return true; // Continue upload
}; };
std::cout << "Publishing to " << publishServer << "..." << std::endl;
std::cout << "Uploading..." << std::flush; std::cout << "Uploading..." << std::flush;
if (!getbin.upload(archivePath.string(), url, hash, token, uploadProgressCallback)) { if (!getbin.upload(publishServer, archivePath.string(), url, hash, token, uploadProgressCallback)) {
std::cerr << "\rFailed to upload archive." << std::endl; std::cerr << "\rFailed to upload archive to " << publishServer << std::endl;
return 1; return 1;
} }
clearAndPrint("Uploading... done\n"); clearAndPrint("Uploading... done\n");
std::cout << "Published! URL: " << url << "\nHash: " << hash << std::endl; std::cout << "Published to " << publishServer << "! URL: " << url << "\nHash: " << hash << std::endl;
return 0; return 0;
} }
@ -369,6 +440,25 @@ int update_tool(int argc, char* argv[]) {
std::string home = get_home(); std::string home = get_home();
std::filesystem::path configDir = std::filesystem::path(home) / ".config/getpkg"; std::filesystem::path configDir = std::filesystem::path(home) / ".config/getpkg";
// Initialize ServerManager and PackageMetadataManager
ServerManager serverManager;
if (!serverManager.loadConfiguration()) {
std::cerr << "Failed to load server configuration" << std::endl;
return 1;
}
std::vector<std::string> servers = serverManager.getServers();
if (servers.empty()) {
std::cerr << "No servers configured" << std::endl;
return 1;
}
PackageMetadataManager packageManager(configDir);
if (!packageManager.ensurePackagesDirectory()) {
std::cerr << "Failed to create packages directory" << std::endl;
return 1;
}
// Structure to hold tool information // Structure to hold tool information
struct ToolInfo { struct ToolInfo {
std::string name; std::string name;
@ -376,29 +466,43 @@ int update_tool(int argc, char* argv[]) {
std::string remoteHash; std::string remoteHash;
std::string arch; std::string arch;
std::string version; std::string version;
std::string sourceServer;
bool needsUpdate = false; bool needsUpdate = false;
std::string status = "Up to date"; std::string status = "Up to date";
}; };
std::vector<ToolInfo> tools; std::vector<ToolInfo> tools;
// Collect all installed tools // Collect all installed tools using PackageMetadataManager
if (std::filesystem::exists(configDir)) { std::vector<std::string> installedPackages = packageManager.listInstalledPackages();
for (const auto& entry : std::filesystem::directory_iterator(configDir)) { for (const std::string& toolName : installedPackages) {
if (entry.path().extension() == ".json") { ToolInfo tool;
std::string tname = entry.path().stem(); tool.name = toolName;
ToolInfo tool; // Load package metadata
tool.name = tname; PackageMetadata metadata = packageManager.loadPackageMetadata(toolName);
if (metadata.isValid()) {
tool.localHash = metadata.hash;
tool.arch = metadata.arch.empty() ? get_arch() : metadata.arch;
tool.version = metadata.version;
tool.sourceServer = metadata.sourceServer;
// Read local tool info if (tool.version.empty() || tool.version == "-") {
std::ifstream tfile(entry.path()); tool.version = "installed";
}
} else {
// Fallback to legacy format if new format fails
std::filesystem::path legacyPath = configDir / (toolName + ".json");
if (std::filesystem::exists(legacyPath)) {
std::ifstream tfile(legacyPath);
if (tfile.good()) { if (tfile.good()) {
json toolInfo; json toolInfo;
tfile >> toolInfo; tfile >> toolInfo;
tool.localHash = toolInfo.value("hash", ""); tool.localHash = toolInfo.value("hash", "");
tool.arch = toolInfo.value("arch", get_arch()); tool.arch = toolInfo.value("arch", get_arch());
tool.version = toolInfo.value("version", "-"); tool.version = toolInfo.value("version", "-");
tool.sourceServer = "getpkg.xyz"; // Default for legacy
if (!tool.version.empty() && tool.version.back() == '\n') { if (!tool.version.empty() && tool.version.back() == '\n') {
tool.version.pop_back(); tool.version.pop_back();
} }
@ -406,10 +510,10 @@ int update_tool(int argc, char* argv[]) {
tool.version = "installed"; tool.version = "installed";
} }
} }
tools.push_back(tool);
} }
} }
tools.push_back(tool);
} }
if (tools.empty()) { if (tools.empty()) {
@ -420,14 +524,14 @@ int update_tool(int argc, char* argv[]) {
// Step 1: Check for updates (with progress) // Step 1: Check for updates (with progress)
std::cout << "Checking " << tools.size() << " tools for updates..." << std::endl; std::cout << "Checking " << tools.size() << " tools for updates..." << std::endl;
GetbinClient getbin; GetbinClient getbin(servers);
for (size_t i = 0; i < tools.size(); ++i) { for (size_t i = 0; i < tools.size(); ++i) {
auto& tool = tools[i]; auto& tool = tools[i];
// Show progress // Show progress
std::cout << "\r[" << (i + 1) << "/" << tools.size() << "] Checking " << tool.name << "..." << std::flush; std::cout << "\r[" << (i + 1) << "/" << tools.size() << "] Checking " << tool.name << "..." << std::flush;
// Check remote hash // Check remote hash - use multi-server fallback
std::string remoteHash; std::string remoteHash;
if (getbin.getHash(tool.name, tool.arch, remoteHash) && !remoteHash.empty()) { if (getbin.getHash(tool.name, tool.arch, remoteHash) && !remoteHash.empty()) {
tool.remoteHash = remoteHash; tool.remoteHash = remoteHash;
@ -499,16 +603,10 @@ int update_tool(int argc, char* argv[]) {
tool.status = "Updated"; tool.status = "Updated";
clearAndPrint("Updated\n"); clearAndPrint("Updated\n");
// Re-read version after update // Re-read version after update using PackageMetadataManager
std::filesystem::path toolInfoPath = configDir / (tool.name + ".json"); PackageMetadata updatedMetadata = packageManager.loadPackageMetadata(tool.name);
if (std::filesystem::exists(toolInfoPath)) { if (updatedMetadata.isValid()) {
std::ifstream tfile(toolInfoPath); tool.version = updatedMetadata.version;
json toolInfo;
tfile >> toolInfo;
tool.version = toolInfo.value("version", tool.version);
if (!tool.version.empty() && tool.version.back() == '\n') {
tool.version.pop_back();
}
if (tool.version.empty() || tool.version == "-") { if (tool.version.empty() || tool.version == "-") {
tool.version = "installed"; tool.version = "installed";
} }
@ -622,38 +720,73 @@ int hash_command(int argc, char* argv[]) {
int unpublish_tool(int argc, char* argv[]) { int unpublish_tool(int argc, char* argv[]) {
if (argc < 3) { if (argc < 3) {
std::cerr << "Usage: getpkg unpublish <tool_name[:ARCH]>" << std::endl; std::cerr << "Usage: getpkg unpublish [--server <url>] <tool_name[:ARCH]>" << std::endl;
std::cerr << " getpkg unpublish [--server <url>] <hash>" << std::endl;
std::cerr << " getpkg unpublish <tool_name[:ARCH]>" << std::endl;
std::cerr << " getpkg unpublish <hash>" << std::endl; std::cerr << " getpkg unpublish <hash>" << std::endl;
return 1; return 1;
} }
std::string target = argv[2];
// Get token // Parse arguments for --server option
std::string token; std::string targetServer;
const char* envToken = std::getenv("SOS_WRITE_TOKEN"); std::string target;
if (envToken && std::strlen(envToken) > 0) {
token = envToken; if (argc >= 4 && std::string(argv[2]) == "--server") {
} else { if (argc < 5) {
std::string home = get_home(); std::cerr << "Usage: getpkg unpublish --server <url> <tool_name[:ARCH]|hash>" << std::endl;
std::filesystem::path tokenPath = std::filesystem::path(home) / ".config/getpkg.xyz/write_token.txt"; return 1;
if (std::filesystem::exists(tokenPath)) {
std::ifstream tfile(tokenPath);
std::getline(tfile, token);
} else {
std::cout << "Enter getpkg.xyz write token: ";
std::getline(std::cin, token);
std::filesystem::create_directories(tokenPath.parent_path());
std::ofstream tfile(tokenPath);
tfile << token << std::endl;
} }
targetServer = argv[3];
target = argv[4];
} else {
target = argv[2];
} }
if (token.empty()) { // Initialize ServerManager
std::cerr << "Error: No write token provided" << std::endl; ServerManager serverManager;
if (!serverManager.loadConfiguration()) {
std::cerr << "Failed to load server configuration" << std::endl;
return 1; return 1;
} }
GetbinClient getbin; // Determine target server
std::string unpublishServer;
if (!targetServer.empty()) {
// User specified a server, validate it exists in configuration
std::vector<std::string> servers = serverManager.getServers();
if (std::find(servers.begin(), servers.end(), targetServer) == servers.end()) {
std::cerr << "Error: Server '" << targetServer << "' is not configured" << std::endl;
std::cerr << "Use 'getpkg server add " << targetServer << "' to add it first" << std::endl;
return 1;
}
unpublishServer = targetServer;
} else {
// Use default publish server (first server with write token)
unpublishServer = serverManager.getDefaultPublishServer();
if (unpublishServer.empty()) {
std::cerr << "Error: No servers with write tokens configured" << std::endl;
std::cerr << "Use 'getpkg server add <url>' and provide a write token" << std::endl;
return 1;
}
}
// Get write token for the target server
std::string token = serverManager.getWriteToken(unpublishServer);
if (token.empty()) {
// Check environment variable as fallback
const char* envToken = std::getenv("SOS_WRITE_TOKEN");
if (envToken && std::strlen(envToken) > 0) {
token = envToken;
} else {
std::cerr << "Error: No write token found for server '" << unpublishServer << "'" << std::endl;
std::cerr << "Set SOS_WRITE_TOKEN environment variable or configure token for this server" << std::endl;
return 1;
}
}
// Initialize GetbinClient with server list
std::vector<std::string> servers = serverManager.getServers();
GetbinClient getbin(servers);
std::string hash = target; std::string hash = target;
// Check if target looks like a hash (all digits) or a tool name // Check if target looks like a hash (all digits) or a tool name
@ -678,8 +811,8 @@ int unpublish_tool(int argc, char* argv[]) {
// If a specific architecture was requested, only unpublish that one // If a specific architecture was requested, only unpublish that one
if (!specificArch.empty()) { if (!specificArch.empty()) {
if (!getbin.getHash(toolName, specificArch, hash)) { if (!getbin.getHash(unpublishServer, toolName, specificArch, hash)) {
std::cerr << "Failed to get hash for " << target << std::endl; std::cerr << "Failed to get hash for " << target << " on server " << unpublishServer << std::endl;
return 1; return 1;
} }
@ -703,14 +836,14 @@ int unpublish_tool(int argc, char* argv[]) {
return 1; return 1;
} }
std::cout << "Found hash " << hash << " for " << target << std::endl; std::cout << "Found hash " << hash << " for " << target << " on " << unpublishServer << std::endl;
// Delete the specific architecture // Delete the specific architecture
if (getbin.deleteObject(hash, token)) { if (getbin.deleteObject(hash, token)) {
std::cout << "Successfully unpublished " << target << " (hash: " << hash << ")" << std::endl; std::cout << "Successfully unpublished " << target << " from " << unpublishServer << " (hash: " << hash << ")" << std::endl;
return 0; return 0;
} else { } else {
std::cerr << "Failed to unpublish " << target << std::endl; std::cerr << "Failed to unpublish " << target << " from " << unpublishServer << std::endl;
return 1; return 1;
} }
} else { } else {
@ -1105,14 +1238,15 @@ void show_help() {
std::cout << " uninstall <tool_name> Remove an installed tool" << std::endl; std::cout << " uninstall <tool_name> Remove an installed tool" << std::endl;
std::cout << " Removes tool files, PATH entries, and autocomplete" << std::endl; std::cout << " Removes tool files, PATH entries, and autocomplete" << std::endl;
std::cout << std::endl; std::cout << std::endl;
std::cout << " publish <tool_name[:ARCH]> <folder> Upload a tool to getpkg.xyz" << std::endl; std::cout << " publish [--server <url>] <tool_name[:ARCH]> <folder>" << std::endl;
std::cout << " Upload a tool to a package server" << std::endl;
std::cout << " ARCH is optional (defaults to 'universal')" << std::endl; std::cout << " ARCH is optional (defaults to 'universal')" << std::endl;
std::cout << " Requires SOS_WRITE_TOKEN environment variable" << std::endl; std::cout << " Uses default publish server if --server not specified" << std::endl;
std::cout << std::endl; std::cout << std::endl;
std::cout << " unpublish <tool_name> Remove ALL architectures of a tool" << std::endl; std::cout << " unpublish [--server <url>] <tool_name> Remove ALL architectures of a tool" << std::endl;
std::cout << " unpublish <tool_name:ARCH> Remove specific architecture only" << std::endl; std::cout << " unpublish [--server <url>] <tool_name:ARCH> Remove specific architecture only" << std::endl;
std::cout << " unpublish <hash> Remove a tool by hash" << std::endl; std::cout << " unpublish [--server <url>] <hash> Remove a tool by hash" << std::endl;
std::cout << " Requires SOS_WRITE_TOKEN environment variable" << std::endl; std::cout << " Uses default publish server if --server not specified" << std::endl;
std::cout << " Without :ARCH, removes x86_64, aarch64, and universal versions" << std::endl; std::cout << " Without :ARCH, removes x86_64, aarch64, and universal versions" << std::endl;
std::cout << std::endl; std::cout << std::endl;
std::cout << " update Update getpkg and all installed tools" << std::endl; std::cout << " update Update getpkg and all installed tools" << std::endl;
@ -1147,8 +1281,10 @@ void show_help() {
std::cout << " getpkg install myapp Install myapp" << std::endl; std::cout << " getpkg install myapp Install myapp" << std::endl;
std::cout << " getpkg publish myapp:x86_64 ./build Publish architecture-specific build" << std::endl; std::cout << " getpkg publish myapp:x86_64 ./build Publish architecture-specific build" << std::endl;
std::cout << " getpkg publish myapp ./build Publish universal build" << std::endl; std::cout << " getpkg publish myapp ./build Publish universal build" << std::endl;
std::cout << " getpkg publish --server example.com myapp ./build Publish to specific server" << std::endl;
std::cout << " getpkg unpublish myapp Remove ALL architectures of myapp" << std::endl; std::cout << " getpkg unpublish myapp Remove ALL architectures of myapp" << std::endl;
std::cout << " getpkg unpublish myapp:x86_64 Remove only x86_64 version" << std::endl; std::cout << " getpkg unpublish myapp:x86_64 Remove only x86_64 version" << std::endl;
std::cout << " getpkg unpublish --server example.com myapp Remove from specific server" << std::endl;
std::cout << " getpkg uninstall myapp Remove myapp from system" << std::endl; std::cout << " getpkg uninstall myapp Remove myapp from system" << std::endl;
std::cout << " getpkg update Update everything" << std::endl; std::cout << " getpkg update Update everything" << std::endl;
std::cout << " getpkg server add packages.example.com Add a custom package server" << std::endl; std::cout << " getpkg server add packages.example.com Add a custom package server" << std::endl;