mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
feat(cli): add workflow and package DBAL commands
New workflow commands: list, get, run, create, status New package commands: install, uninstall, info, search All backed by DBAL REST API with formatted table/JSON output. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ add_executable(metabuilder-cli
|
||||
src/commands/command_dispatch.cpp
|
||||
src/commands/dbal_commands.cpp
|
||||
src/commands/package_commands.cpp
|
||||
src/commands/workflow_commands.cpp
|
||||
src/lua/lua_runner.cpp
|
||||
src/utils/http_client.cpp
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "command_dispatch.h"
|
||||
#include "dbal_commands.h"
|
||||
#include "package_commands.h"
|
||||
#include "workflow_commands.h"
|
||||
|
||||
#include <cpr/cpr.h>
|
||||
#include <iostream>
|
||||
@@ -18,6 +19,7 @@ Available commands:
|
||||
tenant get <tenantId> Get a tenant by ID
|
||||
dbal <subcommand> DBAL operations (use 'dbal help' for details)
|
||||
package <subcommand> Package operations (use 'package help' for details)
|
||||
workflow <subcommand> Workflow operations (use 'workflow help' for details)
|
||||
)";
|
||||
}
|
||||
|
||||
@@ -136,7 +138,11 @@ int dispatch(const HttpClient &client, const std::vector<std::string> &args) {
|
||||
}
|
||||
|
||||
if (args[0] == "package") {
|
||||
return handle_package(args);
|
||||
return handle_package(client, args);
|
||||
}
|
||||
|
||||
if (args[0] == "workflow") {
|
||||
return handle_workflow(client, args);
|
||||
}
|
||||
|
||||
print_help();
|
||||
|
||||
@@ -1,17 +1,30 @@
|
||||
#include "package_commands.h"
|
||||
#include "../lua/lua_runner.h"
|
||||
|
||||
#include <cpr/cpr.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace {
|
||||
|
||||
void print_response(const cpr::Response &response) {
|
||||
std::cout << "status: " << response.status_code << '\n';
|
||||
if (response.error) {
|
||||
std::cout << "error: " << response.error.message << '\n';
|
||||
}
|
||||
std::cout << response.text << '\n';
|
||||
}
|
||||
|
||||
void print_package_help() {
|
||||
std::cout << R"(Usage: metabuilder-cli package <command> [options]
|
||||
|
||||
@@ -19,6 +32,10 @@ Commands:
|
||||
list List available packages with scripts
|
||||
run <package> <script> [args] Run a Lua script from a package
|
||||
generate <package_id> [opts] Generate a new package
|
||||
install <package_id> Install a package via DBAL
|
||||
uninstall <package_id> Uninstall a package via DBAL
|
||||
info <package_id> Show package details from DBAL
|
||||
search <query> Search available packages
|
||||
|
||||
Generate options:
|
||||
--name <name> Display name (default: derived from package_id)
|
||||
@@ -39,6 +56,10 @@ Examples:
|
||||
metabuilder-cli package list
|
||||
metabuilder-cli package run codegen_studio package_template
|
||||
metabuilder-cli package generate my_forum --category social --with-schema --entities Thread,Post
|
||||
metabuilder-cli package install forum
|
||||
metabuilder-cli package uninstall forum
|
||||
metabuilder-cli package info forum
|
||||
metabuilder-cli package search social
|
||||
)";
|
||||
}
|
||||
|
||||
@@ -56,7 +77,7 @@ fs::path find_packages_dir() {
|
||||
|
||||
// Try relative to executable
|
||||
// (would need to pass argv[0] for this)
|
||||
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -77,16 +98,16 @@ std::vector<std::string> split_csv(const std::string& str) {
|
||||
|
||||
int handle_list(const fs::path& packages_dir) {
|
||||
std::cout << "Available packages with scripts:\n\n";
|
||||
|
||||
|
||||
int count = 0;
|
||||
for (const auto& entry : fs::directory_iterator(packages_dir)) {
|
||||
if (!entry.is_directory()) continue;
|
||||
|
||||
|
||||
auto scripts_path = entry.path() / "seed" / "scripts";
|
||||
if (!fs::exists(scripts_path)) continue;
|
||||
|
||||
|
||||
std::cout << " " << entry.path().filename().string() << "\n";
|
||||
|
||||
|
||||
// List available scripts/modules
|
||||
for (const auto& script : fs::directory_iterator(scripts_path)) {
|
||||
if (script.is_directory()) {
|
||||
@@ -103,11 +124,11 @@ int handle_list(const fs::path& packages_dir) {
|
||||
}
|
||||
++count;
|
||||
}
|
||||
|
||||
|
||||
if (count == 0) {
|
||||
std::cout << " (no packages with scripts found)\n";
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -122,7 +143,7 @@ int handle_run(const fs::path& packages_dir, const std::vector<std::string>& arg
|
||||
std::string func_name = args.size() > 4 ? args[4] : "main";
|
||||
|
||||
lua::LuaRunner runner(packages_dir);
|
||||
|
||||
|
||||
if (!runner.load_module(package_id, script_name)) {
|
||||
std::cerr << "Error: " << runner.last_error() << "\n";
|
||||
return 1;
|
||||
@@ -140,7 +161,7 @@ int handle_run(const fs::path& packages_dir, const std::vector<std::string>& arg
|
||||
}
|
||||
|
||||
auto result = runner.call(func_name, config);
|
||||
|
||||
|
||||
if (!result.success) {
|
||||
std::cerr << "Error: " << result.error << "\n";
|
||||
return 1;
|
||||
@@ -160,7 +181,7 @@ int handle_generate(const fs::path& packages_dir, const std::vector<std::string>
|
||||
}
|
||||
|
||||
const auto& package_id = args[2];
|
||||
|
||||
|
||||
// Validate package_id format
|
||||
if (package_id.empty() || !std::isalpha(package_id[0])) {
|
||||
std::cerr << "Error: package_id must start with a letter\n";
|
||||
@@ -186,13 +207,13 @@ int handle_generate(const fs::path& packages_dir, const std::vector<std::string>
|
||||
config["withSchema"] = false;
|
||||
config["withTests"] = true;
|
||||
config["withComponents"] = false;
|
||||
|
||||
|
||||
bool dry_run = false;
|
||||
std::string output_dir = packages_dir.string();
|
||||
|
||||
for (size_t i = 3; i < args.size(); ++i) {
|
||||
const auto& arg = args[i];
|
||||
|
||||
|
||||
if (arg == "--name" && i + 1 < args.size()) {
|
||||
config["name"] = args[++i];
|
||||
} else if (arg == "--description" && i + 1 < args.size()) {
|
||||
@@ -224,7 +245,7 @@ int handle_generate(const fs::path& packages_dir, const std::vector<std::string>
|
||||
|
||||
// Load package_template module from codegen_studio
|
||||
lua::LuaRunner runner(packages_dir);
|
||||
|
||||
|
||||
if (!runner.load_module("codegen_studio", "package_template")) {
|
||||
std::cerr << "Error: Could not load package_template module\n";
|
||||
std::cerr << " " << runner.last_error() << "\n";
|
||||
@@ -244,7 +265,7 @@ int handle_generate(const fs::path& packages_dir, const std::vector<std::string>
|
||||
|
||||
// Generate files
|
||||
auto result = runner.call("generate", config);
|
||||
|
||||
|
||||
if (!result.success) {
|
||||
std::cerr << "Error generating package: " << result.error << "\n";
|
||||
return 1;
|
||||
@@ -278,31 +299,179 @@ int handle_generate(const fs::path& packages_dir, const std::vector<std::string>
|
||||
for (const auto& file : result.files) {
|
||||
fs::path full_path = package_path / file.path;
|
||||
fs::path dir = full_path.parent_path();
|
||||
|
||||
|
||||
if (!dir.empty() && !fs::exists(dir)) {
|
||||
fs::create_directories(dir);
|
||||
}
|
||||
|
||||
|
||||
std::ofstream out(full_path, std::ios::binary);
|
||||
if (!out) {
|
||||
std::cerr << " Error writing: " << file.path << "\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
out << file.content;
|
||||
out.close();
|
||||
|
||||
|
||||
std::cout << " Created: " << file.path << "\n";
|
||||
++written;
|
||||
}
|
||||
|
||||
std::cout << "\n✅ Package '" << package_id << "' created successfully!\n";
|
||||
std::cout << "\nPackage '" << package_id << "' created successfully!\n";
|
||||
std::cout << " Files: " << written << "\n";
|
||||
std::cout << "\nNext steps:\n";
|
||||
std::cout << " 1. Review generated files in " << package_path << "\n";
|
||||
std::cout << " 2. Add package-specific logic to seed/scripts/\n";
|
||||
std::cout << " 3. Run: npm run packages:index\n";
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Print package list from DBAL as a formatted table
|
||||
*/
|
||||
void print_package_table(const cpr::Response &response) {
|
||||
if (response.status_code != 200) {
|
||||
print_response(response);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
auto data = json::parse(response.text);
|
||||
|
||||
json items;
|
||||
if (data.is_array()) {
|
||||
items = data;
|
||||
} else if (data.contains("data") && data["data"].is_array()) {
|
||||
items = data["data"];
|
||||
} else {
|
||||
print_response(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (items.empty()) {
|
||||
std::cout << "No packages found.\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// Header
|
||||
std::cout << '\n';
|
||||
std::cout << " " << std::left
|
||||
<< std::setw(24) << "ID"
|
||||
<< std::setw(28) << "NAME"
|
||||
<< std::setw(14) << "CATEGORY"
|
||||
<< std::setw(10) << "STATUS"
|
||||
<< "VERSION" << '\n';
|
||||
std::cout << " " << std::string(86, '-') << '\n';
|
||||
|
||||
for (const auto &pkg : items) {
|
||||
std::string id = pkg.value("id", pkg.value("packageId", "-"));
|
||||
std::string name = pkg.value("name", pkg.value("title", "-"));
|
||||
std::string category = pkg.value("category", "-");
|
||||
std::string status = pkg.value("status", pkg.value("state", "-"));
|
||||
std::string version = pkg.value("version", "-");
|
||||
|
||||
std::cout << " " << std::left
|
||||
<< std::setw(24) << id
|
||||
<< std::setw(28) << name
|
||||
<< std::setw(14) << category
|
||||
<< std::setw(10) << status
|
||||
<< version << '\n';
|
||||
}
|
||||
|
||||
std::cout << '\n' << items.size() << " package(s) found.\n";
|
||||
} catch (const json::exception &) {
|
||||
print_response(response);
|
||||
}
|
||||
}
|
||||
|
||||
int handle_install(const HttpClient &client, const std::vector<std::string> &args) {
|
||||
if (args.size() < 3) {
|
||||
std::cerr << "Usage: metabuilder-cli package install <package_id>\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string package_id = args[2];
|
||||
std::string body = "{\"packageId\":\"" + package_id + "\",\"action\":\"install\"}";
|
||||
|
||||
std::cout << "Installing package: " << package_id << "\n";
|
||||
auto response = client.post("/api/dbal/package/" + package_id + "/install", body);
|
||||
|
||||
if (response.status_code >= 200 && response.status_code < 300) {
|
||||
std::cout << "[OK] Package '" << package_id << "' installed successfully.\n";
|
||||
try {
|
||||
auto data = json::parse(response.text);
|
||||
if (data.contains("version")) {
|
||||
std::cout << " version: " << data["version"].get<std::string>() << '\n';
|
||||
}
|
||||
if (data.contains("dependencies")) {
|
||||
std::cout << " dependencies resolved: " << data["dependencies"].size() << '\n';
|
||||
}
|
||||
} catch (const json::exception &) {
|
||||
// Ignore parse errors for status output
|
||||
}
|
||||
} else {
|
||||
std::cout << "[!!] Failed to install package '" << package_id << "'.\n";
|
||||
print_response(response);
|
||||
}
|
||||
|
||||
return response.status_code >= 200 && response.status_code < 300 ? 0 : 1;
|
||||
}
|
||||
|
||||
int handle_uninstall(const HttpClient &client, const std::vector<std::string> &args) {
|
||||
if (args.size() < 3) {
|
||||
std::cerr << "Usage: metabuilder-cli package uninstall <package_id>\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string package_id = args[2];
|
||||
|
||||
std::cout << "Uninstalling package: " << package_id << "\n";
|
||||
auto response = client.post("/api/dbal/package/" + package_id + "/uninstall",
|
||||
"{\"packageId\":\"" + package_id + "\",\"action\":\"uninstall\"}");
|
||||
|
||||
if (response.status_code >= 200 && response.status_code < 300) {
|
||||
std::cout << "[OK] Package '" << package_id << "' uninstalled successfully.\n";
|
||||
} else {
|
||||
std::cout << "[!!] Failed to uninstall package '" << package_id << "'.\n";
|
||||
print_response(response);
|
||||
}
|
||||
|
||||
return response.status_code >= 200 && response.status_code < 300 ? 0 : 1;
|
||||
}
|
||||
|
||||
int handle_info(const HttpClient &client, const std::vector<std::string> &args) {
|
||||
if (args.size() < 3) {
|
||||
std::cerr << "Usage: metabuilder-cli package info <package_id>\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string package_id = args[2];
|
||||
auto response = client.get("/api/dbal/package/" + package_id);
|
||||
|
||||
if (response.status_code != 200) {
|
||||
print_response(response);
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
auto data = json::parse(response.text);
|
||||
std::cout << data.dump(2) << '\n';
|
||||
} catch (const json::exception &) {
|
||||
print_response(response);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int handle_search(const HttpClient &client, const std::vector<std::string> &args) {
|
||||
if (args.size() < 3) {
|
||||
std::cerr << "Usage: metabuilder-cli package search <query>\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string query = args[2];
|
||||
print_package_table(client.get("/api/dbal/package?search=" + query));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -310,30 +479,59 @@ int handle_generate(const fs::path& packages_dir, const std::vector<std::string>
|
||||
|
||||
namespace commands {
|
||||
|
||||
int handle_package(const std::vector<std::string>& args) {
|
||||
int handle_package(const HttpClient &client, const std::vector<std::string>& args) {
|
||||
if (args.size() < 2 || args[1] == "help" || args[1] == "--help") {
|
||||
print_package_help();
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto packages_dir = find_packages_dir();
|
||||
if (packages_dir.empty()) {
|
||||
std::cerr << "Error: Could not find packages directory\n";
|
||||
std::cerr << "Run from the MetaBuilder project root or set METABUILDER_PACKAGES\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
const auto& subcommand = args[1];
|
||||
|
||||
// DBAL-backed commands (require HTTP client)
|
||||
if (subcommand == "install") {
|
||||
return handle_install(client, args);
|
||||
}
|
||||
|
||||
if (subcommand == "uninstall") {
|
||||
return handle_uninstall(client, args);
|
||||
}
|
||||
|
||||
if (subcommand == "info") {
|
||||
return handle_info(client, args);
|
||||
}
|
||||
|
||||
if (subcommand == "search") {
|
||||
return handle_search(client, args);
|
||||
}
|
||||
|
||||
// Local filesystem commands
|
||||
if (subcommand == "list") {
|
||||
auto packages_dir = find_packages_dir();
|
||||
if (packages_dir.empty()) {
|
||||
std::cerr << "Error: Could not find packages directory\n";
|
||||
std::cerr << "Run from the MetaBuilder project root or set METABUILDER_PACKAGES\n";
|
||||
return 1;
|
||||
}
|
||||
return handle_list(packages_dir);
|
||||
}
|
||||
|
||||
|
||||
if (subcommand == "run") {
|
||||
auto packages_dir = find_packages_dir();
|
||||
if (packages_dir.empty()) {
|
||||
std::cerr << "Error: Could not find packages directory\n";
|
||||
std::cerr << "Run from the MetaBuilder project root or set METABUILDER_PACKAGES\n";
|
||||
return 1;
|
||||
}
|
||||
return handle_run(packages_dir, args);
|
||||
}
|
||||
|
||||
|
||||
if (subcommand == "generate") {
|
||||
auto packages_dir = find_packages_dir();
|
||||
if (packages_dir.empty()) {
|
||||
std::cerr << "Error: Could not find packages directory\n";
|
||||
std::cerr << "Run from the MetaBuilder project root or set METABUILDER_PACKAGES\n";
|
||||
return 1;
|
||||
}
|
||||
return handle_generate(packages_dir, args);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "../utils/http_client.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -7,12 +8,16 @@ namespace commands {
|
||||
|
||||
/**
|
||||
* Handle package commands
|
||||
*
|
||||
*
|
||||
* Usage:
|
||||
* package list List available packages
|
||||
* package run <pkg> <script> [args] Run a Lua script from a package
|
||||
* package generate <pkg_id> Generate a new package (uses package_generator)
|
||||
* package list List available packages
|
||||
* package run <pkg> <script> [args] Run a Lua script from a package
|
||||
* package generate <pkg_id> Generate a new package (uses package_generator)
|
||||
* package install <pkg_id> Install a package via DBAL
|
||||
* package uninstall <pkg_id> Uninstall a package via DBAL
|
||||
* package info <pkg_id> Show package details from DBAL
|
||||
* package search <query> Search available packages
|
||||
*/
|
||||
int handle_package(const std::vector<std::string>& args);
|
||||
int handle_package(const HttpClient &client, const std::vector<std::string>& args);
|
||||
|
||||
} // namespace commands
|
||||
|
||||
318
frontends/cli/src/commands/workflow_commands.cpp
Normal file
318
frontends/cli/src/commands/workflow_commands.cpp
Normal file
@@ -0,0 +1,318 @@
|
||||
/**
|
||||
* @file workflow_commands.cpp
|
||||
* @brief Workflow command handler implementations
|
||||
*/
|
||||
|
||||
#include "workflow_commands.h"
|
||||
#include <cpr/cpr.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace {
|
||||
|
||||
void print_response(const cpr::Response &response) {
|
||||
std::cout << "status: " << response.status_code << '\n';
|
||||
if (response.error) {
|
||||
std::cout << "error: " << response.error.message << '\n';
|
||||
}
|
||||
std::cout << response.text << '\n';
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Print a formatted table row with fixed column widths
|
||||
*/
|
||||
void print_table_row(const std::string &id, const std::string &name,
|
||||
const std::string &status, const std::string &version) {
|
||||
std::cout << " " << std::left << std::setw(26) << id << std::setw(30)
|
||||
<< name << std::setw(12) << status << version << '\n';
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Print workflow list as a formatted table
|
||||
*/
|
||||
void print_workflow_table(const cpr::Response &response) {
|
||||
if (response.status_code != 200) {
|
||||
print_response(response);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
auto data = json::parse(response.text);
|
||||
|
||||
// Handle both array and { data: [...] } shapes
|
||||
json items;
|
||||
if (data.is_array()) {
|
||||
items = data;
|
||||
} else if (data.contains("data") && data["data"].is_array()) {
|
||||
items = data["data"];
|
||||
} else {
|
||||
// Single object or unexpected shape — fall back to raw output
|
||||
print_response(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (items.empty()) {
|
||||
std::cout << "No workflows found.\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// Header
|
||||
std::cout << '\n';
|
||||
print_table_row("ID", "NAME", "STATUS", "VERSION");
|
||||
std::cout << " " << std::string(78, '-') << '\n';
|
||||
|
||||
for (const auto &wf : items) {
|
||||
std::string id = wf.value("id", wf.value("workflowId", "-"));
|
||||
std::string name = wf.value("name", wf.value("title", "-"));
|
||||
std::string status = wf.value("status", wf.value("state", "-"));
|
||||
std::string version = wf.value("version", "-");
|
||||
print_table_row(id, name, status, version);
|
||||
}
|
||||
|
||||
std::cout << '\n' << items.size() << " workflow(s) found.\n";
|
||||
} catch (const json::exception &) {
|
||||
// JSON parse failed — show raw response
|
||||
print_response(response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Pretty-print a single workflow as indented JSON
|
||||
*/
|
||||
void print_workflow_detail(const cpr::Response &response) {
|
||||
if (response.status_code != 200) {
|
||||
print_response(response);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
auto data = json::parse(response.text);
|
||||
std::cout << data.dump(2) << '\n';
|
||||
} catch (const json::exception &) {
|
||||
print_response(response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Print workflow execution result with status indicator
|
||||
*/
|
||||
void print_run_result(const cpr::Response &response) {
|
||||
std::cout << "status: " << response.status_code << '\n';
|
||||
|
||||
if (response.error) {
|
||||
std::cout << "error: " << response.error.message << '\n';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
auto data = json::parse(response.text);
|
||||
|
||||
std::string state = data.value("status", data.value("state", "unknown"));
|
||||
std::string exec_id =
|
||||
data.value("executionId", data.value("id", "unknown"));
|
||||
|
||||
if (state == "completed" || state == "success") {
|
||||
std::cout << "[OK] Workflow executed successfully\n";
|
||||
} else if (state == "running" || state == "pending") {
|
||||
std::cout << "[..] Workflow execution started\n";
|
||||
} else {
|
||||
std::cout << "[!!] Workflow execution returned: " << state << '\n';
|
||||
}
|
||||
|
||||
std::cout << " execution-id: " << exec_id << '\n';
|
||||
|
||||
if (data.contains("output")) {
|
||||
std::cout << " output: " << data["output"].dump(2) << '\n';
|
||||
}
|
||||
|
||||
if (data.contains("error")) {
|
||||
std::cout << " error: " << data["error"].dump() << '\n';
|
||||
}
|
||||
} catch (const json::exception &) {
|
||||
std::cout << response.text << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
int workflow_list(const HttpClient &client) {
|
||||
print_workflow_table(client.get("/api/dbal/workflow"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int workflow_get(const HttpClient &client,
|
||||
const std::vector<std::string> &args) {
|
||||
if (args.size() < 3) {
|
||||
std::cout << "Usage: workflow get <id>\n";
|
||||
std::cout << "Example: workflow get wf_user_created\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
print_workflow_detail(client.get("/api/dbal/workflow/" + args[2]));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int workflow_run(const HttpClient &client,
|
||||
const std::vector<std::string> &args) {
|
||||
if (args.size() < 3) {
|
||||
std::cout
|
||||
<< "Usage: workflow run <id> [param=value ...]\n"
|
||||
<< "Example: workflow run wf_user_created userId=clx123 tenant=acme\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string id = args[2];
|
||||
|
||||
// Build input params from remaining key=value pairs
|
||||
std::ostringstream body;
|
||||
body << "{\"workflowId\":\"" << id << "\"";
|
||||
|
||||
if (args.size() > 3) {
|
||||
body << ",\"input\":{";
|
||||
bool first = true;
|
||||
for (size_t i = 3; i < args.size(); ++i) {
|
||||
auto eq_pos = args[i].find('=');
|
||||
if (eq_pos == std::string::npos)
|
||||
continue;
|
||||
|
||||
if (!first)
|
||||
body << ",";
|
||||
first = false;
|
||||
|
||||
std::string key = args[i].substr(0, eq_pos);
|
||||
std::string value = args[i].substr(eq_pos + 1);
|
||||
|
||||
// Simple type detection (matches dbal_commands pattern)
|
||||
if (value == "true" || value == "false" ||
|
||||
(value.find_first_not_of("0123456789.-") == std::string::npos &&
|
||||
!value.empty())) {
|
||||
body << "\"" << key << "\":" << value;
|
||||
} else {
|
||||
body << "\"" << key << "\":\"" << value << "\"";
|
||||
}
|
||||
}
|
||||
body << "}";
|
||||
}
|
||||
|
||||
body << "}";
|
||||
|
||||
std::cout << "Running workflow: " << id << "\n";
|
||||
print_run_result(
|
||||
client.post("/api/dbal/workflow/" + id + "/execute", body.str()));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int workflow_create(const HttpClient &client,
|
||||
const std::vector<std::string> &args) {
|
||||
if (args.size() < 3) {
|
||||
std::cout << "Usage: workflow create <file.json>\n";
|
||||
std::cout << "Example: workflow create my_workflow.json\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string filepath = args[2];
|
||||
|
||||
std::ifstream file(filepath);
|
||||
if (!file.is_open()) {
|
||||
std::cerr << "Error: Could not open file: " << filepath << "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Read entire file
|
||||
std::string body((std::istreambuf_iterator<char>(file)),
|
||||
std::istreambuf_iterator<char>());
|
||||
file.close();
|
||||
|
||||
// Validate JSON before sending
|
||||
try {
|
||||
auto parsed = json::parse(body);
|
||||
// Re-serialize to ensure clean JSON
|
||||
body = parsed.dump();
|
||||
} catch (const json::exception &e) {
|
||||
std::cerr << "Error: Invalid JSON in " << filepath << "\n";
|
||||
std::cerr << " " << e.what() << "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Creating workflow from: " << filepath << "\n";
|
||||
print_workflow_detail(client.post("/api/dbal/workflow", body));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int workflow_status(const HttpClient &client,
|
||||
const std::vector<std::string> &args) {
|
||||
if (args.size() < 3) {
|
||||
std::cout << "Usage: workflow status <execution-id>\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
print_run_result(
|
||||
client.get("/api/dbal/workflow/executions/" + args[2]));
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace commands {
|
||||
|
||||
void print_workflow_help() {
|
||||
std::cout << R"(Workflow Commands:
|
||||
workflow list List all workflows
|
||||
workflow get <id> Show workflow details (JSON)
|
||||
workflow run <id> [param=value ...] Execute a workflow with optional input
|
||||
workflow create <file.json> Create a workflow from a JSON file
|
||||
workflow status <execution-id> Check execution status
|
||||
|
||||
Examples:
|
||||
workflow list
|
||||
workflow get wf_user_created
|
||||
workflow run wf_user_created userId=clx123 tenant=acme
|
||||
workflow create workflows/on_post_created.json
|
||||
workflow status exec_abc123
|
||||
)";
|
||||
}
|
||||
|
||||
int handle_workflow(const HttpClient &client,
|
||||
const std::vector<std::string> &args) {
|
||||
if (args.size() < 2) {
|
||||
print_workflow_help();
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string subcommand = args[1];
|
||||
|
||||
if (subcommand == "list") {
|
||||
return workflow_list(client);
|
||||
}
|
||||
|
||||
if (subcommand == "get") {
|
||||
return workflow_get(client, args);
|
||||
}
|
||||
|
||||
if (subcommand == "run") {
|
||||
return workflow_run(client, args);
|
||||
}
|
||||
|
||||
if (subcommand == "create") {
|
||||
return workflow_create(client, args);
|
||||
}
|
||||
|
||||
if (subcommand == "status") {
|
||||
return workflow_status(client, args);
|
||||
}
|
||||
|
||||
if (subcommand == "help" || subcommand == "-h" || subcommand == "--help") {
|
||||
print_workflow_help();
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::cout << "Unknown workflow subcommand: " << subcommand << "\n";
|
||||
print_workflow_help();
|
||||
return 1;
|
||||
}
|
||||
|
||||
} // namespace commands
|
||||
36
frontends/cli/src/commands/workflow_commands.h
Normal file
36
frontends/cli/src/commands/workflow_commands.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* @file workflow_commands.h
|
||||
* @brief Workflow command handlers for CLI
|
||||
*
|
||||
* Provides CLI commands for workflow operations:
|
||||
* - workflow list List all workflows
|
||||
* - workflow get <id> Show workflow details
|
||||
* - workflow run <id> Execute a workflow
|
||||
* - workflow create <file> Create workflow from JSON file
|
||||
*/
|
||||
|
||||
#ifndef WORKFLOW_COMMANDS_H
|
||||
#define WORKFLOW_COMMANDS_H
|
||||
|
||||
#include "../utils/http_client.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace commands {
|
||||
|
||||
/**
|
||||
* @brief Handle workflow-related commands
|
||||
* @param client HTTP client instance
|
||||
* @param args Command arguments (first element is "workflow")
|
||||
* @return Exit code (0 = success)
|
||||
*/
|
||||
int handle_workflow(const HttpClient &client, const std::vector<std::string> &args);
|
||||
|
||||
/**
|
||||
* @brief Print workflow command help
|
||||
*/
|
||||
void print_workflow_help();
|
||||
|
||||
} // namespace commands
|
||||
|
||||
#endif // WORKFLOW_COMMANDS_H
|
||||
Reference in New Issue
Block a user